feat(openfeature): preserve configuration source semantics - #9413
Conversation
Define source selection, managed and custom endpoint derivation, staging and GovCloud routing, and bounded timing semantics. Keep this policy layer independent from transport and provider activation, with focused resolver tests.
Fetch one authenticated UFC snapshot with the runtime transport, suppress tracer instrumentation, validate the JSON:API resource, and only advance last-known-good state after successful application. Cover gzip, ETags, malformed payloads, and custom endpoints alongside the loader.
Turn snapshot loading into a fixed-delay lifecycle with bounded retries, independent timeouts, overlap prevention, rate-limited warnings, TLS-aware credential handling, and idempotent shutdown. Exercise timing, failure, and cancellation boundaries with fake timers.
Expose the configuration surface, attach the selected source to the provider lifecycle, and keep Agent Remote Configuration behind explicit opt-in. Pair the activation path with fleet-wide config, provider, shutdown, and packaged-application tests.
Overall package sizeSelf size: 6.8 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.2 | 124.41 kB | 440.65 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🔄 Datadog retried 1 test - 1 passed on retry 🎯 Code Coverage (details) 🔗 Commit SHA: 94a16dc | Docs | Datadog PR Page | Give us feedback! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source #9413 +/- ##
=================================================================================================
- Coverage 98.35% 96.92% -1.43%
=================================================================================================
Files 927 926 -1
Lines 123669 123703 +34
Branches 10629 21513 +10884
=================================================================================================
- Hits 121637 119905 -1732
- Misses 2032 3798 +1766 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
BenchmarksBenchmark execution time: 2026-07-21 03:25:22 Comparing candidate commit 94a16dc in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2321 metrics, 37 unstable metrics.
|
e9d4e43 to
4f57fe8
Compare
|
@codex Review this. Focus on changes to start-up behavior to lazy init. |
There was a problem hiding this comment.
Pull request overview
This PR refines dd-trace-js OpenFeature Feature Flagging configuration-source precedence to preserve legacy Remote Config semantics while introducing a stable global enable switch and a lazy agentless (CDN) activation boundary tied to tracer.openfeature access, preventing unintended billed network traffic at tracer startup.
Changes:
- Adds stable
DD_FEATURE_FLAGS_ENABLEDkill switch and updates configuration-source resolution to preserve legacy opt-in/out semantics when the new source is absent. - Makes agentless polling lazy by deferring provider construction and source startup until application code accesses
tracer.openfeature. - Updates unit/integration tests to validate precedence, capability/RC subscription behavior, and new environment variables.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/dd-trace/test/proxy.spec.js | Updates proxy test wiring for OpenFeature RC enablement call signature. |
| packages/dd-trace/test/openfeature/remote_config.spec.js | Updates RC unit tests to match new subscription/capability semantics. |
| packages/dd-trace/test/openfeature/register.spec.js | Verifies lazy construction/activation boundary and updated RC hook invocation. |
| packages/dd-trace/test/openfeature/configuration_source.spec.js | Adds coverage for stable kill switch + legacy/explicit-source precedence rules. |
| packages/dd-trace/test/config/index.spec.js | Verifies config defaults/origins and env var parsing for the new stable switch. |
| packages/dd-trace/src/openfeature/remote_config.js | Changes RC enablement to gate capability + handler registration on explicit subscription. |
| packages/dd-trace/src/openfeature/register.js | Implements lazy provider exposure via getter and uses resolved source to decide RC subscription. |
| packages/dd-trace/src/openfeature/index.js | Removes noisy debug log on module enable. |
| packages/dd-trace/src/openfeature/configuration_source.js | Implements new resolution model (enabled + source) and stable kill switch behavior. |
| packages/dd-trace/src/config/supported-configurations.json | Adds DD_FEATURE_FLAGS_ENABLED to supported configuration inventory. |
| packages/dd-trace/src/config/generated-config-types.d.ts | Adds generated typing for DD_FEATURE_FLAGS_ENABLED. |
| integration-tests/openfeature/openfeature-exposure-events.spec.js | Migrates integration tests to stable enable var while keeping RC source explicit. |
| integration-tests/openfeature/openfeature-agentless.spec.js | Migrates agentless integration test env vars to stable enable var. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23a9e2bb78
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bd5962c to
8154935
Compare
| /** | ||
| * Whether to enable the feature flagging provider. | ||
| * Requires Remote Config to be properly configured. | ||
| * Can be configured via DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED environment variable. |
There was a problem hiding this comment.
I think this file needs more revising.
Would it be beneficial to mention DD_FEATURE_FLAGS_ENABLED to encourage the newer env variable instead? Also, is it worth documenting DD_FEATURE_FLAGS_CONFIGURATION_SOURCE here? Another place to add documentation about configuring the provider might be docs/API.md
Also the provider now defaults to enabled, and that legacy variable specifically selects Remote Config, so this might be confusing
Lines 159 to 164 in 5b83654
typotter
left a comment
There was a problem hiding this comment.
agreed on documentation of the options/modes being super important here. other than that 🚀
| env: { | ||
| DD_API_KEY: 'integration-api-key', | ||
| DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED: 'true', | ||
| DD_FEATURE_FLAGS_ENABLED: 'true', |
8154935 to
05df5bd
Compare
|
Handling this scope in #9397 |
Motivation
Until now, Node.js customers could activate the Datadog OpenFeature provider only by setting
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true. That experimental input did more than enable a provider: the implementation it unlocked subscribed to the Agent'sFFE_FLAGSRemote Configuration product. Customers who set it therefore adopted Feature Flags with Remote Configuration's Agent connectivity, network path, and delivery assumptions.The new configuration model makes CDN-backed agentless delivery the default for new customers so they no longer need an experimental opt-in. We cannot reinterpret the existing legacy opt-in as agentless, however, because that would silently move an established customer to a different delivery system. During the migration window, explicit legacy
truemust remain on Remote Configuration, explicit legacyfalsemust remain disabled, and only an absent legacy value may receive the new agentless default.This also has a billing boundary. Requests to Remote Configuration and the CDN are billing surfaces, so "agentless is the default" cannot mean that installing or initializing the tracer starts polling. The migration separates the concerns previously collapsed into the experimental switch:
DD_FEATURE_FLAGS_ENABLEDis the global kill switch,DD_FEATURE_FLAGS_CONFIGURATION_SOURCEchooses a delivery system when explicit, and application access totracer.openfeatureactivates default agentless polling. This PR implements that precedence on top of sealed #9397 without creating phantom traffic or breaking the existing RC cohort.flowchart TD Start["Tracer starts"] --> Enabled{"DD_FEATURE_FLAGS_ENABLED"} Enabled -->|"false"| Disabled["Provider disabled<br/>No RC subscription<br/>No CDN polling"] Enabled -->|"true or unset"| Explicit{"Explicit configuration source?"} Explicit -->|"remote_config"| RC["Eager FFE_FLAGS subscription"] Explicit -->|"agentless"| Wait["Wait for application provider access"] Explicit -->|"unsupported offline or invalid"| Closed["Fail closed<br/>No network delivery"] Explicit -->|"absent"| Legacy{"Legacy experimental variable?"} Legacy -->|"true"| RC Legacy -->|"false"| Disabled Legacy -->|"absent"| Wait Wait --> Access["Application accesses tracer.openfeature"] Access --> CDN["Start CDN-backed polling"]Changes
Separate enablement from source selection. The resolver now returns an enabled state independently from the selected source. A disabled provider has no synthetic
disabledsource; enabled configurations select onlyagentlessorremote_config.Apply the compatibility precedence once. Global
falsewins first, an explicit supported source wins next, and the legacy variable is consulted only when the new source is absent. The result is fixed for the lifetime of the tracer.Activate only the selected delivery path. Explicit and grandfathered Remote Configuration remain eager. The provider, exposure writer, Agent strategy probe, and agentless poller remain lazy until application access to
tracer.openfeature. Invalid input, including reservedoffline, fails closed without RC or CDN traffic.Preserve migration observability. Existing configuration-origin reporting remains intact so legacy usage can be measured during the migration window.
Decisions
The kill switch is absolute.
DD_FEATURE_FLAGS_ENABLED=falsedisables the provider and prevents both CDN polling and theFFE_FLAGSsubscription.truemerely permits source resolution.Existing customers stay on Remote Configuration. With no explicit new source, legacy
truepreserves RC and legacyfalsepreserves disabled behavior. Only customers with no legacy value receive the new default.Agentless is the default, not startup traffic. New customers default to agentless, but polling begins only after application provider access. An explicit
agentlessorremote_configselection still overrides the legacy value.Only two sources are supported today.
agentlessandremote_configare valid.offlineis reserved for future startup-provided UFC bytes and currently fails closed; it is not the disable switch.Validation
No-Agent dogfooding proof
The exact Node.js head
94a16dc3017bf63056561645521a47a5ef625e93was packaged with the repository'snpm pack-based dogfooding helper and mounted into the Node app fromffe-dogfoodingheadc2ca5be1ae59d7ef083ef731957881d23f95ff00. The resultingdd-trace@7.0.0-preartifact had SHA-256d36448abe2708a56dadc3b9dc190500330e6d80e3144a04479293f17478689c3.The isolated Compose graph rendered only:
datadog-agentwas absent from both the rendered graph and running services. Container inspection also confirmed thatDD_FEATURE_FLAGS_CONFIGURATION_SOURCE,DD_FEATURE_FLAGS_ENABLED, andDD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLEDwere all absent.FFE_ACTIVATE_PROVIDER_ON_STARTUP=falsekept the application on the pre-access side of the billing boundary.Result: PASS: tracer startup was silent and provider access started agentless delivery.
providerActivated=false,providerState=PROVIDER_NOT_READY, andconfiguration_requests_total=0after the validator's three-second silence window./activate-providerproviderActivated=true,providerState=PROVIDER_READY, and the first configuration request reached/api/v2/feature-flagging/config/rules-based/server.ffe-dogfooding-string-flagreturneddogfood-treatmentwith reasonTARGETING_MATCH.GET /healthreturned{"status":"healthy"}.GET /api/latest-resultsreported Node.jsstatus=success,variant=dogfood-treatment,evaluation_reason=TARGETING_MATCH, andprovider_state=PROVIDER_READYat2026-07-21T03:30:18Z.This run also found a dogfooding-harness mismatch before product validation: the local mock returned raw UFC while the CDN contract and tracer require a JSON:API
universal-flag-configurationenvelope. The same fixture was wrapped in that envelope for the proof run;apps/mock-intakestill passedgo test ./.... No tracer code was changed for the correction, and the temporary harness/Compose edits were removed after the isolated stack was torn down.Cross-repository system-tests proof
The exact Node.js head
94a16dc3017bf63056561645521a47a5ef625e93was validated against mergedsystem-testsmain at54649784b2d2807522ebeaabae49ffe70e921014(DataDog/system-tests#7332).binaries/nodejs-load-from-localmounted the localdd-trace-js-node-configuration-contractworktree, whose checked-out head was the signed Node.js commit above. The freshly rebuilt parametric environment exercised 19 source/enablement cases plus the slow-poller concurrency boundary.Result: 20 passed in 25.92s.
The focused registration regression test passed 7/7 and proves tracer initialization does not initialize the OpenFeature module before provider access. The complete OpenFeature unit suite passed 229/229, and the OpenFeature integration suite passed 5/5.
remote_configFFE_FLAGScapability/product, and sends zero CDN requests.falsewith explicit RC/ffe/start, evaluates the expected value and sends the authenticated request to the expected CDN path.truewith no sourcetruefalsewith legacytruefalsetruefalsefalsewith default agentlessofflineinputmax_in_flight == 1, proving requests do not overlap.