Repository navigation
feat: Add the override marker to the flag and segment models and mark evaluations - #363
Merged
kinyoklion merged 2 commits intoOct 28, 2026
Conversation
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
September 28, 2026 20:39
e4dfcf4 to
74be7f9
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
September 28, 2026 20:39
aff3a67 to
ae86267
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 1, 2026 23:43
74be7f9 to
93c9160
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 1, 2026 23:43
ae86267 to
533647f
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 3, 2026 01:34
93c9160 to
3996906
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 3, 2026 01:34
533647f to
511de7f
Compare
Member
Author
|
bugbot review |
There was a problem hiding this comment.
✅ 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 511de7f. Configure here.
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 3, 2026 01:52
3996906 to
285612d
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 3, 2026 01:52
511de7f to
33e2973
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 5, 2026 21:55
285612d to
5200c06
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 5, 2026 21:55
33e2973 to
22c3c01
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 5, 2026 22:12
5200c06 to
4038dce
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 5, 2026 22:12
22c3c01 to
426dbe4
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 5, 2026 22:28
4038dce to
dca375c
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 5, 2026 22:28
426dbe4 to
167a54a
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 5, 2026 23:56
dca375c to
6b919c2
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 5, 2026 23:56
167a54a to
6894b3d
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 13, 2026 21:03
6b919c2 to
8ef07c2
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 13, 2026 21:03
6894b3d to
f0fca10
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 13, 2026 22:13
8ef07c2 to
e8bc083
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 13, 2026 22:13
f0fca10 to
f083d90
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 23, 2026 23:49
e8bc083 to
b35db17
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 23, 2026 23:50
f083d90 to
c3adb5b
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 26, 2026 22:46
b35db17 to
d7b86e2
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 26, 2026 22:46
c3adb5b to
ff43cdf
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 26, 2026 23:13
d7b86e2 to
bae5720
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 26, 2026 23:13
ff43cdf to
bcbfead
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 27, 2026 20:23
bae5720 to
5bafb8e
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 27, 2026 20:23
bcbfead to
3c03873
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-reason-marker
branch
from
October 27, 2026 20:46
5bafb8e to
f1da988
Compare
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 27, 2026 20:46
3c03873 to
4456477
Compare
kinyoklion
marked this pull request as ready for review
October 27, 2026 20:57
keelerm84
approved these changes
Oct 28, 2026
Base automatically changed from
rlamb/overrides-dotnet-reason-marker
to
dotnet-server-sdk-v9
October 28, 2026 20:35
… evaluations FeatureFlag and Segment gain an internal IsOverride marker and an AsOverride method that returns a marked copy sharing the immutable parts of the original. The marker lives on the model only and is never serialized. The evaluator marks an evaluation as override-affected when any definition it read carried the marker: the evaluated flag, a prerequisite at any depth, or a segment consulted during matching, whether or not the segment matched. The marking propagates upward only: a prerequisite record reflects only the definitions its own subtree read. Error results are marked too. The result is reported through the evaluation reason's OverrideAffected indicator, as defined by the OVERRIDE specification.
kinyoklion
force-pushed
the
rlamb/overrides-dotnet-model-evaluator
branch
from
October 28, 2026 20:38
4456477 to
7e64644
Compare
The override marker on the evaluation reason is released in CommonSdk 7.3.0-alpha from this branch. The server's Debug and Release builds use the package, so they reference that version.
kinyoklion
added a commit
that referenced
this pull request
Oct 29, 2026
…stem wiring (#364) <!-- TERMAG_PR_BODY_BEGIN --> ## Summary Adds the override store, the overlay at the store read boundary, the override source interfaces, and the data system wiring for flag overrides, as defined by the OVERRIDE specification. With this change an application can configure an override source on the FDv2 data system and have its flags and segments take precedence over LaunchDarkly data at evaluation time. Configuration surface: `DataSystemBuilder.Overrides(IComponentConfigurer<IOverrideSource>)` and `DataSystemConfiguration.OverrideSource`. At most one source is accepted. `IOverrideSource` (`Start(IOverrideSink)`, `IDisposable`) and `IOverrideSink.SetOverrides(FullDataSet<ItemDescriptor>)` follow the shape of the data source interfaces, so an application can supply a custom source the same way it supplies a custom data source. The SDK builds the source when the client is created, starts it before the data sources so that its initial load completes inside the client constructor, and disposes it with the client. An exception from the source's `Dispose` is logged and the rest of the client still shuts down, and a snapshot that the source supplies during shutdown is ignored. A configuration error in the source propagates from the constructor like any other invalid component configuration. An exception from the source's `Start` does not: the SDK logs it, disposes the source, removes any snapshot the source supplied before failing, and the client runs without overrides, because a client in an incident is better off without its overrides than not starting. The SDK's own file-based source never throws from `Start`; it logs a failed load and retries. The source is not built when the client is offline, so no overrides are served and a configuration error in it is not reported; the SDK logs a warning saying so. Without a configured source the SDK is unchanged. The override layer (`Internal/Overrides`): `OverrideLayer` holds the entries as an immutable map swapped on every snapshot, marking copies of the supplied definitions so the source's own objects are never modified. `OverrideOverlay` implements the data system's read-only store: a read returns the override entry when one exists and the LaunchDarkly entry otherwise, an enumeration returns the union with override entries winning (a failure of the base store propagates, as it does without the overlay), and initialization status still comes from the base store. `OverrideSink` applies snapshots one at a time and computes the flags an update may have changed, including dependency fan-out through prerequisites and segments over both the old and the new merged views, then sends the same flag change notifications a LaunchDarkly change would. Client behavior: the not-initialized short-circuit consults the override layer first, so an overridden flag is served before the client has LaunchDarkly data while a flag the layer does not hold takes exactly the path it took before this feature. An evaluation that fails with an unexpected exception keeps the marking when the flag came from the layer. A malformed override definition is logged and skipped during change computation so the other notifications still fire, a source whose Build throws leaves nothing started, and a source whose Start throws is disposed while the client runs without overrides. `AllFlagsState` reads through the overlay, so it includes override-only flags. When neither the client nor the store is initialized and the layer holds flags, it returns a state of those flags alone (with a warning logged once), read from the layer rather than the merged store so that LaunchDarkly data in a store without an init marker stays out; with no override flags the state is invalid, as it is without a source. Without a source this state is empty, because the SDK reports no flags before the store is initialized, so the override flags only add to what it reports; overrides never remove a flag from the state. When the store fails to list its flags, the failure is logged and the state holds the override layer's flags and is invalid: without a source that state is invalid and empty, and a client-side SDK bootstrapped from the state uses its flags whatever its validity says, so the overridden values still reach it. A flag whose evaluation is override-affected keeps its value, version, and marked reason in the state but has `trackEvents` and `trackReason` off and no `debugEventsUntilDate`, because a consumer of the state must not send individual events for it. A wrong-type result keeps the marking on its reason, including the reason `MigrationVariation` reports for an unrecognized stage. The specification's test vectors run as a unit test against the full client stack (value, variation index, and reason; the summary-counter contribution is asserted once the events change lands). Unit tests cover the layer, overlay, sink, the builder, and the client behaviors above. It depends on the model and evaluator marking change, #363, which is merged into `dotnet-server-sdk-v9`. [SDK-3248](https://launchdarkly.atlassian.net/browse/SDK-3248) [SDK-3248]: https://launchdarkly.atlassian.net/browse/SDK-3248?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ ## Follow-up from review A LaunchDarkly change now notifies the flags whose overrides depend on it. The override layer keeps the dependency edges of its own entries, rebuilt on every replacement, and `DataSourceUpdatesImpl` expands each changed LaunchDarkly item through both its own tracker and the layer's until nothing new is reached, so an override that matches a LaunchDarkly segment or has a LaunchDarkly prerequisite is notified like the LaunchDarkly flags that depend on it. An entry whose references cannot be read contributes no edges, the same as the change computation skips it. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Adds **experimental flag overrides** for the FDv2 data system: `DataSystemBuilder.Overrides()` configures an optional `IOverrideSource` that pushes full snapshots through `IOverrideSink`, without affecting initialization or data-source status. > > When a source is configured, the SDK builds an **override layer** and wraps the read-only store with **`OverrideOverlay`** so evaluations, prerequisites, segments, and `AllFlagsState` see override entries before LaunchDarkly data. **`OverrideSink`** replaces the layer atomically and notifies flag listeners using the same path as LaunchDarkly updates, including dependency fan-out via **`OverrideChanges`** and **`IExternalDependencies`** on **`DataSourceUpdatesImpl`** (LaunchDarkly changes can now reach overridden flags that reference them). > > **`LdClient`** is updated so overridden flags can be served before the client initializes; **`AllFlagsState`** can return override-only flags (with one-time warnings), fall back to overrides when `GetAll` fails, and disable per-flag event tracking when **`overrideAffected`** is set. Start/dispose failures on the source are logged and the client continues without overrides; offline mode skips starting the source. **`FDv1DataSystem`** exposes empty override APIs for parity. Extensive unit tests and **`override-vectors.json`** cover the stack. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 58ef4ac. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- TERMAG_PR_BODY_END --> <!-- termag: box=i-0575d1c54853058d7 project=projects --> <sub>Opened from termag</sub>
kinyoklion
pushed a commit
that referenced
this pull request
Oct 29, 2026
…ha.1 (#382) 🤖 I have created a release *beep* *boop* --- ## [9.0.0-alpha.1](LaunchDarkly.ServerSdk-v9.0.0-alpha.0...LaunchDarkly.ServerSdk-v9.0.0-alpha.1) (2026-10-29) ### Features * Add a shared file data reloader, poller, and watcher ([#361](#361)) ([4eed241](4eed241)) * Add the file-based override source ([#367](#367)) ([b834131](b834131)) * Add the override marker to the flag and segment models and mark evaluations ([#363](#363)) ([21f666a](21f666a)) * Add the override store, overlay, source interfaces, and data system wiring ([#364](#364)) ([e1748a1](e1748a1)) * Mark evaluation events as override-affected ([#366](#366)) ([7c873a0](7c873a0)) * Split summary counters by overrideAffected and suppress individual events for marked evaluations ([#365](#365)) ([c9b327b](c9b327b)) * Support the flag-overrides capability in the contract test service ([#368](#368)) ([b80d6aa](b80d6aa)) --- 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 publishes **LaunchDarkly.ServerSdk `9.0.0-alpha.1`**. The diff only updates release metadata: `.release-please-manifest.json`, `LaunchDarkly.ServerSdk.csproj` `<Version>`, `PROVENANCE.md` verification example, and a new **`9.0.0-alpha.1`** section in `CHANGELOG.md`. > > The changelog entry documents what ships in this alpha (already merged elsewhere): **local flag overrides** via override store/overlay/source wiring and a **file-based override source** with shared file reloader/poller/watcher; **override markers** on flag/segment models and evaluations; **analytics** changes that mark override-affected evaluations, split summary counters by `overrideAffected`, and suppress individual events when appropriate; plus **contract-test** support for the flag-overrides capability. > > Reviewers can treat this as a **versioning and release-notes sanity check**—no application logic changes appear in the diff itself. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 81f0588. 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>
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
Adds the override marker to the flag and segment models and makes the evaluator mark evaluations as override-affected, as defined by the OVERRIDE specification.
FeatureFlagandSegmentgain an internalIsOverrideproperty and anAsOverride()method that returns a marked copy sharing the immutable parts of the original. The marker lives on the model only: the JSON converters do not write it, so a marked definition serializes exactly like an unmarked one, and the original object handed toAsOverride()is never modified.The evaluator marks an evaluation when any definition it read carried the marker: the evaluated flag, a prerequisite flag at any depth, or a segment consulted during clause matching, whether or not the segment matched. The marking propagates upward only. Each prerequisite evaluation is a scope of its own that starts from the prerequisite's marker, so the record for a prerequisite reflects only its own subtree, while the parent scope absorbs the nested marking when the nested evaluation returns or throws. Error results are marked too: a malformed override flag, a prerequisite cycle through an override flag, and an invalid context all produce an error reason that carries the indicator. A definition that cannot be resolved contributes nothing. The marking is reported through the reason's
OverrideAffectedindicator from the previous change in this series.Nothing in this change produces marked definitions yet; the override store that does so is the next change. Every existing evaluation therefore behaves as before, which the existing evaluator tests confirm. The new tests cover each marking case, including the prerequisite tree in the specification where only one leaf is overridden.
This PR is based on the branch of the EvaluationReason indicator change, which it depends on.
SDK-3248
Note
Overview
Adds an internal override marker on
FeatureFlagandSegment(IsOverride/AsOverride()). The marker is model-only and does not change serialized flag/segment JSON.The evaluator tracks whether the current evaluation read any marked definition and sets
EvaluationReason.OverrideAffectedon the final result (and on prerequisite sub-records for their own scope). Marking applies when the evaluated flag is overridden, when a prerequisite subtree reads an override (nested scopes merge upward), or when a segment is loaded for a segment-match clause—even if the segment does not match or the clause is negated. Error paths (invalid context, malformed flag, exceptions) are marked when the evaluated flag is an override. Missing flags/segments that are never read do not mark.Extensive new tests cover marking rules; existing evaluations stay unmarked until something supplies
AsOverride()(override store is follow-up work).Reviewed by Cursor Bugbot for commit 7e64644. Bugbot is set up for automated code reviews on this repo. Configure here.