Skip to content

feat: Add the file-based override source - #367

Merged
kinyoklion merged 8 commits into
dotnet-server-sdk-v9from
rlamb/overrides-dotnet-file-source
Oct 29, 2026
Merged

kinyoklion merged 8 commits into
dotnet-server-sdk-v9from
rlamb/overrides-dotnet-file-source

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds the file-based override source, as defined by the OVERRIDE specification, on top of the shared file loading code and the override layer from the earlier changes in this series. The series targets dotnet-server-sdk-v9, the branch that prepares the next major version. The existing file data source keeps its current behavior: it does not use the shared code, and nothing in this change touches it.

Configuration: FileOverrides.Source() returns a FileOverrideSourceBuilder that is passed to DataSystemBuilder.Overrides(...). Its options are FilePaths (one or more files, in precedence order, required), DuplicateKeysHandling (Fail, the default, or Ignore, which keeps the first configured file's entry), ChangeDetection (Polling, the default, or Watching), PollInterval (one second by default and at minimum; a lower value is raised to the minimum with a warning), and Parser, which registers a parsing function for a file extension, for formats other than JSON such as YAML. Building a source with no file paths, with an option outside its enumeration, or with a YAML file for which no parser is registered, throws, so the client constructor fails the same way it does for other invalid component configuration. A path listed more than once is logged at Warn and the repeats are ignored; the first occurrence keeps its place in the order. Every public member carries the note that flag overrides are currently experimental and subject to change.

Behavior: the files use the file data document format (flags, flagValues, segments); a flagValues entry expands to a flag that is on and serves its single value by fallthrough, so it evaluates with the FALLTHROUGH reason. The parser for a file is chosen by its extension, and a file whose extension has no registered parser is parsed as JSON. A flag or segment is listed under its key; a key property in the definition is optional and must be equal to it, and a version property is optional. The initial load runs inside the client constructor. Change detection is in place before the initial load, so a change made between the two is not missed. In watching mode a configured path that is a link to a file in another directory is detected only through the periodic directory check; polling, the default, follows the link directly. A configured file that does not exist contributes no overrides and produces no error log; creating it later adds its overrides and deleting it removes them. A file that exists but cannot be read or parsed, or a duplicate key under Fail, fails that whole reload: the previously loaded overrides stay in effect, the failure is logged, and the source retries after a bounded delay and on the next change. Every applied change is logged at Info level with the overrides in effect and what each configured file supplied (for example Flag overrides in effect: 2 flags, 1 segment (/etc/ld/a.json: 2 flags, 1 segment; /etc/ld/b.json: absent)). Polling examines the files' modification time, size, and existence once per interval; watching uses file system notifications on each file's directory.

YAML: the SDK does not bundle a YAML parser, following the existing file data source, so YAML files require the application to register a parser for the .yaml or .yml extension through the Parser option. A YAML file configured without one is reported when the client is created, not on every reload. The specification allows the application to supply the parser.

Tests cover the builder validation and defaults, including the YAML check, every file behavior above in both change detection modes, the Info log lines, and client end-to-end scenarios in which an operator adds, changes, and removes an override on a running client that never obtains LaunchDarkly data, and in which a YAML override file is read with the registered parser.

It depends on the server events change, #366, which is merged into dotnet-server-sdk-v9.

SDK-3248

Release Please could not parse the squash commit for this PR, so this block supplies its changelog entry:

BEGIN_COMMIT_OVERRIDE
feat: Add the file-based override source (#367)
END_COMMIT_OVERRIDE


Note

Overview
Adds the built-in file override integration so operators can force flag/segment values from local files via DataSystemBuilder.Overrides(FileOverrides.Source()...), without restarting the client.

The public surface is FileOverrides, FileOverrideSourceBuilder, and FileOverrideTypes (duplicate-key policy, polling vs watching, poll interval, optional per-extension parsers for YAML). The internal FileOverrideSource merges multiple paths in order, loads synchronously on startup, hot-reloads through shared file-loading helpers, pushes full snapshots to the override layer, and logs what is in effect per file. Failed reloads keep the last good snapshot; missing files are ignored until they appear.

Docs on DataSystemBuilder.Overrides and IOverrideSource now point at FileOverrides. Tests cover builder validation, reload/merge/error behavior, and end-to-end LdClient evaluation (including OverrideAffected and client-side AllFlagsState rules).

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

Opened from termag

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 86caaa5 to 9ddeb28 Compare September 28, 2026 20:39
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 4011fef to bf327fc Compare September 28, 2026 20:39
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 9ddeb28 to 75135db Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from bf327fc to e30694a Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 75135db to 89329c5 Compare October 3, 2026 01:34
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from e30694a to 4de98c7 Compare October 3, 2026 01:34
@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-dotnet-events branch from 89329c5 to 403d56e Compare October 3, 2026 01:52
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 4de98c7 to 57ffcf0 Compare October 3, 2026 01:52
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 403d56e to b085719 Compare October 5, 2026 21:55
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 57ffcf0 to c3dbd2d Compare October 5, 2026 21:55
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from b085719 to 2b54c3e Compare October 5, 2026 22:12
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from c3dbd2d to 6973a4f Compare October 5, 2026 22:12
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 2b54c3e to 4ec85dc Compare October 5, 2026 22:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 6973a4f to 78d3e87 Compare October 5, 2026 22:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 4ec85dc to 368f8fa Compare October 5, 2026 23:56
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 78d3e87 to 08c4cf0 Compare October 5, 2026 23:56
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 368f8fa to f12a5f6 Compare October 13, 2026 21:03
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 08c4cf0 to c9c756b Compare October 13, 2026 21:03
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from f12a5f6 to 6d2b453 Compare October 13, 2026 22:13
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from c9c756b to e0d0597 Compare October 13, 2026 22:13
@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.

Comment thread pkgs/sdk/server/src/Integrations/FileOverrideSourceBuilder.cs Outdated
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 6d2b453 to 4e21dce Compare October 16, 2026 21:21
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 6bdec5e to 40f4cd0 Compare October 16, 2026 21:21
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 4e21dce to 28e15b9 Compare October 23, 2026 23:50
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 40f4cd0 to adf57b3 Compare October 23, 2026 23:50
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 28e15b9 to 8bd83fe Compare October 26, 2026 22:46
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from adf57b3 to 6ea364e Compare October 26, 2026 22: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.

✅ 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 f18639e. Configure here.

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 9d0b734 to 9259cff Compare October 29, 2026 16:45
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from f18639e to f755f8c Compare October 29, 2026 16:45
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 9259cff to 19628f5 Compare October 29, 2026 20:23
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from f755f8c to cd33ec6 Compare October 29, 2026 20:23
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 19628f5 to 7f9e390 Compare October 29, 2026 20:39
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from cd33ec6 to 7bad1f8 Compare October 29, 2026 20:39
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-events branch from 7f9e390 to 2b8016e Compare October 29, 2026 20:53
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from 7bad1f8 to a281c34 Compare October 29, 2026 20:53
Base automatically changed from rlamb/overrides-dotnet-events to dotnet-server-sdk-v9 October 29, 2026 21:08
Adds FileOverrides.Source() and FileOverrideSourceBuilder with FilePaths,
DuplicateKeysHandling (Fail by default, or Ignore), ChangeDetection
(Polling by default, or Watching), PollInterval (one second by default
and at minimum), and Parser for non-JSON content. The source reads files
in the file data document format, expands flagValues entries into flags
that are off and serve their single value, merges the files in order,
treats a missing file as contributing no overrides, keeps the last good
overrides across a file that cannot be read or parsed with a logged
failure and a retry, and logs the overrides in effect and what each file
supplied at Info level on every applied change, as defined by the
OVERRIDE specification.
… interval above the timer maximum

The file override source sets up its change detector before it reads the
files, so a change made during the initial load is reported. The builder
rejects a polling interval longer than the timer supports when the client
is created, instead of failing when the source starts.
…ntries as duplicate keys

A path listed more than once would make every entry of that file a duplicate of itself: under the
default fail handling the load failed, and under ignore the first listing won by accident. The
builder now drops the repeats, keeps the first occurrence in its place in the order, and logs a
warning once at build time. This is the behavior the Node SDK has.
The override source builder's Parser method takes the file extension the parser is for. The
parser for a file is chosen by its extension; every other file is parsed as JSON. A configured
file with a YAML extension and no registered parser is a configuration error reported when the
client is created, since it can never load. The source passes the registered parsers to the
reloader. The documentation describes the entry rules: a flag or segment is listed under its
key, the key property is optional and must match, and the version property is optional.
…t on Windows

Windows file names do not distinguish letter case, so the builder's deduplication of the
configured paths ignores case there. On the other platforms the two spellings can name different
files, so both are kept.
… SDKs

A flagValues entry becomes a flag without client-side availability, so
AllFlagsState with ClientSideOnly leaves it out even when the flag it
overrides is client-side. A full definition with clientSide is the way
to override such a flag. A test pins both.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-dotnet-file-source branch from a281c34 to feaf592 Compare October 29, 2026 21:14
@kinyoklion
kinyoklion merged commit b834131 into dotnet-server-sdk-v9 Oct 29, 2026
10 checks passed
@kinyoklion
kinyoklion deleted the rlamb/overrides-dotnet-file-source branch October 29, 2026 21:22
kinyoklion added a commit that referenced this pull request Oct 29, 2026
…ice (#368)

<!-- TERMAG_PR_BODY_BEGIN -->
## Summary

Adds the `flag-overrides` capability to the contract test service and
the `overrides` block of the SDK configuration it accepts, as described
in the test harness service specification. The block's `filePaths`,
`duplicateKeysHandling` (`fail` or `ignore`), `changeDetection`
(`polling` or `watching`), and `pollIntervalMs` map onto
`FileOverrides.Source()` options, and the source is attached to the FDv2
data system builder that the `dataSystem` block configures. The test
service registers a YamlDotNet parser for the `.yaml` and `.yml`
extensions so the harness's YAML document test can run.

The test service also takes an optional port as its first command line
argument, defaulting to 8000, so that several test services can run on
one machine.

It depends on the file-based override source change, #367, 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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Adds **`flag-overrides`** to the contract test service so the harness
can exercise file-based flag overrides on the .NET server SDK.
> 
> Harness JSON can now include an **`overrides`** block (`filePaths`,
`duplicateKeysHandling`, `changeDetection`, `pollIntervalMs`), which is
mapped to **`FileOverrides.Source()`** and attached to the FDv2
**`dataSystem`** builder; **`overrides` without `dataSystem`** fails
fast with an **`ArgumentException`**. **YamlDotNet** is added so
**`.yaml`/`.yml`** override files parse correctly in conformance tests.
> 
> The test service **`Main`** also accepts an optional **port** argument
(default **8000**) so multiple instances can run on one machine.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
477ea95. 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>
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