Repository navigation
feat: Add preconfigured flags and event settings to TestDataV2 - #486
Conversation
The FDv2 streaming and polling requests, and the FDv1 fallback polling request, no longer send the filter query parameter. FDv2 already logged that payload filtering was unsupported and that the payload_filter_key option had no effect, but the requests still sent it. The behavior now matches the warning. The FDv1 data system keeps its payload filter support until the FDv1 removal deletes it. The contract tests no longer map the payloadFilter data system setting. The filtering capabilities stay, because only harness v2 tests them, against FDv1.
TestDataV2 gains use_preconfigured_flag, and FlagBuilderV2 gains migration_settings, sampling_ratio, exclude_from_summaries and a nested FlagMigrationSettingsBuilder. The v1 TestData has all of these, and v9 removes v1 TestData with the FDv1 data system, so TestDataV2 must cover them first. A preconfigured flag keeps its own version, as in v1 TestData, because tests use specific versions to check the evaluation and event output. A flag that replaces an existing flag gets the next version. A preconfigured flag also discards any stored builder for its key, so a later flag call starts from a new builder.
The shared spec config now gives the client an FDv2 data system with no sources, in place of the FDv1 null data source. Specs that need data use TestDataV2 as a synchronizer, and specs that need a persistent store use a hash-backed MockFeatureStore through the data system config. The end-to-end and environment ID specs poll the FDv2 endpoint with an FDv2 payload. On a 401, FDv2 turns the data source off at once and does not retry, so those specs now expect OFF, a single request and no full start wait, in place of the FDv1 retry behavior. The tracker specs use the V2 flag builder. The FDv2 store specs gain the flag change cases that only the FDv1 data source specs covered: flags that a full data set adds or removes, a deleted prerequisite, and a removed dependency. The nil SDK key and use_ldd fork specs still use FDv1 options, because they test FDv1 logic in LDClient. They change with that logic when FDv1 is removed.
|
Note Agent-authored comment. This finding comes from an automated multi-agent review of this PR (general / security / adversarial / spec-conformance agents, graded against
|
|
Note Agent-authored comment. From the same automated multi-agent review as the boundary-validation comment above, posted with the account owner's review and approval. Verified by executing the PR head. Replacement version clamp discards a higher provided version (
|
|
Note Agent-authored comment. Same automated multi-agent review as the two comments above, posted with the account owner's review and approval. Both halves of this were proven deterministically by executing the PR head (a blocking fake instance for the delivery ordering, and a direct store-level test for the missing version check). Out-of-order upsert delivery + no stale-version rejection in the FDv2 store = clients can stick on a stale flagTwo cooperating layers: Layer 1 — delivery is not serialized with commit. Both Layer 2 — the store accepts the regression. Concrete trigger in a test suite: a "reset these flags" helper racing a test body that updates the same key — the test then reads back the wrong variation with no error anywhere. Labeling, for fairness: the compute-inside/deliver-outside pattern is pre-existing in Suggested direction: cheapest correct fix for this PR is propagating upserts while holding the lock (the critical section is short — an enqueue per instance). The more robust fix, at FDv2-infra scope rather than this PR, is adding stale-version rejection to Proof specs for both layers fail today ( |
…rder use_preconfigured_flag and use_preconfigured_segment now convert the input to a new hash with symbol keys at every level, check it, and raise ArgumentError for an input that is not a valid flag or segment: nil or another type, a missing or empty key, a version that is not an integer, malformed JSON, or a data model error. Before, a hash parsed from JSON kept string keys below the top level and evaluated wrongly, and a malformed item was accepted and later made the whole initial data set fail to load while the data source reported VALID. Both methods also accept a JSON string, as v1 TestData did, and store a copy, so later changes to the caller's object have no effect. A preconfigured flag now keeps a provided version that is greater than the version of the flag it replaces, and otherwise gets the next version. This matches Go's TestDataV2 and the test data source spec, which requires the increment only when the provided version is not greater. Version 0 on a new flag becomes 1. update, use_preconfigured_flag and use_preconfigured_segment now hold a mutex across the change and its delivery to the data source instances. Before, two concurrent changes could reach an instance in the opposite order to the one in which they were made.
The previous commit normalized a preconfigured flag or segment with a JSON round trip. JSON.generate calls to_s on a value it cannot encode, so a Set, a Time or another object became a string with no error. A segment whose included keys were a Set then failed to match. NaN, Infinity, and a hash with the same key as a string and a symbol raised JSON::GeneratorError in place of ArgumentError. The item is now copied value by value. A Set becomes an array and a symbol value becomes a string, as in JSON. Any other value that JSON cannot hold, a value that is not finite, a hash key that is not a string or symbol, a repeated key, and nesting deeper than 100 levels raise ArgumentError. The delivery order spec now has a deadline for the first thread and always releases it, so a failure cannot hang the suite.
…r update FlagRuleBuilderV2#build put the builder's own clause array into the built flag. A rule builder that the test kept after update could then add clauses to the stored flag. This changed what later clients received without a version change or a delivered update. The build now copies each clause and its values.
|
Fixed in f4887c4 and aadae60. Agreed that |
|
Fixed in f4887c4, with Go's semantics: the stored version is |
|
Layer 1 is fixed in f4887c4. Layer 2, the missing stale-version rejection in |
|
Correction on layer 2: there won't be a separate PR. FDv2 guarantees upstream that payloads arrive in order, so TestDataV2 is a local source, so it has to keep that order itself. This PR fixes the order of changes between two updates. #491 fixes the other case, where a change made before a synchronizer starts was sent again after the initial data. |
fix: Enforce the 30 second minimum poll interval in the polling builders
…-on-fdv2 # Conflicts: # spec/integrations/test_data_v2_spec.rb
…ng spec The invalid stream event fix moved the INVALID_DATA status from on_event to on_error. The FDv1 removal added a spec that expected on_event to report it. The spec now passes the raised error to on_error, as the SSE client does.
The FDv2 file source yields its initial data before it starts the poller, and the poller reads the first modification times when it starts. On a slow runner the spec changed the file before the poller started, so the poller saw no change. The spec now waits until the poller exists.
feat!: Remove the FDv1 data system
fix: Enforce the 30 second minimum poll interval in the polling builders
fix: Prevent TestDataV2 from briefly restoring an older flag when a client starts
Summary
This PR merges two stacked PRs: #487 removes the FDv1 data system, and
#491 fixes a TestDataV2 startup race. The lines above keep their
changelog entries in the squash commit.
TestDataV2 replaces v1 TestData
TestDataV2 gains use_preconfigured_flag and use_preconfigured_segment,
and FlagBuilderV2 gains migration_settings, sampling_ratio,
exclude_from_summaries and a nested FlagMigrationSettingsBuilder. The v1
TestData has all of these, so TestDataV2 must cover them before v1
TestData goes.
A preconfigured flag keeps its own version, as in v1 TestData, because
tests use specific versions to check the evaluation and event output. A
flag that replaces an existing flag gets the higher of its own version
and the next version. A preconfigured flag also discards any stored
builder for its key, so a later flag call starts from a new builder. The
preconfigured methods accept a hash or a JSON string, and raise
ArgumentError for input that is not a valid flag or segment.
TestDataV2 now delivers changes to its data sources in the order it
commits them. A synchronizer gets its initial data and its registration
for later changes in one step, so a change made before it starts does
not arrive again after the initial data. An initializer no longer
registers for changes. A flag built from a FlagBuilderV2 no longer
changes when its rule builder changes later.
The FDv1 data system is removed
The client now always uses the FDv2 data system. When data_system_config
is nil, it uses DataSystem.default.
Removed config options: stream, use_ldd, poll_interval,
initial_reconnect_delay, base_uri, stream_uri, feature_store,
data_source, cache_store, payload_filter_key and data_source_update_sink.
Use the data system builders in their place: DataSystem.persistent_store
or DataSystem.daemon for a persistent store, and the polling and
streaming builders for URIs and intervals.
Removed classes and methods: the v1 TestData and its FlagBuilder,
FileData.data_source, InMemoryFeatureStore, the FDv1 data source mixin
in Interfaces::DataSource, and the Interfaces::DataSource::UpdateSink
and Interfaces::DataStore::UpdateSink interfaces. Use TestDataV2 and
FileData.data_source_v2. LaunchDarkly::Integrations now loads
TestDataV2.
The SDK key is required unless the client is offline, or events are
disabled and no data source connects to LaunchDarkly. Daemon mode is a
data system with no initializers or synchronizers. With events disabled
it reports initialized and a fork does not break it, as LDD mode did.
The diagnostic init event no longer reports customBaseURI,
customStreamURI, pollingIntervalMillis, streamingDisabled or
usingRelayDaemon. The FDv2 fields are still to be decided before 9.0.
The FDv1 fallback that FDv2 uses stays. UnexpectedResponseError moves to
Impl::DataSystem with the FDv2 polling code.
The FDv2 polling builder and the FDv1 fallback polling builder replace
a poll interval below 30 seconds with 30 seconds and log a warning. The
removed poll_interval config option had the same minimum. Without it, a
small or zero interval polls LaunchDarkly in a tight loop.
Specs, contract tests and CI
The shared spec config gives the client an FDv2 data system with no
sources. Specs that need data use TestDataV2 as a synchronizer, and
specs that need a persistent store use a hash-backed MockFeatureStore
through the data system config.
The end-to-end and environment ID specs poll the FDv2 endpoint with an
FDv2 payload. On a 401, FDv2 turns the data source off at once and does
not retry, so those specs now expect OFF, a single request and no full
start wait, in place of the FDv1 retry behavior.
The test service no longer maps the top-level streaming, polling and
persistentDataStore settings that harness v2 sends, and no longer reports
the filtering, filtering-strict and retry-conformance-fdv1 capabilities.
Harness v3 sends a dataSystem block for every test. A client with no
dataSystem block gets a data system with no sources, as LDD mode did.
CI and the Makefile run harness v3 only.
New specs cover the FDv1 specs' cases on the FDv2 code:
ETag and 304, gzip, the basis parameter, error status, response
headers and proxy.
metacharacters do not run code, reload without the listen gem, an
initial sync failure, full flags from flagValues, and evaluation
through LDClient.
on_connect handler in place of a copy of its logic.
removes, a deleted prerequisite, and a removed dependency.
Note
Overview
Breaking: The Ruby SDK drops the legacy FDv1 data path and always runs the FDv2 data system.
LDClientnow usesdata_system_configwhen set, otherwiseLaunchDarkly::DataSystem.default. LegacyConfigknobs (stream,use_ldd,poll_interval,base_uri,stream_uri,feature_store,data_source,cache_store,payload_filter_key, and related helpers) are removed—streaming, polling, persistent stores, daemon/LDD-style setups, and file/test sources go through DataSystem builders (DataSystem.persistent_store,DataSystem.daemon, polling/streaming builders,FileData.data_source_v2,TestDataV2).v1 TestData, FlagBuilder, and FileData.data_source are deleted; TestDataV2 picks up preconfigured flags/segments, migration/sampling/summary builder options, ordered delivery via
subscribe, and stricter validation. Integrations docs now point persistent stores at the data system instead offeature_store.Polling builders clamp intervals below 30s to 30s with a warning (replacing the old
Config#poll_intervalminimum). Diagnostic init events no longer report FDv1-era fields. Large chunks of FDv1 impl (stream/poll processors,InMemoryFeatureStore,RetryState, etc.) are removed;UnexpectedResponseErrorlives under FDv2 polling.CI/contract tests run harness v3 only; the test service drops legacy streaming/polling/persistence mapping and several v2-only capabilities; clients without a data system get an empty custom data system (LDD-like).
Reviewed by Cursor Bugbot for commit c148842. Bugbot is set up for automated code reviews on this repo. Configure here.