Repository navigation
fix: Reject deleted flags and segments in flag overrides - #2187
kinyoklion wants to merge 1 commit into
Conversation
The override layer never holds a deleted item (sdk-specs #288, Requirements 1.1.1.1, 1.2.8.1 and 1.3.6.2). A snapshot that contains a flag or segment with "deleted": true is rejected as a whole: the layer is unchanged, no change notifications are sent, the sink throws to the source, and the SDK logs the rejection at Error level with the kind and the key. The check runs before the shape checks, so a deleted placeholder is reported as deleted, not as a flag without variations. A "deleted" field that is not a boolean is a shape error. "deleted": false is an ordinary definition. The file-based override source fails a file that contains such an entry while it reads the file, the same way as a file that cannot be parsed: the previous overrides stay, the failure is logged with the file and the key, and the retry applies. The ordinary file data sources are unchanged on purpose. Rejecting deleted entries there would be a breaking change. New tests pin their behavior.
|
bugbot review |
|
@launchdarkly/js-client-sdk-common size report |
|
@launchdarkly/js-client-sdk size report |
|
@launchdarkly/js-sdk-common size report |
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 617ec2c. Configure here.
Requirements
Related issues
Implements the "reject deleted entries" rule of launchdarkly/sdk-specs#288 (OVERRIDE Requirements 1.1.1.1, 1.2.8.1 and 1.3.6.2): the override layer never holds a flag or segment marked as deleted.
Describe the solution you've provided
Before:
"deleted": trueand served it as a live definition. A deleted flag was rejected only when it also lackedvariations, and the message was"variations" must be an array, not about deletion. A full definition plusdeleted, and any deleted segment, were stored and served.After:
"deleted": trueis rejected as a whole, from any source, including a custom one. The layer is unchanged and no change notifications are sent. The sink still throws to the source, now withflag "f" is marked as deleted; to remove its override, leave it out of the snapshot. The SDK also logs the rejection at Error level with the kind and the key, because a custom source may not report the error. The check runs over the whole snapshot before the shape checks, so a deleted placeholder is reported as deleted. Adeletedfield that is not a boolean is now a shape error ("deleted" must be a boolean)."deleted": falseis an ordinary definition and still loads.Unable to load flags: flag "f" is marked as deleted; remove the entry to remove its override [/path/overrides.json].LDOverrideSink.setOverrides. Every public override API keeps its experimental note.The ordinary file data sources are unchanged on purpose: the FDv1
FileDataSourceand the FDv2 file initializer still load deleted entries as they always have. Rejecting them there would be a breaking change, planned for a later major version. They share thefiledataengine with the override source through an injected policy, so the check is only in the override code (overrideDocument.ts,OverrideLayer.ts,OverrideSink.ts,validateDefinition.ts). New tests pin the data source behavior.Tests added:
OverrideLayer.test.ts: rejects a deleted flag placeholder, a full flag plusdeleted, a deleted segment placeholder, and a full segment plusdeleted, and keeps the previous contents each time. Reports the deleted item even when another definition is malformed. Does not modify the supplied definitions. Acceptsdeleted: falseandnull. Rejects a non-booleandeletedon a flag and on a segment.OverrideSink.test.ts: a deleted flag, a full flag plusdeleted, and a deleted segment are each rejected. The layer is unchanged, nothing is notified, and exactly one Error log names the kind and the key.LDClientImpl.overrides.test.ts: a custom source's snapshot with a deleted flag is rejected and logged. The LaunchDarkly value and the earlier override stay in effect in evaluation and inallFlagsState.FileOverrideSource.test.ts: a deleted flag entry and a deleted segment entry each fail the reload and keep the overrides. The log names the key and the file. The retry repeats at debug level and recovers once the entry is removed. A deleted entry in one of two files fails the initial load.deleted: falseentries load into the layer.overrideDocument.test.ts: rejects deleted flag and segment entries in JSON and YAML, and acceptsdeleted: false.FileDataSource.test.tsandfileDataInitilizerFDv2.test.ts: pin that both data sources still load deleted entries with no error.All new rejection tests fail against the
mainsources. Thedeleted: falsetests and the data source pins pass on both.How I ran them:
yarn workspace @launchdarkly/js-sdk-common build, then inpackages/shared/sdk-server:yarn test(86 suites, 1608 passed, 12 skipped),yarn lint(no errors; the warnings are already on main),yarn build.yarn oxfmt --checkon the changed files.packages/sdk/server-node:npx jest --ci --runInBand __tests__/LDClientNode.fileOverrides.test.ts __tests__/LDClientNode.fileDataSource(11 passed).Describe alternatives you've considered
filedataengine. That would change the ordinary file data sources.Additional context
#2154 rewrites some of the same doc comments (
LDDataSystemOptions.ts,FileOverrideSource.ts,overrideDocument.ts), so whichever of the two merges second will need a small doc-comment rebase. The code changes do not overlap.Opened from termag
Note
Overview
Flag override snapshots that include a flag or segment with
deleted: trueare now rejected end-to-end: the override layer never stores them,OverrideSinklogs an error and throws without applying the snapshot or notifying listeners, and the file override parser fails the reload with a message that names the file and key.Removing an override is done by omitting the entry from the snapshot (or deleting the file), not by supplying a deleted marker.
deleted: falseand non-booleandeletedvalues are handled as ordinary definitions or shape validation errors respectively. FDv1/FDv2 file data sources still accept deleted entries unchanged (breaking-change avoidance); only the override path enforces the new rule. Public docs onLDOverrideSink.setOverridesand file override options describe this behavior.Reviewed by Cursor Bugbot for commit 617ec2c. Bugbot is set up for automated code reviews on this repo. Configure here.