Skip to content

[FFL-3364] Support flag selection in Node rules fetches - #445

Merged
sameerank merged 4 commits into
mainfrom
sameerank/FFL-3364-node-flag-selection
Nov 3, 2026
Merged

sameerank merged 4 commits into
mainfrom
sameerank/FFL-3364-node-flag-selection

Conversation

@sameerank

@sameerank sameerank commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Addresses the flag-selection follow-up in FFL-3364. Node callers preparing rules configurations for SSR should be able to request a smaller payload using the same tag and key-pattern selection supported by the browser helper in #426.

Changes

  • Add optional flagSelection: { tags, keyPatterns } to fetchRulesConfiguration for both server and client distribution channels.
  • Reuse the core canonicalizer and send repeated RFC 3986-encoded query parameters. Malformed or over-limit selections reject with ConfigurationFetchError and code invalid_options before fetching. Selection-read and encoding failures are sanitized as well.
  • Unlike the browser helper's reported-drop behavior, the standalone Node helper rejects invalid selection options. A valid selection that would exceed the 8192-byte URL limit is still not sent, as requested in the ticket.
  • Keep backend responses unchanged, including full configurations returned when selection is ignored. Selection is payload filtering, not access control; no new logging API is introduced.
  • Export FlagSelection, document SSR usage, fallback behavior, and FLAG_NOT_FOUND for unselected flags, and add 76 regression cases plus consumer type coverage.
  • Regenerate the standalone declaration bundle. This also includes split-feature fields already present in core; no split-feature runtime behavior is changed here.

Test instructions

From a checkout with the pinned test-fixture submodules initialized:

yarn install --immutable
yarn workspace @datadog/flagging-core build
yarn workspace @datadog/openfeature-node-server build
yarn workspace @datadog/openfeature-node-server test --runInBand
yarn workspace @datadog/flagging-core test --runInBand
yarn workspace @datadog/openfeature-node-server typecheck
yarn lint

Local validation on the latest changes passed:

  • All 623 Node tests, including 76 new selection cases. The added edge cases cover throwing getters, revoked proxies, unstable list elements, sanitization of caller-thrown errors, and validation before cancellation.
  • Node build and declaration generation, Node type checking, repository lint, and formatting checks for changed files.

Also validated during the initial implementation, before the validation/error-handling follow-up:

  • Core build and 1,148 core tests passed; 6 existing tests skipped. Core is unchanged by this PR.
  • Isolated local-tarball checks without OpenFeature installed: CommonJS/ESM imports, consumer types under Node/CommonJS, Node16, and NodeNext resolution, and absence of browser SDK dependencies.

Fetch tests use fixture responses. No live staging requests or new end-to-end scenarios were run.

Checklist

  • Updated Documentation
  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

Add optional tag and key-pattern selection to both rules distribution channels, reuse core canonicalization, and fall back to unfiltered requests for invalid or oversized selections. Add regression and consumer type coverage plus SSR documentation.

Refs: FFL-3364
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

OpenFeature Browser Provider Bundle Sizes

Measured from the Vite production output after installing packed @datadog/flagging-core and @datadog/openfeature-browser tarballs.

Both scenarios initialize an OpenFeature provider, evaluate a boolean flag, change context, and evaluate again. Telemetry is disabled for DatadogProvider; no tracking hooks are registered for DatadogCoreProvider. DatadogProvider fetches precomputed assignments for each context; DatadogCoreProvider receives rules from fetchRulesConfiguration once and evaluates locally.

Sizes include OpenFeature and the same small scenario harness. Configuration responses are supplied by Playwright and are not bundled. These are complete scenario JS sizes, not configuration payload sizes or isolated provider/Protobuf costs; the difference between rows is not a decoder-only delta.

Scenario HTML JS Assets Raw JS Gzip JS Protobuf Markers
DatadogProvider (precomputed fetching) provider.html 15 104.4 KiB 37.7 KiB no
DatadogCoreProvider + fetchRulesConfiguration core-provider.html 15 196.1 KiB 63.4 KiB yes

Dependency checks passed: no Protobuf markers in default/precomputed scenarios; markers present in rules-based scenarios.

OpenFeature Browser Tracking Hook Bundle Sizes

Synthetic entrypoints import and call tracking hook factories from the packed @datadog/openfeature-browser/rules-based ESM package. Deltas are measured against the no-hook baseline from the same Vite production build.

Scenario HTML JS Assets Raw JS Raw Δ Gzip JS Gzip Δ
tracking hook baseline tracking-baseline.html 5 2.7 KiB 0 B 1.5 KiB 0 B
exposure logging hook tracking-exposure.html 10 71.1 KiB +68.4 KiB 26.4 KiB +24.9 KiB
evaluation logging hook tracking-evaluation.html 11 66.7 KiB +64.0 KiB 24.9 KiB +23.4 KiB
RUM tracking hook tracking-rum.html 8 6.8 KiB +4.1 KiB 3.5 KiB +2.0 KiB
all tracking hooks tracking-all.html 13 75.7 KiB +73.0 KiB 28.5 KiB +27.0 KiB

OpenFeature Node Server Bundle Sizes

Independent Webpack production builds of the installed core and Node SDK tarballs, targeting Node 18 with ESM (module export condition). Node built-ins remain external. OpenFeature, dd-trace, and configuration fixtures are not bundled.

Scenario Raw JS Gzip JS Protobuf Modules
Node provider only 42.1 KiB 13.4 KiB no
SSR helpers only (fetch client rules + serialize) 84.0 KiB 24.7 KiB yes
Node provider + SSR helpers 124.4 KiB 36.6 KiB yes

Adding SSR helpers to the provider adds 82.2 KiB raw / 23.2 KiB gzip in this build. This includes fetching, parsing, and serialization, not just the decoder. SSR helpers fetch rules for the browser, not for configuring that provider (which uses its existing JSON API).

These are bundler sizes, not npm install sizes, unbundled Node require/import costs, payload sizes, or latency. Gzip compares compressed artifacts. The browser scenarios include OpenFeature and a different harness, so their sizes are not directly comparable.

Dependency checks passed: no Protobuf runtime or generated schema modules in the provider-only bundle; present in both SSR bundles (checked using Webpack module statistics).

This report shows current PR artifact sizes only; it does not compare against the base branch.

@sameerank sameerank added the crucible: reviewing A Crucible review has started; this PR has reviewer attention. label Oct 30, 2026
Report malformed and over-limit selections as invalid_options, sanitize selection-read and encoding failures, and preserve the oversized-URL fallback. Add regression coverage and clarify selection behavior in the docs.

Refs: FFL-3364
@sameerank
sameerank marked this pull request as ready for review October 30, 2026 17:21
@sameerank
sameerank requested a review from a team as a code owner October 30, 2026 17:22
@leoromanovsky

leoromanovsky commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

FFE reviewer selection

Reviewers: @leoromanovsky, @vjfridge

Why: Configuration scoping via tags — The PR adds an optional flagSelection option (tags and keyPatterns) to the Node fetchRulesConfiguration helper. Reviewers come from the matched pod.

🤖 Automated selection.

Selection details

Initiative: project / flag-tag-filtering

Why this match: The PR adds an optional flagSelection option (tags and keyPatterns) to the Node fetchRulesConfiguration helper. It canonicalizes the selection with the core helper, sends it as repeated selected_flag_tags and selected_flag_key_patterns query parameters, and rejects invalid selections. A selection that would push the URL past 8192 bytes is not sent. This is tag- and name-based configuration filtering in the request path, matching the browser helper. The FlagSelection type export, README and typecheck updates support the same purpose. No openfeature-architecture supplemental area applies: provider lifecycle and evaluation metadata contracts are unchanged, and the Split/Feature additions in the regenerated rules-based.d.ts are generated declarations of fields already in core, with no runtime change.

  • @leoromanovsky — primary (configuration-delivery): Matched pod member. Existing review coverage preserved.
  • @vjfridge — core (configuration-delivery): Matched pod member. Existing review coverage preserved.

The PR author was excluded. Existing reviewers and teams were preserved.

Debug or fix a problem

Workflow run · Bits Code session

  • Wrong project or pod? Clarify this PR’s purpose, or correct the reviewer registry and publish the change. Sheet edits take effect after publication.
  • Need another reviewer now? Request them in GitHub. This workflow only adds requests and keeps existing reviewers.
  • Stuck beyond five minutes or failed? Inspect the run’s failed step and available Bits session. After fixing the cause, rerun the workflow with this PR URL and dry_run=false. Unchanged completed runs are skipped. If the receipt says needs_reconciliation or a write outcome is uncertain, ask the workflow owner to reconcile GitHub and the receipt before retrying; do not delete the lock blindly.
  • Report a problem: Comment on the reviewer-routing skill PR with this PR URL, the workflow run link, and the expected initiative or reviewer correction.
Evidence and run details

✅ Complete — reviewer coverage verified. The workflow checked pending requests or submitted reviews on the evaluated head.

Generated asynchronously by the FFE reviewer-routing workflow.

Changed paths: packages/node-server/src/configuration/fetch-rules-configuration.ts, packages/node-server/src/rules-based.ts, packages/node-server/test/fetch-rules-configuration-selection.spec.ts, packages/node-server/README.md, test-app-node/typecheck.ts, test-app-node/typecheck.mts

Evaluated head: 52746aecb1c2489b78087681a5845021552075c8

Registry revision: revision:efde5129-b144-41ab-b630-1adf6ad22901

Pinned skill source: 7e3283c602f6b18a7e3e623eecb52d3a9c091a2e

Instructions: workflow-injected snapshot (no plugin loaded)

Instruction snapshot SHA-256: 52ed8cb68675dde1a47addaefe4a9500891c24014abf7dade6dfe38cf836c1d2

@sameerank sameerank removed the crucible: reviewing A Crucible review has started; this PR has reviewer attention. label Oct 31, 2026

@leoromanovsky leoromanovsky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@sameerank
sameerank requested a review from mark-caii November 2, 2026 15:38

Copy link
Copy Markdown
Contributor

🤖 Comment from Claude Code working with @mark-caii

Two things worth resolving before merge. The description is accurate to the code; these are about behaviour relative to the flag-selection RFC.

1. Invalid selections are rejected, which differs from the RFC and the browser SDK

The RFC Requirements say that over a limit, the SDK drops the selection, requests the full UFC, and logs an error, and that the SDK MUST log an error when DD-Flag-Selection-Ignored comes back. The shared selection vectors in core encode the same "drop" outcome (sdk.dropped). At 52746ae:

  • an invalid selection rejects with invalid_options (fetch-rules-configuration.ts#L109);
  • an over-long request URL drops the selection silently (#L112);
  • DD-Flag-Selection-Ignored is never read.

The first commit (e67f128) dropped and fell back; the second switched to rejecting. The description attributes rejection to the ticket, but the FFL-3364 comment only asked to drop selections that would make the URL too long. Either align with the RFC (drop and fall back to the full configuration, as the browser does) or record the reject decision in the RFC and FFL-3364 so the two SDKs' documented contracts match.

2. Both fallbacks are silent

When the URL would exceed 8192 bytes, and when the server ignores the selection, the caller gets no log, no telemetry, and no return signal. For SSR this means a page can embed the full client configuration (823 flags on staging vs a handful with the selection) without anyone noticing. The browser reports both cases with display.error plus Flag selection dropped / Flag selection ignored telemetry carrying the reason only, and reads the header only when a selection was sent and the response is 2xx (browser fetchRulesConfiguration.ts). Something equivalent for Node (a logger hook, a warning, or a field on the result) would make both cases visible.

Drop invalid and oversized selections consistently with the browser contract. Report dropped and server-ignored selections through a configurable logger, with regression coverage and updated declarations.

Ref: FFL-3364
Preserve both the standalone flag-selection documentation and the experimentation opt-out documentation from main.

@mark-caii mark-caii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Comment from Claude Code working with @mark-caii

Verified at 97a580b: invalid, malformed and over-long (8192-byte URL) selections are now dropped, the full configuration is fetched, and one error is logged through the new logger option. DD-Flag-Selection-Ignored is read only when a selection was sent and the response is 200, and its reason is logged verbatim. This matches the RFC and the browser SDK, so both points from my earlier comment are resolved.

@sameerank
sameerank merged commit 2e92669 into main Nov 3, 2026
4 checks passed
@sameerank
sameerank deleted the sameerank/FFL-3364-node-flag-selection branch November 3, 2026 22:26
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.

3 participants