Skip to content

feat(browser): add experimentation opt-out modes - #423

Merged
leoromanovsky merged 11 commits into
mainfrom
leoromanovsky/browser-experimentation-opt-out-immediate
Oct 22, 2026
Merged

leoromanovsky merged 11 commits into
mainfrom
leoromanovsky/browser-experimentation-opt-out-immediate

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

Applications need per-subject experimentation opt-out while retaining ordinary feature delivery. Both modes use the customer-owned dd_experimentation_opt_out_at timestamp.

Changes

const provider = new DatadogProvider({
  clientToken: '<CLIENT_TOKEN>',
  env: 'production',
  experimentationOptOutMode: ExperimentationOptOutMode.FutureExperimentsOnly,
})
await OpenFeature.setProviderAndWait(provider, {
  targetingKey: user.id,
  ...(user.experimentationOptOutAt && {
    dd_experimentation_opt_out_at: user.experimentationOptOutAt,
  }),
})
const enabled = OpenFeature.getClient().getBooleanValue('new-route-planner', false)
Mode Behavior with a timestamp
Disabled, the default Ignore the attribute.
Immediate Skip all experiment allocations.
FutureExperimentsOnly Keep allocations activated before the cutoff; skip equal, later, or unknown activation.
  • Apply the same policy to online and portable providers, local JSON/protobuf evaluation, and Precompute fetches.
  • Omitted timestamps preserve ordinary evaluation. Invalid enabled input returns INVALID_CONTEXT. Future timestamps do not schedule exclusion.
  • Require Precompute acknowledgement; future mode also checks the exact cutoff. Preserve nanosecond precision and bind caches to the context and policy.
  • Keep ordinary allocation order and evaluation telemetry unchanged. No new telemetry events.

Future mode uses allocation activation, not previous subject enrollment. Earlier allocations keep normal exposure logging. It does not freeze assignment values.

RFC. This PR does not change the Node public API; #420 consumes the shared core implementation for Node.

Release dependencies

Complete the deployment checks on every customer site before publishing core and browser together with exact dependency pins. A merged dependency is not deployment confirmation.

  • Confirm RC validation #139367 and database migration #12479 are deployed before activation publication. Both are merged; deployment has not been verified here.
  • Deploy winner classification #139368, so ordinary winners remain eligible after experiment exclusion.
  • Deploy fallback preservation #139379 after winner classification, so excluded experiments retain ordinary fallback allocations.
  • Deploy activation publication #139381 after its schema/storage prerequisites. Confirm activation coverage and the legacy/unknown-activation policy for allocation types enabled in FutureExperimentsOnly.
  • Deploy edge opt-out #140422 and verify acknowledgements for both modes, including the future-mode cutoff.
  • Merge shared fixture PR #52, replace the temporary fixture pin with its merged commit, and rerun the core and Node fixture suites.
  • Publish the coordinated core/browser releases only after these checks are complete.

Shared fixture follow-up

This PR landed with temporary fixture revision 1c831fed02a428a1b0a4ac412da9b773e22df257. #52 is still open; its latest candidate is b28876200152f00c7d5eb04616f721afa5955c1d, which includes merged fixture PR #53. Refresh to that candidate while #52 is pending, then repin to its merged commit before release.

#53 adds twelve serial-ID/logging cases. Core and Node runners should assert optional result.serialId and result.doLog, preserving zero, explicit null, and omitted expectations, and run repeated cases against the same configuration/provider.

Implementation and tests were prepared with AI assistance.

Test instructions

Shared fixtures cover immediate and future exclusion, activation boundaries, and ordinary fallback. Browser tests cover acknowledgements, timestamp precision, context changes, cache restart, and stale responses. Regression tests cover requested-subject telemetry and rejection of filtered snapshots when opt-out is disabled.

Tested commit Validation Result
DataDog/openfeature-js-client@5e9709b84588b3e93f7847517840c375d05f0bd0 Packed-package Chromium integration: online/portable delivery, local rules, cache restart, successful opted-out evaluations, and retained experiment exposures 39 passed, 0 failed, 0 skipped. Local HTTP fixtures; not live Fastly.

Checklist

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

@github-actions

github-actions Bot commented Oct 21, 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 19 97.7 KiB 35.8 KiB no
DatadogCoreProvider + fetchRulesConfiguration core-provider.html 15 165.1 KiB 51.7 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 6 2.7 KiB 0 B 1.6 KiB 0 B
exposure logging hook tracking-exposure.html 14 65.3 KiB +62.6 KiB 24.8 KiB +23.2 KiB
evaluation logging hook tracking-evaluation.html 14 61.6 KiB +58.9 KiB 23.4 KiB +21.8 KiB
RUM tracking hook tracking-rum.html 10 6.8 KiB +4.1 KiB 3.7 KiB +2.1 KiB
all tracking hooks tracking-all.html 17 69.6 KiB +66.9 KiB 26.8 KiB +25.2 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 39.1 KiB 12.5 KiB no
SSR helpers only (fetch client rules + serialize) 80.8 KiB 23.4 KiB yes
Node provider + SSR helpers 118.8 KiB 34.7 KiB yes

Adding SSR helpers to the provider adds 79.7 KiB raw / 22.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.

@leoromanovsky leoromanovsky changed the title feat(browser): add immediate experimentation opt-out feat(browser): add experimentation opt-out modes Oct 21, 2026
@leoromanovsky
leoromanovsky marked this pull request as ready for review October 21, 2026 19:38
@leoromanovsky
leoromanovsky requested a review from a team as a code owner October 21, 2026 19:38
Comment thread packages/browser/README.md Resolved
Comment thread packages/browser/README.md Resolved
Validate activation timestamps when JSON configurations are received and protobuf
responses are decoded. Reuse parsed instants during evaluation while preserving
nanosecond precision, per-flag errors in every mode, and raw core API compatibility.

Environment: Datadog workspace
Make doLog an explicit boolean in the UFC allocation type, matching the backend
schema. Update test allocations and regenerate the Node package declarations.

Environment: Datadog workspace
Use one mode-and-cutoff cache key scheme and require an explicit acknowledgement policy.
Receive the edited browser rules fixture again so activation preparation uses its final timestamp.

Environment: Datadog workspace
Explain immediate exclusion and the literal activation cutoff used by future-only mode.

Environment: Datadog workspace
Keep one parser for subject cutoffs and JSON activation timestamps. Reject unsupported syntax and excess precision with clear diagnostics, and document the supported format.

Environment: Datadog workspace
Distinguish unavailable browser assignments from malformed context, and only fail activation-dependent evaluations for invalid metadata. Preserve receipt-time parsing and typed activation states.

Environment: Datadog workspace

@aarsilv aarsilv 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.

Thanks for iterating! Approving but note the inconsistency with timezone parsing between edge and SDKs and decide how you want to proceed. My loosely held opinion is to have what the edge does be the source of truth.

The non-multiline end anchor already rejects trailing newlines. Keep the canonical timestamp grammar unchanged.

Environment: Datadog workspace
Accept lowercase timestamp separators and truncate fractional digits beyond nanoseconds. Apply the same normalization to subject cutoffs, JSON activation, and acknowledgements.

Environment: Datadog workspace
@leoromanovsky
leoromanovsky merged commit 58bc213 into main Oct 22, 2026
5 checks passed
@leoromanovsky
leoromanovsky deleted the leoromanovsky/browser-experimentation-opt-out-immediate branch October 22, 2026 12:47
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