Skip to content

fix: Base the validity of the uninitialized all flags state on the override layer's flags - #2132

Merged
kinyoklion merged 3 commits into
mainfrom
rlamb/overrides-node-all-flags-layer-only
Nov 2, 2026
Merged

kinyoklion merged 3 commits into
mainfrom
rlamb/overrides-node-all-flags-layer-only

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Summary

Before the client is initialized, and while the data store is not initialized either, allFlagsState evaluates whatever flags the store holds into an invalid state. A store can hold LaunchDarkly flags without being initialized, for example a persistent store with data but without the marker that a complete data set was written. With an override source configured, the state was marked valid whenever the merged read through the overlay returned any flag, so a source whose layer is empty turned that invalid state into a valid one. Configuring a source that supplies nothing must change nothing (OVERRIDE Requirement 1.2.2).

Whether the override layer holds flags now decides the validity, instead of whether the merged read returned any. The read itself still goes through the overlay, so the state holds the store's flags with the override layer's entries taking precedence. Overrides only add to the flags the state would report without them, and never remove one: a layer with flags gives a valid state of the store's flags plus the overrides, and a layer without flags gives exactly the state that a client without an override source gives. The once-per-client warning now says that the state holds flags from the override layer and the data store.

This was found while reviewing the same code in the .NET port (launchdarkly/dotnet-core#364). In the other server-side SDKs this branch returned an empty state before overrides, so a state of the override flags alone adds to it there; in this SDK the store's flags were already in the state, so they stay.

Tests

Two tests in LDClientImpl.overrides.test.ts use a persistent store that holds LaunchDarkly flags but reports that it is not initialized. One checks that a client with an empty override source reports the same invalid state as a client without a source. The other checks that a client with overrides reports the store's flags with the overrides added and taking precedence for a shared key. Reading the layer alone fails the second; deciding validity from the merged read fails the first. The sdk-server suite passes (1561 passed, 7 skipped), and lint and formatting are clean.

Opened from termag

SDK-3419


Note

Overview
When allFlagsState runs before the client and persistent store are initialized, the SDK still reads flags through the store overlay, but LDFlagsState.valid no longer follows “any merged flag came back.” Validity now depends only on whether the override layer has feature flags, so an empty override source cannot turn an otherwise invalid partial-store snapshot into a valid one (OVERRIDE 1.2.2).

If the override layer has flags, the state is valid and includes store flags plus overrides (overrides win on key clashes); the once-per-client warning text now says flags come from both the override layer and the data store. If the layer is empty, behavior matches a client with no override source: invalid state with whatever the store returned, plus the existing “data store not available” path when appropriate.

Tests add an uninitialized store that still holds LD flags and assert the empty-source vs merged-override cases; existing override tests were updated for the new log message.

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

Before the client is initialized and while the store is not initialized, allFlagsState read the
merged view and reported a valid state whenever it returned any flag and a source was
configured. A store that holds LaunchDarkly flags without being initialized therefore produced
a valid state full of LaunchDarkly data when the layer was empty, and LaunchDarkly flags next to
the overridden ones when it was not. The overrides-only state is now built from the layer's
flags, and a layer without flags leaves the state as it is without an override source.
@kinyoklion

Copy link
Copy Markdown
Member Author

bugbot review

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 32220 bytes
Compressed size limit: 32500
Uncompressed size: 152548 bytes

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 34474 bytes
Compressed size limit: 35000
Uncompressed size: 122974 bytes

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 26066 bytes
Compressed size limit: 44000
Uncompressed size: 168326 bytes

@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.

… overrides are present

Without a source, an uninitialized store's flags are evaluated into an
invalid state. Building the state from the override layer alone dropped
them whenever the layer held flags, so configuring overrides removed
flags from the state. The read goes through the overlay again, so the
state holds the store's flags with the override entries taking
precedence; whether the layer holds flags still decides the validity,
so an empty layer leaves the state exactly as without a source.
@kinyoklion kinyoklion changed the title fix: Build the overrides-only all flags state from the override layer fix: Base the validity of the uninitialized all flags state on the override layer's flags Oct 29, 2026
@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 4d26701. Configure here.

@kinyoklion
kinyoklion marked this pull request as ready for review October 29, 2026 21:28
@kinyoklion
kinyoklion requested a review from a team as a code owner October 29, 2026 21:28
The general comment covers the uninitialized store, and the override caveat sits at the override check.
@kinyoklion
kinyoklion merged commit 9cafac5 into main Nov 2, 2026
65 checks passed
@kinyoklion
kinyoklion deleted the rlamb/overrides-node-all-flags-layer-only branch November 2, 2026 19:13
@cursor cursor Bot mentioned this pull request Nov 2, 2026
joker23 pushed a commit that referenced this pull request Nov 3, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>akamai-edgeworker-sdk-common: 2.0.47</summary>

##
[2.0.47](akamai-edgeworker-sdk-common-v2.0.46...akamai-edgeworker-sdk-common-v2.0.47)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from ^2.26.1 to ^2.26.2
</details>

<details><summary>akamai-server-base-sdk: 3.0.48</summary>

##
[3.0.48](akamai-server-base-sdk-v3.0.47...akamai-server-base-sdk-v3.0.48)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
* @launchdarkly/akamai-edgeworker-sdk-common bumped from ^2.0.46 to
^2.0.47
</details>

<details><summary>akamai-server-edgekv-sdk: 1.4.50</summary>

##
[1.4.50](akamai-server-edgekv-sdk-v1.4.49...akamai-server-edgekv-sdk-v1.4.50)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
* @launchdarkly/akamai-edgeworker-sdk-common bumped from ^2.0.46 to
^2.0.47
    * @launchdarkly/js-server-sdk-common bumped from ^2.26.1 to ^2.26.2
</details>

<details><summary>client-testing-plugin: 1.0.29</summary>

##
[1.0.29](client-testing-plugin-v1.0.28...client-testing-plugin-v1.0.29)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/react-sdk bumped from 4.1.30 to 4.2.0
  * peerDependencies
    * @launchdarkly/react-sdk bumped from ^4.1.4 to ^4.2.0
</details>

<details><summary>cloudflare-server-sdk: 2.7.49</summary>

##
[2.7.49](cloudflare-server-sdk-v2.7.48...cloudflare-server-sdk-v2.7.49)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
* @launchdarkly/js-server-sdk-common-edge bumped from 2.6.44 to 2.6.45
</details>

<details><summary>fastly-server-sdk: 0.2.39</summary>

##
[0.2.39](fastly-server-sdk-v0.2.38...fastly-server-sdk-v0.2.39)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
</details>

<details><summary>js-server-sdk-common: 2.26.2</summary>

##
[2.26.2](js-server-sdk-common-v2.26.1...js-server-sdk-common-v2.26.2)
(2026-11-03)


### Bug Fixes

* Base the validity of the uninitialized all flags state on the override
layer's flags
([#2132](#2132))
([9cafac5](9cafac5))
</details>

<details><summary>js-server-sdk-common-edge: 2.6.45</summary>

##
[2.6.45](js-server-sdk-common-edge-v2.6.44...js-server-sdk-common-edge-v2.6.45)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
</details>

<details><summary>node-server-sdk: 9.16.2</summary>

##
[9.16.2](node-server-sdk-v9.16.1...node-server-sdk-v9.16.2)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
</details>

<details><summary>node-server-sdk-dynamodb: 6.4.5</summary>

##
[6.4.5](node-server-sdk-dynamodb-v6.4.4...node-server-sdk-dynamodb-v6.4.5)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/node-server-sdk bumped from 9.16.1 to 9.16.2
  * peerDependencies
    * @launchdarkly/node-server-sdk bumped from >=9.14.1 to >=9.16.2
</details>

<details><summary>node-server-sdk-otel: 1.3.41</summary>

##
[1.3.41](node-server-sdk-otel-v1.3.40...node-server-sdk-otel-v1.3.41)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/node-server-sdk bumped from 9.16.1 to 9.16.2
  * peerDependencies
    * @launchdarkly/node-server-sdk bumped from >=9.11.3 to >=9.16.2
</details>

<details><summary>node-server-sdk-redis: 4.4.5</summary>

##
[4.4.5](node-server-sdk-redis-v4.4.4...node-server-sdk-redis-v4.4.5)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/node-server-sdk bumped from 9.16.1 to 9.16.2
  * peerDependencies
    * @launchdarkly/node-server-sdk bumped from >=9.14.1 to >=9.16.2
</details>

<details><summary>openfeature-cloudflare-server: 1.1.2</summary>

##
[1.1.2](openfeature-cloudflare-server-v1.1.1...openfeature-cloudflare-server-v1.1.2)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/cloudflare-server-sdk bumped from 2.7.48 to 2.7.49
  * peerDependencies
    * @launchdarkly/cloudflare-server-sdk bumped from ^2.7.0 to ^2.7.49
</details>

<details><summary>openfeature-node-server: 2.1.5</summary>

##
[2.1.5](openfeature-node-server-v2.1.4...openfeature-node-server-v2.1.5)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/node-server-sdk bumped from 9.16.1 to 9.16.2
  * peerDependencies
    * @launchdarkly/node-server-sdk bumped from ^9.0.0 to ^9.16.2
</details>

<details><summary>react-sdk: 4.2.0</summary>

##
[4.2.0](react-sdk-v4.1.30...react-sdk-v4.2.0)
(2026-11-03)


### Features

* add fetch-based eventsource entry point to the react sdk
([#2150](#2150))
([a4f23b3](a4f23b3))


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
</details>

<details><summary>server-sdk-ai: 2.0.18</summary>

##
[2.0.18](server-sdk-ai-v2.0.17...server-sdk-ai-v2.0.18)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
  * peerDependencies
    * @launchdarkly/js-server-sdk-common bumped from ^2.0.0 to ^2.26.2
</details>

<details><summary>server-sdk-ai-langchain: 0.8.29</summary>

##
[0.8.29](server-sdk-ai-langchain-v0.8.28...server-sdk-ai-langchain-v0.8.29)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/server-sdk-ai bumped from ^2.0.17 to ^2.0.18
  * peerDependencies
* @launchdarkly/server-sdk-ai bumped from ^1.1.1 || ^2.0.0 to ^2.0.18
</details>

<details><summary>server-sdk-ai-openai: 0.7.29</summary>

##
[0.7.29](server-sdk-ai-openai-v0.7.28...server-sdk-ai-openai-v0.7.29)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
    * @launchdarkly/server-sdk-ai bumped from ^2.0.17 to ^2.0.18
  * peerDependencies
* @launchdarkly/server-sdk-ai bumped from ^1.1.1 || ^2.0.0 to ^2.0.18
</details>

<details><summary>server-sdk-ai-vercel: 0.7.29</summary>

##
[0.7.29](server-sdk-ai-vercel-v0.7.28...server-sdk-ai-vercel-v0.7.29)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * devDependencies
    * @launchdarkly/server-sdk-ai bumped from ^2.0.17 to ^2.0.18
  * peerDependencies
* @launchdarkly/server-sdk-ai bumped from ^1.1.1 || ^2.0.0 to ^2.0.18
</details>

<details><summary>shopify-oxygen-sdk: 0.2.4</summary>

##
[0.2.4](shopify-oxygen-sdk-v0.2.3...shopify-oxygen-sdk-v0.2.4)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * @launchdarkly/js-server-sdk-common bumped from 2.26.1 to 2.26.2
</details>

<details><summary>vercel-server-sdk: 1.3.72</summary>

##
[1.3.72](vercel-server-sdk-v1.3.71...vercel-server-sdk-v1.3.72)
(2026-11-03)


### Dependencies

* The following workspace dependencies were updated
  * dependencies
* @launchdarkly/js-server-sdk-common-edge bumped from 2.6.44 to 2.6.45
</details>

---
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 bumps versions across the
monorepo and refreshes changelogs, manifests, wrapper version strings,
and example dependency pins—there is no new feature code in the diff
itself beyond what earlier merges already landed.
> 
> The meaningful user-facing releases bundled here are
**`@launchdarkly/js-server-sdk-common` 2.26.2**, which fixes how
**uninitialized “all flags” state** is considered valid when
**file/override layers** supply flags
([#2132](#2132)), and
**`@launchdarkly/react-sdk` 4.2.0**, which adds a
**`./fetch-eventsource`** package export for a fetch-based streaming
entry point
([#2150](#2150)).
> 
> Everything else in the PR is downstream **patch/minor version
alignment** (Node server SDK 9.16.2, edge SDKs, OpenFeature wrappers, AI
providers, Redis/DynamoDB/OTel plugins, etc.) picking up the updated
`@launchdarkly/js-server-sdk-common` / edge common dependencies.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
809a8f1. 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>
kinyoklion added a commit to launchdarkly/sdk-test-harness that referenced this pull request Nov 3, 2026
…thout the init key (#539)

<!-- TERMAG_PR_BODY_BEGIN -->
## Summary

Overrides only add to what the SDK reports: a flag that the override
layer does not hold evaluates as it does without an override source, and
the all-flags state holds every flag it holds without one, plus the
overridden flags. A client that has not initialized is where SDKs broke
this, and only when the store holds flags without being initialized, for
example a persistent store that holds data but no init key. The flag
overrides suite uses the SDK's in-memory store, which is empty until a
data source provides data, so it could not tell a read of the override
layer apart from a read of everything the store returns.

A new `flag overrides` group in the persistent data store suite
(`server_side_persistence_overrides.go`) runs when the test service has
the `flag-overrides` capability. It writes flags to the store without
the init key and gives the client a data source that never provides
data. Each test compares a client with an override source to a client
without one against the same store, so it holds whatever an SDK reports
for an uninitialized store:

- **a flag without an override evaluates as it does without an override
source**: an override file that holds a different flag leaves the
evaluation of a store flag unchanged (OVERRIDE Requirements 1.5.1 and
1.5.3).
- **an empty override source leaves the all-flags state unchanged**
(Requirement 1.2.2).
- **overrides add to the all-flags state and make it valid**: the state
holds exactly the flags of the client without a source, with override
values for the overridden keys, plus the flags that exist only in the
override layer, and it is valid (launchdarkly/sdk-specs#280,
Requirements 1.4.1.2 and 1.4.1.3).

The flag overrides suite gains **evaluate all flags returns only the
override flags as a valid state** in its uninitialized client group,
where the store is empty and every SDK reports only the override flags.

## Results

Run with `-enable-persistence-tests` against Redis.

| Test service | flag without an override | empty override source |
overrides add |
|---|---|---|---|
| Go `v7` | fails: served from the store | fails: valid state of the
store's flags | fails: the store's flags join the overrides |
| Go launchdarkly/go-server-sdk#491 | passes | passes | passes |
| Node `main` | passes | fails: valid state of the store's flags |
passes |
| Node launchdarkly/js-core#2132 first commit (`57498a9e`) | passes |
passes | fails: drops a store flag the state holds without overrides |
| Node launchdarkly/js-core#2132 | passes | passes | passes |
| Python (top of the launchdarkly/python-server-sdk#530 stack, sync and
async) | passes | passes | passes |
| Ruby (top of the launchdarkly/ruby-server-sdk#454 stack) | passes |
passes | passes |

The Java (launchdarkly/java-core#225) and .NET (`dotnet-server-sdk-v9`)
test services do not support persistent stores, so the group skips
there; the new uninitialized client test passes on both. The full Redis
persistent data store suite and the flag overrides suite pass against Go
#491 at `30d4f216` (131 tests run). `golangci-lint` reports no issues.

Go and Node fail these tests until launchdarkly/go-server-sdk#491 and
launchdarkly/js-core#2132 are merged, for test services whose CI runs
the persistent data store suite with this harness version.

[SDK-3419](https://launchdarkly.atlassian.net/browse/SDK-3419)
<!-- TERMAG_PR_BODY_END -->

<!-- termag: box=i-0575d1c54853058d7 project=sdk-test-harness -->
<sub>Opened from termag</sub>


[SDK-3419]:
https://launchdarkly.atlassian.net/browse/SDK-3419?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Adds harness coverage for **file flag overrides** when the SDK never
initializes but a **persistent store already holds feature data without
the `$inited` key**—a case the existing in-memory override suite could
not distinguish from “read everything from the store.”
> 
> A new **`flag overrides`** group under the server-side persistent data
store suite (`server_side_persistence_overrides.go`, gated on
`flag-overrides`) seeds Redis/Consul/Dynamo with flags, uses a blocking
unavailable data source, and compares clients with and without override
files via `uninitializedClient`. Scenarios assert: store flags not in
the override file behave like no override source; an empty override file
leaves **evaluate all flags** unchanged; overrides merge into all-flags
state, mark it **valid**, and apply override values while retaining
other store flags.
> 
> The standalone flag-overrides **`uninitialized client`** group gains
**evaluate all flags returns only the override flags as a valid state**
(empty store, overrides-only valid state). The persistent suite
registers the new group from `server_side_persistence_base.go`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
09a8e02. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

2 participants