fix: honor FDv1 fallback directive during initializer phase#158
Open
fix: honor FDv1 fallback directive during initializer phase#158
Conversation
When an FDv2 initializer returns a result with the FDv1 fallback signal, apply any accompanying payload first, then halt the FDv2 chain and switch terminally to the FDv1 fallback synchronizer. If no FDv1 fallback is configured, transition the data source status to OFF with the underlying error preserved instead of staying stuck at INITIALIZING. The synchronizer-phase handling already honored the directive; this brings the initializer phase in line with the spec so the directive is honored throughout the data system lifecycle.
Declare the fdv1-fallback capability and accept the new top-level dataSystem.fdv1Fallback config object (baseUri, pollIntervalMs) sent by sdk-test-harness. Wire it directly to the SDK's FDv1 fallback synchronizer instead of inferring it from the last entry of the FDv2 synchronizer list, which misrepresented the SDK's architecture: the FDv1 Fallback Synchronizer is distinct from the FDv2 Primary/Fallback chain. Bumps the test harness pin to v3.0.0-alpha.6 to pull in the new directive test suite that exercises this configuration.
… one configured The synchronizer-phase fallback handler previously required a configured FDv1 fallback synchronizer to honor the X-LD-FD-Fallback directive: when no FDv1 fallback was configured, the directive was silently ignored and the SDK kept reconnecting to the FDv2 synchronizer. Per Data System spec 1.6.3(4), the directive must terminally halt the data system in this case. Now, when a synchronizer surfaces a result with FallbackToFDv1=true and no FDv1 fallback is configured, the current synchronizer is blocked, the status transitions to OFF with the underlying error info preserved, and runSynchronizers() returns terminally so no further FDv2 synchronizers are attempted. The caller in run() observes the deliberate halt and skips the "unexpected exhaustion" log so the OFF status is not clobbered.
Previously, Java marked the data source VALID and completed startFuture on `anyDataReceived` -- any CHANGE_SET applied during the initializer phase, regardless of whether the basis carried a defined selector. The other FDv2-supporting SDKs (Go, Python, Ruby) only consider initialization complete when a selectorful basis is applied; the synchronizer phase (or FDv1 fallback) is responsible for the eventual VALID transition when the initializer phase only produced selectorless data. Drop the "treat data without a selector as enough" path in runInitializers so Java matches the cross-SDK contract. Selectorless bases are still applied to the store so evaluations can serve them during the gap between init and the first selectorful basis from a synchronizer. Update two tests that asserted the old behavior: - statusTransitionsToValidAfterInitialization now uses a selectorful basis (matching its name). - initializerChangeSetWithoutSelectorCompletesIfLastInitializer is renamed and inverted to assert the new contract: a selectorless initializer with no synchronizer transitions to OFF, not VALID.
tanderson-ld
reviewed
May 6, 2026
| "Synchronizer '{}' requested FDv1 fallback, but no FDv1 fallback synchronizer is configured; halting the data system.", | ||
| synchronizer.name() | ||
| ); | ||
| sourceManager.blockCurrentSynchronizer(); |
Contributor
There was a problem hiding this comment.
Why does this only block the current synchronizer and not all non-fallback synchronizers? Singling out a specific synchronizer seems unexpected since fallback has us blocking all fdv2 synchronizers via sourceManager.fdv1Fallback();
Contributor
There was a problem hiding this comment.
I think you can call sourceManager.fdv1Fallback(); unconditionally and that will block all non-fallback synchronizers. Then the if (sourceManager.hasFDv1Fallback()) check is just about logging and deciding to keep running or terminate with return.
| // consider ourselves initialized. | ||
| if (anyDataReceived) { | ||
| dataSourceUpdates.updateStatus(DataSourceStatusProvider.State.VALID, null); | ||
| startFuture.complete(true); |
Contributor
There was a problem hiding this comment.
I think this results in a change in behavior if there are no synchronizers to follow the initializers.
…applied data Restore the previous behavior where a selectorless basis applied during the initializer phase marks the data source VALID once the entire initializer chain is exhausted. Without this, an SDK configured with only selectorless initializers (and no synchronizer, or a synchronizer that hasn't yet produced a selectorful payload) would never transition out of INITIALIZING. The selectorful early-return path is unchanged: a basis with a defined selector continues to mark VALID immediately, before any further initializers run. The directive-on-selectorless-basis path is also unchanged: the FDv1 fallback continues to be triggered without a premature VALID transition there. Drops a unit test that asserted the cross-SDK "no VALID without selector" gate that this commit reverses.
tanderson-ld
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FDv2DataSourcenow honorsX-LD-FD-Fallback: trueduring the initializer phase: any accompanying payload is applied first, then the loop bails out and either swaps to the configured FDv1 fallback synchronizer or transitions the data source toOFFwhen no fallback is configured. Synchronizer-phase behavior is unchanged.fdv1-fallbackcapability and accepts a top-leveldataSystem.fdv1Fallbackconfig object, wiring it directly todataSystemBuilder.fDv1FallbackSynchronizer(...)instead of inferring the fallback from the last synchronizer entry.v3.0.0-alpha.3tov3.0.0-alpha.6to pick up the new directive test suite.Mirrors the Go reference change in go-server-sdk#365 and contract-test wiring in go-server-sdk#368. Spec rationale lives in sdk-specs#155; the new harness suite is in sdk-test-harness#336.
Test plan
:lib:sdk:server:testsuite green.v3.0.0-alpha.6runs the new "FDv1 Fallback Directive" suite cleanly in CI.Note
Medium Risk
Changes FDv2 data source orchestration to potentially short-circuit initialization/synchronization and transition to
OFFbased on a server directive, which can affect startup/availability behavior. Contract-test harness wiring and version bump reduce risk but the control-flow change is in core data acquisition logic.Overview
FDv2DataSourcenow honors the server-directed FDv1 fallback directive during the initializer phase: it applies any accompanying payload, then immediately switches to the configured FDv1 fallback synchronizer; if none is configured it transitions the data source toOFF(preserving error info) and halts further FDv2 processing.Synchronizer-phase fallback handling is tightened to halt the data system when a fallback directive is received without an FDv1 fallback synchronizer (rather than continuing through other synchronizers), and tests are expanded to cover initializer-phase fallback scenarios and the new halt behavior.
Contract test infrastructure is updated to advertise an
fdv1-fallbackcapability, accept a dedicateddataSystem.fdv1Fallbackconfig (instead of inferring from the FDv2 synchronizer list), and bump the v3 harness pin tov3.0.0-alpha.6.Reviewed by Cursor Bugbot for commit 1b5bb14. Bugbot is set up for automated code reviews on this repo. Configure here.