Skip to content

feat: Add flag overrides to the contract test service - #454

Draft
kinyoklion wants to merge 1 commit into
rlamb/overrides-ruby-file-sourcefrom
rlamb/overrides-ruby-contract-tests
Draft

kinyoklion wants to merge 1 commit into
rlamb/overrides-ruby-file-sourcefrom
rlamb/overrides-ruby-contract-tests

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR is stacked on #453 because the test service configures the file-based override source that PR adds.

The contract test service now advertises the flag-overrides capability and accepts the overrides block of the SDK configuration described in the test harness service specification. filePaths, duplicateKeysHandling, changeDetection, and pollIntervalMs map to the options of LaunchDarkly::Integrations::FileData.override_source, which is set on the FDv2 data system builder. An overrides block without a dataSystem block is an error, as in the Go reference service.

The listen gem joins the contract test bundle so that the harness's watching change detection tests can run. The service port can be set with the PORT environment variable and still defaults to 9000, so the existing Makefile and CI steps are unchanged.

Verification against the prebuilt harness (sdk-test-harness branch rlamb/sdk-2657/flag-overrides-tests at 9ad082c, flag-overrides capability): -run 'flag overrides' ran 38 tests with 0 failures, and the full suite with no filter ran 4733 tests with 0 failures (13 skipped by pre-existing capability and long-running gates). The first run of the watching mode tests failed because the watcher relied on listen, which could not scan the system temporary directory where the harness writes its files; the watcher change in #449 corrects that, and both runs above were made with it in place.

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
The Ruby contract test service now supports the harness flag-overrides capability so file-based flag override scenarios can run against this SDK.

When client config includes an overrides block alongside dataSystem, the service maps filePaths, duplicateKeysHandling, changeDetection, and pollIntervalMs onto LaunchDarkly::Integrations::FileData.override_source and attaches it via data_system.overrides. Supplying overrides without dataSystem raises ArgumentError, matching the Go reference service.

The bundle adds the listen gem so watching change-detection tests can run, and the Sinatra app reads PORT (default 9000) for the bind port without changing existing Makefile/CI defaults.

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

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 605d0ab to 5bda436 Compare September 28, 2026 20:36
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 63dd6c6 to 24a8d56 Compare September 28, 2026 20:36
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 5bda436 to 4287523 Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 24a8d56 to d04deb5 Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 4287523 to 703e368 Compare October 3, 2026 00:46
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from d04deb5 to b98adec Compare October 3, 2026 00:46
@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.

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 703e368 to 18d4335 Compare October 3, 2026 01:54
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from b98adec to b249b70 Compare October 3, 2026 01:54
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 18d4335 to d4f7481 Compare October 8, 2026 16:55
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from b249b70 to f4aea05 Compare October 8, 2026 16:55
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from d4f7481 to ddada85 Compare October 13, 2026 22:12
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch 2 times, most recently from 3758acf to ca1e973 Compare October 15, 2026 22:25
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from d0a8633 to e0adc16 Compare October 16, 2026 21:40
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from ca1e973 to 108dd59 Compare October 16, 2026 21:40
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from e0adc16 to 15125ab Compare October 28, 2026 22:18
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 108dd59 to ad1e465 Compare October 28, 2026 22:18
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 15125ab to 647d385 Compare October 28, 2026 22:41
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from ad1e465 to 704e170 Compare October 28, 2026 22:41
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 647d385 to 357c6ae Compare October 28, 2026 22:53
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 704e170 to 6330747 Compare October 28, 2026 22:53
@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.

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 6330747 to 1d45fe4 Compare October 28, 2026 22:58
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from 68137e0 to d5a0b9c Compare October 28, 2026 23:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 1d45fe4 to 34cc17c Compare October 28, 2026 23:28
@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 34cc17c. Configure here.

The test service advertises the flag-overrides capability and accepts the
overrides block of the SDK configuration: filePaths, duplicateKeysHandling,
changeDetection, and pollIntervalMs map to the options of
LaunchDarkly::Integrations::FileData.override_source on the FDv2 data
system builder. Overrides without a data system configuration are an
error. The listen gem joins the contract test bundle so that the watching
change detection mode can run. The service port can be set with the PORT
environment variable and still defaults to 9000.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-file-source branch from d5a0b9c to 79e08c0 Compare October 29, 2026 16:49
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-contract-tests branch from 34cc17c to 8b8a337 Compare October 29, 2026 16:49
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 -->

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