Skip to content

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

Draft
kinyoklion wants to merge 4 commits into
rlamb/overrides-python-model-markerfrom
rlamb/overrides-python-override-store
Draft

kinyoklion wants to merge 4 commits into
rlamb/overrides-python-model-markerfrom
rlamb/overrides-python-override-store

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This is the third step of the flag overrides port described by the OVERRIDE specification. It is based on the model marker branch because the phases are stacked; retarget to feat/overrides once that branch merges.

The override layer (ldclient/impl/overrides). OverrideLayer is the override store: it holds marked shallow copies of the flag and segment definitions a source supplies, keyed by key, and is replaced wholesale on each update, so the layer's contents are exactly one snapshot at any instant and an empty snapshot clears it. Dictionaries are decoded with the model constructors, and an invalid definition raises rather than being defaulted. OverrideStoreView is the overlay at the store read boundary: a read of a flag or segment returns the override entry when one exists and the LaunchDarkly entry otherwise, whatever the state of the base store, and an enumeration is the union of both with the override entry winning, including over a deleted item. When the base enumeration fails and the layer holds entries, the layer's entries are returned alone. OverrideSinkImpl applies each snapshot as one serialized operation and notifies flag change listeners of every flag whose merged-view evaluation may have changed: entries added, removed, or changed, plus every flag that depends on them through prerequisites or segment references. An identical snapshot notifies nothing.

Configuration and wiring. OverrideSource and OverrideSink are public protocols in ldclient.interfaces, so a custom source can be supplied. OverrideSourceBuilder and DataSystemConfig.override_source (also on AsyncDataSystemConfig) plus ConfigBuilder.overrides(...) make the source an option of the FDv2 data system. The source is built with the data system, so invalid configuration raises from the client constructor like any other invalid component configuration. It starts before the data system's own threads and its initial load completes before the constructor returns; it is closed when the client is closed; it is not started when the client is offline. The data system's store becomes the overlay when a source is configured, so evaluation, prerequisite and segment resolution, and the all-flags read all see override precedence. The override source has no effect on initialization status, data availability, or data source status. The legacy FDv1 data system reports no override source.

The client facade. Both LDClient and AsyncLDClient consult the override layer before the not-initialized short-circuit: an overridden flag is served before the client has LaunchDarkly data, and a flag the layer does not hold takes exactly the path it took before this feature, so a configured but empty source changes nothing with the same warning and event as before. The all-flags state reads through the overlay; while the client has no LaunchDarkly data it contains only the overridden flags (with a once-per-client warning) and is invalid when the layer is empty. On the async client, override change notifications are marshalled onto the event loop, like every other change notification.

Tests. Unit tests for the layer, overlay (sync and async), and sink; client tests for the gate, source lifecycle, construction errors, offline, precedence, prerequisite and segment overrides, all-flags state, and flag change and flag value change listeners, for both clients; and a runner for the OVERRIDE specification test vectors (ldclient/testing/testdata/override-vectors/vectors.json) through the full client stack. The vector runner's per-evaluation summary marker assertion is added with the events step.

SDK-3250


Note

Overview
Adds an experimental flag/segment override path on the FDv2 data system: operators can plug in an OverrideSource (via ConfigBuilder.overrides / DataSystemConfig.override_source) whose snapshots land in an OverrideLayer and are served ahead of LaunchDarkly data through store overlays (OverrideStoreView / async equivalent). Overrides do not change initialization or data-source status; FDv1 still reports no override layer.

Sync and async clients now consult override_layer when LaunchDarkly data is unavailable: overridden keys evaluate normally (including overrideAffected reasons); other keys still get CLIENT_NOT_READY. all_flags_state can return override-only state before init (with a once-per-client warning) instead of always returning invalid. The sync client also tears down data system / big-segment / event processor if data-system start() fails during construction.

New OverrideSource / OverrideSink protocols, OverrideSinkImpl change notifications (with prerequisite/segment fan-out), and broad unit/client tests plus OVERRIDE spec vectors.

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

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-model-marker branch from 34322d8 to ee313c1 Compare September 28, 2026 20:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-override-store branch from 3fcdc80 to 19f88eb Compare September 28, 2026 20:29
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-model-marker branch from ee313c1 to 2c70075 Compare September 30, 2026 20:31
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-override-store branch from 19f88eb to 7e7b3f4 Compare September 30, 2026 20:31
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-model-marker branch from 2c70075 to 5f738a6 Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-override-store branch from 7e7b3f4 to aa28264 Compare October 1, 2026 23:43
Adds the override layer described by the OVERRIDE specification: an override
store that holds marked flag and segment definitions, an overlay at the store
read boundary that returns the override entry for a key in preference to
LaunchDarkly data and enumerates the union of both, and a sink that applies
each snapshot as a single replacement and notifies flag change listeners of
every flag whose evaluation may have changed, including flags that depend on
a changed prerequisite or segment.

The override source is an option of the FDv2 data system configuration:
DataSystemConfig.override_source and ConfigBuilder.overrides(). The source
is built with the data system, so invalid configuration raises from the
client constructor. It starts before the data system's own threads, so its
initial load completes before the constructor returns, and it is closed with
the client. OverrideSource and OverrideSink are public protocols so a custom
source can be supplied.

The client consults the override store before its not-initialized
short-circuit. An overridden flag is served before the client has
LaunchDarkly data, and a flag absent from the override store still returns
the client-not-ready default. The all-flags state reads through the overlay
and, while the client has no LaunchDarkly data, contains only the overridden
flags. The async client and data system receive the same changes, with
override change notifications delivered on the event loop.

The OVERRIDE specification test vectors run as a unit test through the full
client stack.
…arkly data

Before initialization the clients consult the override layer directly. A flag the layer does not hold gets the not-ready handling it had without an override source, for an invalid context, a store outage, and an uninitialized persistent store alike. The all-flags state is unavailable when the layer is empty. The sync client stops the components it started when the data system fails to start.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-model-marker branch from 5f738a6 to 0013c0e Compare October 3, 2026 01:18
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-python-override-store branch from aa28264 to 1c1bd5c Compare October 3, 2026 01:18
@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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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 1c1bd5c. Configure here.

Comment thread ldclient/client.py
with self._cached_data_warning_lock:
if not self._all_flags_overrides_only_warned:
self._all_flags_overrides_only_warned = True
log.warning("all_flags_state() called before client has finished initializing! Returning only flags from the override layer. This message is logged once.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All-flags includes unavailable store flags

Medium Severity

When availability is DEFAULTS and the override layer is non-empty, all_flags_state reads through the overlay instead of the layer. The overlay unions in whatever the base store still holds, so flags from an uninitialized persistent store can appear in a valid state even though variation() returns CLIENT_NOT_READY for those keys. The once-per-client warning also claims the result contains only override-layer flags.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c1bd5c. Configure here.

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