Skip to content

feat: Add the override marker and override-affected evaluation marking - #2050

Merged
kinyoklion merged 8 commits into
feat/overridesfrom
rlamb/overrides-node-evaluator-marking
Oct 9, 2026
Merged

kinyoklion merged 8 commits into
feat/overridesfrom
rlamb/overrides-node-evaluator-marking

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR is based on the shared file data branch because it builds on the same feature branch history and should merge after it.

This change adds the model and evaluator work that the flag overrides feature of the OVERRIDE specification builds on. Nothing in the SDK sets the marker yet, so the SDK's behavior is unchanged until the override store lands.

Flag and segment definitions can carry an override marker. The Node SDK stores definitions in their JSON data model form, so the marker lives under a reserved, namespaced key. Only the new overrideMarker module names that key. Other code reads or strips the marker through its functions. The serializer omits the marker at the root of a flag or segment, so it never leaves the process, and leaves nested values untouched. Definitions read from LaunchDarkly or from a store lose the key as they are ingested, so only the override store can set it. Only the SDK's override store will set it, on the entries it holds.

The evaluator marks an evaluation as override-affected when any definition it read carries the marker: the evaluated flag, a prerequisite flag at any depth, or a segment consulted during matching, whether or not the segment matched. The marking propagates upward only. A prerequisite evaluation starts its own marker from its own definition and merges it into its parent's marker when it completes, so the record of a prerequisite reflects only the definitions that its own evaluation read. Error results the evaluator produces are marked too, so a malformed override definition yields the caller's default with a marked error reason. The client's own replacement of a result on a requested-type mismatch keeps the marking in the override layer change that follows, where the marker is first set.

The evaluation reason gains an optional overrideAffected indicator that is set only when true, following the inExperiment precedent. The evaluation result carries the same marking as a scalar so that event generation can key on it without reading the reason. Migration operation events keep the indicator on their evaluation reason when it is true. The OpenFeature provider reports it in flag metadata, following the inExperiment precedent.

The tests mirror the Go evaluation library's marking tests: direct marking for off, fallthrough, rule match, and error results, prerequisite records at depth one and two, an unaffected sibling prerequisite, segments matched, not matched, negated, referenced through another segment, and resolved through big segment membership, unresolvable definitions, and the agreement between the result scalar and the reason indicator.

SDK-3247


Note

Overview
Introduces experimental flag override plumbing: flag/segment definitions can carry an internal _sdk_override marker (set only by the future override store), and evaluations expose overrideAffected on the reason and result when any read definition was override-sourced—directly or via prerequisites/segments at any depth.

The evaluator tracks marking per evaluation (including prerequisite records and segment reads, even on non-match/negated clauses), merges it up the prerequisite tree without mutating shared reason singletons, and omits overrideAffected from JSON unless it is true (same pattern as inExperiment). Ingest/serialization strips root _sdk_override from LD payloads and persistent-store round-trips while preserving nested customer data that happens to use the same key name.

OpenFeature flag metadata and migration op event conversion forward overrideAffected when true. Until the override store lands, nothing sets the marker, so runtime behavior for normal LD data should stay the same aside from the new optional fields and strip-on-ingest safety.

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

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-filedata branch from c7a8bf8 to c32d023 Compare September 28, 2026 20:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 9e22472 to e6ad1d1 Compare September 28, 2026 20:28
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-filedata branch from c32d023 to 8c05f0b Compare September 28, 2026 21:20
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from e6ad1d1 to 30eb906 Compare September 28, 2026 21:20
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-filedata branch from 8c05f0b to 79d2f0b Compare September 29, 2026 15:20
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 30eb906 to 11aaea9 Compare September 29, 2026 15:20
@kinyoklion
kinyoklion added this pull request to stack #2061 September 30, 2026 17:08
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 29603 bytes
Compressed size limit: 30000
Uncompressed size: 142116 bytes

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 32662 bytes
Compressed size limit: 34000
Uncompressed size: 117055 bytes

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25437 bytes
Compressed size limit: 44000
Uncompressed size: 165420 bytes

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-filedata branch from 79d2f0b to 3b34072 Compare September 30, 2026 18:17
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 11aaea9 to 91d45a2 Compare September 30, 2026 18:17
@kinyoklion
kinyoklion removed this pull request from stack #2061 September 30, 2026 18:37
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-filedata branch from 3b34072 to 484b8cf Compare September 30, 2026 20:16
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 91d45a2 to 7874143 Compare September 30, 2026 20:16
@kinyoklion
kinyoklion changed the base branch from rlamb/overrides-node-filedata to rlamb/overrides-node-file-poller September 30, 2026 20:16
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 487be22 to 4376e71 Compare September 30, 2026 20:23
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 7874143 to 4ecf628 Compare September 30, 2026 20:23
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 4376e71 to 6478cbc Compare September 30, 2026 21:01
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 4ecf628 to 2be4e36 Compare September 30, 2026 21:01
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 6478cbc to cf939a8 Compare September 30, 2026 21:04
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 2be4e36 to fa5493d Compare September 30, 2026 21:04
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from cf939a8 to f2eb769 Compare September 30, 2026 22:22
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from fa5493d to f8012e2 Compare September 30, 2026 22:22
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from f2eb769 to 1ca25de Compare September 30, 2026 22:27
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from f8012e2 to 5605b28 Compare September 30, 2026 22:27
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 1ca25de to 6150019 Compare October 1, 2026 23:07
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 5605b28 to f5a88e9 Compare October 1, 2026 23:07
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 6150019 to 8f495fe Compare October 1, 2026 23:52
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from f5a88e9 to 08abea8 Compare October 1, 2026 23:52
@kinyoklion
kinyoklion requested a review from a team as a code owner October 2, 2026 23:19
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 8f495fe to 4c0b961 Compare October 3, 2026 00:40
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 08abea8 to 4b7069e Compare October 3, 2026 00:40
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 4c0b961 to 098d985 Compare October 5, 2026 22:32
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 554cfbd to 14bcbd6 Compare October 5, 2026 22:32
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from 098d985 to d7729d8 Compare October 5, 2026 22:45
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 14bcbd6 to cb45af0 Compare October 5, 2026 22:45
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from d7729d8 to b2343d0 Compare October 8, 2026 16:38
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from cb45af0 to 9b941d7 Compare October 8, 2026 16:38
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from b2343d0 to af68e40 Compare October 8, 2026 22:17
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 9b941d7 to 50819bc Compare October 8, 2026 22:17
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from af68e40 to fecb728 Compare October 8, 2026 22:19
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 50819bc to 4550837 Compare October 8, 2026 22:19
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from fecb728 to f703d00 Compare October 8, 2026 22:35
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 4550837 to 8650066 Compare October 8, 2026 22:35
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-file-poller branch from f703d00 to 6d3ca83 Compare October 9, 2026 18:49
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 8650066 to 405de3e Compare October 9, 2026 18:49
Base automatically changed from rlamb/overrides-node-file-poller to feat/overrides October 9, 2026 19:56
Adds the model and evaluator changes that the flag overrides feature of the
OVERRIDE specification builds on.

Flag and segment definitions can carry an override marker. The marker lives
under a reserved, namespaced key that only the overrideMarker module names, so
it cannot collide with a data model field. The serializer omits it, so it never
leaves the process. Only the SDK's override store sets it.

The evaluator marks an evaluation as override-affected when any definition it
read carries the marker: the evaluated flag, a prerequisite flag at any depth,
or a segment consulted during matching, whether or not the segment matched. The
marking propagates upward only. A prerequisite evaluation starts its own marker
and merges it into its parent's marker when it completes, so the record of a
prerequisite reflects only the definitions that its own evaluation read. Error
results are marked too.

The evaluation reason gains an optional overrideAffected indicator that is set
only when true, and the evaluation result carries the same marking as a scalar
so that event generation does not need to read the reason. Migration operation
events keep the indicator on their evaluation reason.
…gment only

A property of the same name inside a variation value or a clause value is data and is stored unchanged.
… module

Flag and Segment extend OverrideMarkable instead of each declaring the key. The interface is no longer tagged internal because the bundled typings of Flag and Segment reference it.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-node-evaluator-marking branch from 405de3e to 95c87a5 Compare October 9, 2026 19:58
…definition

The marker was read through the prototype chain while the strip removes own
keys only, so a definition that inherited the key could read as an override
after a copy with assignment semantics. The marker now counts only as an own
property that is exactly true.

The strip threw on a null segment entry, which processSegment tolerated
before the marker was added, so a payload with such an entry was rejected.
The strip now leaves a value that is not an object alone.

Tests folded in from the review: edge cases of the marking (prerequisites
that are off, fail, or are never read; segments behind failing clauses, in
cycles, and in big segment stores; shared reason objects; interleaved
evaluations) and the marker outside the evaluator (all-flags state, ingest
paths, serializers, persistent store round trip).

Negative controls: the null-tolerance test fails when the guard is removed;
the own-property test fails when the hasOwnProperty check is removed.
@kinyoklion
kinyoklion merged commit fabd142 into feat/overrides Oct 9, 2026
59 checks passed
@kinyoklion
kinyoklion deleted the rlamb/overrides-node-evaluator-marking branch October 9, 2026 21:24
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