Skip to content

feat(openfeature): add agentless Feature Flagging configuration source#9397

Open
leoromanovsky wants to merge 10 commits into
masterfrom
leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source
Open

feat(openfeature): add agentless Feature Flagging configuration source#9397
leoromanovsky wants to merge 10 commits into
masterfrom
leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

NOTE TO REVIEWERS

This PR is intentionally organized as four vertical layers. Review the commits in order: each adds application code together with the tests for that layer. The production implementation is byte-for-byte identical to the previously validated bb4469e head; layer 4 also includes reviewer-requested test clarifications.

Commit Guide

Commit Layer and associated tests LOC (+/-)
7ffa2a3 Resolve delivery policy: source selection, managed/custom endpoints, staging and GovCloud routing, and bounded timing configuration. +321
7b0b67e Load snapshots: built-in fetch, authentication, tracing suppression, gzip, strict JSON:API, ETags, and last-known-good application. +421
b83caab Harden polling: fixed-delay scheduling, retries, independent timeouts, overlap prevention, credential safety, warnings, and shutdown. +449 / -12
ac9b55d Activate delivery: public configuration, provider lifecycle, explicit Remote Configuration opt-in, and packaged application integration. +459 / -7

What does this PR do?

Adds a no-Agent Feature Flagging configuration path to dd-trace-js: load Universal Flag Configuration from the Datadog UFC CDN and evaluate flags locally through the existing OpenFeature provider.

Motivation

Node.js server and serverless applications need to use Feature Flagging without depending on a local Datadog Agent. Agentless CDN delivery becomes the default source, while Agent Remote Configuration remains available through explicit opt-in.

Changes

  • Adds DD_FEATURE_FLAGS_CONFIGURATION_SOURCE with agentless as the default and remote_config as explicit opt-in.
  • Keeps offline startup bytes as future scope; offline is not currently accepted by the public or runtime configuration API.
  • Derives the managed URL as https://ufc-server.ff-cdn.<DD_SITE>/api/v2/feature-flagging/config/rules-based/server?dd_env=<DD_ENV>.
  • Treats DD_SITE=datad0g.com as the staging managed CDN site and regression-tests the exact derived URL.
  • Authenticates CDN requests with DD-API-KEY.
  • Omits the API key from cleartext non-local custom URLs while retaining controlled localhost and host.docker.internal test endpoints.
  • Uses the runtime's built-in fetch, advertises Accept-Encoding: gzip, and delegates successful-response decompression before JSON:API validation.
  • Decodes response bodies only for 200; non-success bodies are cancelled without text or JSON decoding.
  • Suppresses tracing around CDN polling so internal requests do not emit HTTP spans or inject trace-propagation headers.
  • Adds an optional custom agentless URL for controlled system-test, dogfood, and operator-managed endpoints.
  • Requires every managed or custom endpoint response to use JSON:API with data.type = universal-flag-configuration, then passes data.attributes to the existing evaluator.
  • Polls using ETags, If-None-Match, 304, bounded jittered retries, request timeouts, no overlap, last-known-good preservation, a one-hour interval cap, and shutdown cancellation.
  • Starts the Agent FFE_FLAGS Remote Configuration subscription only when remote_config is explicitly selected.
  • Derives the managed endpoint for every DD_SITE, including GovCloud, without hard-coding current CDN availability into the SDK.

Decisions

Agentless is the default. An unset, empty, whitespace-only, or case-variant source value resolves to agentless. Agent Remote Configuration must be selected with remote_config.

Staging uses the same managed contract. DD_SITE=datad0g.com derives https://ufc-server.ff-cdn.datad0g.com/api/v2/feature-flagging/config/rules-based/server?dd_env=<DD_ENV>; staging is not hidden behind a custom endpoint.

Network delivery is strict JSON:API. Every managed or custom endpoint response must contain a universal-flag-configuration resource and UFC fields under data.attributes. A malformed response never replaces the last-known-good configuration.

Gzip is negotiated, not configurable. Agentless requests advertise Accept-Encoding: gzip. Built-in fetch decompresses successful gzip responses before JSON:API validation, while identity responses remain supported. Invalid gzip fails the poll without replacing the last-known-good configuration or ETag.

Custom URLs only override the endpoint. A configured origin receives the standard rules-based path; a configured URL with a non-root path is used as the exact endpoint. All network endpoints use the same JSON:API contract; raw UFC is not accepted over the network.

API keys require TLS outside controlled local endpoints. HTTPS endpoints receive DD-API-KEY. Cleartext HTTP endpoints receive it only for literal loopback hosts, localhost, or host.docker.internal; other HTTP URLs are allowed for compatibility but the key is omitted and an error is logged.

There is no SDK payload-size cap. Valid managed JSON:API payloads larger than 500 KB are accepted.

Transport stays simple. The poller uses the runtime's built-in fetch with no additional HTTP client dependency or runtime-version branch.

Explicit connection reuse is deferred. Built-in fetch owns connection behavior for this PR. A follow-up can add source-owned connection recycling once its lifecycle and system-test contract are designed.

Polling is bounded. The default interval remains 30 seconds. Customer values above one hour are clamped to one hour with a warning.

Timeout completion does not depend on fetch abort behavior. The request timer marks the attempt failed and retryable before signalling AbortController; a late response cannot apply configuration after the timeout has settled.

ETags describe accepted configurations. A 200 response advances the ETag only after parsing and local application succeed. An accepted 200 without a non-blank ETag clears the previous ETag.

GovCloud remains SDK-compatible. Node follows Java and derives https://ufc-server.ff-cdn.ddog-gov.com/... from DD_SITE=ddog-gov.com. Current managed-CDN availability is a backend concern, not an SDK rejection; ordinary request failures preserve caller defaults or last-known-good configuration. An explicitly configured custom endpoint remains available.

No custom headers. Agentless requests expose no custom-header configuration surface. The fixed standard Accept-Encoding header is internal and not customer-configurable.

No emission changes. This PR only loads UFC and evaluates locally. It does not add or modify exposure emission, aggregate-evaluation emission, span enrichment, or evaluation metrics.

Offline is future scope, not an API value. Startup-provided UFC bytes will be added only when its lifecycle and system-test contract are ready.

GovCloud Compatibility

Node intentionally follows the Java implementation: managed delivery derives the UFC CDN hostname from DD_SITE for every site rather than maintaining an SDK-side allowlist.

DD_SITE=ddog-gov.com
  -> https://ufc-server.ff-cdn.ddog-gov.com/api/v2/feature-flagging/config/rules-based/server?dd_env=<DD_ENV>

This is an SDK compatibility decision, not a claim that the managed GovCloud CDN is provisioned today. If the hostname is unavailable, normal polling-failure behavior keeps evaluations on caller defaults or last-known-good configuration. Once the CDN is enabled, GovCloud can begin working without another Node SDK release. Custom endpoints remain supported in the meantime.

Delivery Architecture

flowchart TD
    Gate{Flagging provider enabled?}
    Select{Configuration source}

    Gate -- no --> Disabled[Provider not started]
    Gate -- yes --> Select

    Select -- unset or agentless --> URL{Custom URL?}
    URL -- no --> CDN[Datadog UFC CDN<br/>strict JSON:API]
    URL -- yes --> Custom[Custom endpoint<br/>strict JSON:API]

    CDN --> Poller[Authenticated fetch poller<br/>gzip, ETag, retry, timeout, LKG]
    Custom --> Poller
    Poller --> UFC[Existing UFC pipeline]

    Select -- remote_config --> Agent[Datadog Agent RC<br/>FFE_FLAGS]
    Agent --> UFC

    FutureOffline[Future: offline startup bytes<br/>not currently selectable]
    FutureOffline -. later .-> UFC
    style FutureOffline stroke-dasharray: 5 5

    FutureReuse[Future: explicit connection reuse<br/>not included in this PR]
    FutureReuse -. later .-> Poller
    style FutureReuse stroke-dasharray: 5 5

    UFC --> Evaluator[OpenFeature local evaluator]
    Evaluator --> Result[Application result]
Loading

Poll Lifecycle

sequenceDiagram
    participant App
    participant Source as Node agentless source
    participant CDN as UFC endpoint
    participant Evaluator as Local evaluator

    App->>Source: start
    Source->>CDN: GET .../server?dd_env=<env>
    Note over Source,CDN: DD-API-KEY and gzip with tracing suppressed
    CDN-->>Source: 200 JSON:API or gzip JSON:API plus optional ETag
    Source->>Evaluator: setConfiguration(data.attributes)
    Evaluator-->>App: local flag result

    loop fixed delay after completion
        Source->>CDN: GET with accepted If-None-Match
        alt unchanged
            CDN-->>Source: 304
        else changed
            CDN-->>Source: 200 + configuration
            Source->>Evaluator: apply accepted configuration
        else retryable failure
            CDN-->>Source: timeout, 429, or 5xx
            Source->>CDN: bounded jittered retries
        end
    end

    App->>Source: close
    Source-->>Source: cancel request and timers
Loading

Network JSON:API Contract

{
  "data": {
    "id": "1",
    "type": "universal-flag-configuration",
    "attributes": {
      "createdAt": "2026-07-15T19:57:07.219869778Z",
      "environment": {
        "name": "Staging"
      },
      "flags": {}
    }
  }
}

Requests advertise Accept-Encoding: gzip. Built-in fetch decompresses a response declaring Content-Encoding: gzip before JSON:API validation; identity responses remain supported.

Only data.attributes is supplied to the existing UFC evaluator.

Customer Usage Examples

Default Datadog-managed CDN delivery:

export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
export DD_API_KEY=<datadog-api-key>
export DD_SITE=datadoghq.com
export DD_ENV=prod

# DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=agentless is optional.
node app.js

Datadog staging CDN delivery:

export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
export DD_API_KEY=<staging-api-key>
export DD_SITE=datad0g.com
export DD_ENV=staging
node app.js

Application code for either managed site:

const tracer = require('dd-trace').init()
const { OpenFeature } = require('@openfeature/server-sdk')

await OpenFeature.setProviderAndWait(tracer.openfeature)

const client = OpenFeature.getClient()
const details = await client.getStringDetails(
  'checkout-flow',
  'control',
  {
    targetingKey: user.id,
    plan: user.plan
  }
)

Explicit Agent Remote Configuration delivery:

export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
export DD_FEATURE_FLAGS_CONFIGURATION_SOURCE=remote_config
export DD_REMOTE_CONFIGURATION_ENABLED=true
node app.js

Custom agentless endpoint:

export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
export DD_API_KEY=<backend-api-key>
export DD_ENV=prod
export DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL=https://flags.example.com/ufc

# Custom endpoints return the same JSON:API envelope as the managed CDN.
node app.js

GovCloud endpoint derivation (managed CDN availability may vary):

export DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED=true
export DD_API_KEY=<govcloud-api-key>
export DD_SITE=ddog-gov.com
export DD_ENV=prod

# The SDK attempts ufc-server.ff-cdn.ddog-gov.com.
# Until that CDN is provisioned, failures preserve defaults or last-known-good state.
node app.js

System Test Evidence

Using the merged DataDog/system-tests#7315 contract, the fetch-compatible framing follow-up in DataDog/system-tests#7329, and the Node.js manifest locally enabled:

PYTEST_XDIST_AUTO_NUM_WORKERS=4 TEST_LIBRARY=nodejs ./run.sh \
  PARAMETRIC tests/parametric/test_ffe/test_configuration_sources.py -q
dd-trace-js b99ca54:       15 passed in 85.28s (0:01:25)
Mock fixture self-test:    3 passed in 2.34s
OpenFeature unit suite:    217 passed
Packaged integration:      1 passed with built-in fetch

The suite covers source selection, managed dd_env URL validation, JSON:API delivery, authentication, gzip negotiation, ETags and 304, malformed/cold/warm recovery, timeout and retry behavior, last-known-good preservation, and non-overlapping polls. Repo-native tests separately prove canonical resolved-site routing and the one-hour customer polling cap. The framing follow-up adds Content-Length to the controlled HTTP/1.1 mock because Node's built-in fetch rejects an otherwise ambiguous terminated body.

Dogfooding Evidence

The real ffe-dogfooding Node app installed the packaged local tracer and evaluated against a controlled gzip JSON:API endpoint:

dd-trace=7.0.0-pre
PROVIDER_READY after_ms=19
value=true
reason=TARGETING_MATCH
transport=built-in fetch
payload=gzip JSON:API

The app installed the packaged local tracer, sent Accept-Encoding: gzip, authenticated the controlled endpoint, loaded the configuration, and evaluated locally with TARGETING_MATCH. A direct live-staging attempt from the local Docker network timed out while connecting to the CDN, so this dogfood run proves the packaged application path but does not claim live-CDN reachability from that machine.

Additional Notes

The original JSON:API and dd_env mock contract is merged in DataDog/system-tests#7315. Standards-compliant response framing for fetch clients is tracked in draft DataDog/system-tests#7329.

@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Jul 15, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 98.35% (+0.01%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 053684d | Docs | Datadog PR Page | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 6.79 MB
Deduped: 7.45 MB
No deduping: 7.45 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.1 | 122.62 kB | 438.86 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

@pr-commenter

pr-commenter Bot commented Jul 15, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-07-18 00:55:46

Comparing candidate commit 053684d in PR branch leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source with baseline commit 76d4d45 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2319 metrics, 39 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-207.448ms; +215.553ms] or [-7.774%; +8.078%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-225.514ms; +171.774ms] or [-8.922%; +6.796%]

scenario:appsec-appsec-enabled-with-attacks-24

  • unstable execution_time [-157.941ms; +153.025ms] or [-5.107%; +4.948%]

scenario:appsec-appsec-enabled-with-attacks-26

  • unstable execution_time [-185.889ms; +190.735ms] or [-6.413%; +6.580%]

scenario:appsec-control-20

  • unstable execution_time [-116.482ms; +118.683ms] or [-7.030%; +7.163%]

scenario:appsec-control-24

  • unstable execution_time [-116.231ms; +118.510ms] or [-9.370%; +9.554%]

scenario:appsec-control-26

  • unstable execution_time [-128.683ms; +136.614ms] or [-10.347%; +10.985%]

scenario:appsec-iast-no-vulnerability-control-20

  • unstable execution_time [-10.871ms; +16.886ms] or [-4.244%; +6.593%]

scenario:appsec-iast-no-vulnerability-iast-enabled-always-active-20

  • unstable execution_time [-13999.431µs; +12326.447µs] or [-5.453%; +4.801%]

scenario:appsec-iast-with-vulnerability-control-20

  • unstable execution_time [-28.916ms; +39.768ms] or [-5.269%; +7.246%]

scenario:appsec-iast-with-vulnerability-iast-enabled-always-active-20

  • unstable execution_time [-32.289ms; +25.664ms] or [-5.843%; +4.644%]

scenario:debugger-line-probe-with-snapshot-default-26

  • unstable cpu_user_time [-2315.652ms; +754.924ms] or [-24.272%; +7.913%]
  • unstable execution_time [-2330.282ms; +759.290ms] or [-22.681%; +7.390%]
  • unstable instructions [-20.4G instructions; +6.5G instructions] or [-25.668%; +8.142%]
  • unstable throughput [-150.830op/s; +457.936op/s] or [-4.672%; +14.184%]

scenario:debugger-line-probe-with-snapshot-minimal-26

  • unstable cpu_user_time [-2671.103ms; +4185.384ms] or [-27.931%; +43.766%]
  • unstable execution_time [-2867.508ms; +4379.269ms] or [-27.813%; +42.476%]
  • unstable instructions [-23.1G instructions; +36.9G instructions] or [-29.047%; +46.335%]
  • unstable max_rss_usage [-10.127MB; +12.619MB] or [-6.346%; +7.907%]
  • unstable throughput [-831.375op/s; +563.628op/s] or [-25.781%; +17.478%]

scenario:debugger-line-probe-without-snapshot-24

  • unstable cpu_user_time [-2039.476ms; +3211.880ms] or [-24.615%; +38.765%]
  • unstable execution_time [-2187.940ms; +3386.057ms] or [-24.362%; +37.703%]
  • unstable instructions [-17.1G instructions; +27.3G instructions] or [-25.252%; +40.309%]
  • unstable max_rss_usage [-8.088MB; +13.201MB] or [-5.162%; +8.425%]
  • unstable throughput [-881.987op/s; +582.046op/s] or [-23.977%; +15.823%]

scenario:dogstatsd-aggregated-24

  • unstable execution_time [-52.121ms; +92.097ms] or [-4.741%; +8.376%]
  • unstable throughput [-917634.586op/s; +498791.702op/s] or [-6.650%; +3.615%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-270.723ms; +370.179ms] or [-5.698%; +7.792%]
  • unstable execution_time [-273.744ms; +368.063ms] or [-5.668%; +7.621%]
  • unstable throughput [-132244.868op/s; +93928.511op/s] or [-7.610%; +5.405%]

scenario:plugin-aws-sdk-extract-response-body-26

  • unstable execution_time [-83449.737µs; +84127.137µs] or [-5.419%; +5.463%]

scenario:plugin-claude-agent-sdk-compact-stream-scan-24

  • unstable cpu_usage_percentage [-9.831%; +1.337%]

scenario:plugin-graphql-long-with-depth-off-20

  • unstable max_rss_usage [-3.623MB; +11.271MB] or [-2.812%; +8.748%]

scenario:plugin-graphql-long-with-depth-on-max-20

  • unstable cpu_user_time [-707.156ms; +721.849ms] or [-5.510%; +5.624%]
  • unstable execution_time [-711.183ms; +724.740ms] or [-5.423%; +5.526%]
  • unstable throughput [-3.339op/s; +3.238op/s] or [-5.440%; +5.276%]

scenario:plugin-mongodb-core-deep-aggregate-26

  • unstable execution_time [-122.213ms; +183.533ms] or [-4.508%; +6.769%]

scenario:plugin-pg-service-26

  • unstable execution_time [-89.707ms; +35.797ms] or [-9.832%; +3.923%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-5284.427KB; +7158.094KB] or [-6.463%; +8.754%]

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.35%. Comparing base (76d4d45) to head (053684d).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9397      +/-   ##
==========================================
+ Coverage   96.90%   98.35%   +1.44%     
==========================================
  Files         923      927       +4     
  Lines      123011   123669     +658     
  Branches    21249    10629   -10620     
==========================================
+ Hits       119209   121637    +2428     
+ Misses       3802     2032    -1770     
Flag Coverage Δ
aiguard 58.22% <59.09%> (+4.52%) ⬆️
aiguard-integration 57.07% <55.90%> (-0.03%) ⬇️
apm-bucket-0 58.48% <59.09%> (+4.56%) ⬆️
apm-bucket-1 64.56% <59.09%> (+5.66%) ⬆️
apm-bucket-2 63.61% <59.09%> (+5.48%) ⬆️
apm-bucket-3 60.92% <59.09%> (+5.41%) ⬆️
apm-capabilities-tracing 62.30% <59.09%> (+1.64%) ⬆️
apm-integrations-aerospike 57.61% <59.09%> (+4.54%) ⬆️
apm-integrations-confluentinc-kafka-javascript 62.47% <59.09%> (+5.31%) ⬆️
apm-integrations-couchbase 57.89% <59.09%> (+4.49%) ⬆️
apm-integrations-http 63.60% <59.09%> (+5.53%) ⬆️
apm-integrations-kafkajs 63.06% <59.09%> (+5.20%) ⬆️
apm-integrations-next 60.01% <59.09%> (+5.71%) ⬆️
apm-integrations-prisma 59.44% <59.09%> (+5.15%) ⬆️
appsec 73.87% <59.09%> (+4.93%) ⬆️
appsec-express_fastify_graphql 71.47% <59.09%> (+5.71%) ⬆️
appsec-integration 51.91% <55.90%> (+<0.01%) ⬆️
appsec-kafka_ldapjs_lodash 64.79% <59.09%> (+5.58%) ⬆️
appsec-mongodb-core_mongoose_mysql 68.50% <59.09%> (+6.07%) ⬆️
appsec-next 58.18% <59.09%> (+5.30%) ⬆️
appsec-node-serialize_passport_postgres 68.19% <59.09%> (+6.12%) ⬆️
appsec-sourcing_stripe_template 66.51% <59.09%> (+6.04%) ⬆️
debugger 65.82% <59.09%> (+0.11%) ⬆️
instrumentations-bucket-0 52.42% <59.09%> (+3.51%) ⬆️
instrumentations-bucket-1 61.10% <59.09%> (+5.98%) ⬆️
instrumentations-bucket-10 62.95% <59.09%> (+6.21%) ⬆️
instrumentations-bucket-11 52.43% <59.09%> (+3.52%) ⬆️
instrumentations-bucket-12 52.88% <59.09%> (+3.79%) ⬆️
instrumentations-bucket-13 52.38% <59.09%> (+3.53%) ⬆️
instrumentations-bucket-2 54.34% <59.09%> (+4.28%) ⬆️
instrumentations-bucket-3 60.07% <59.09%> (+6.05%) ⬆️
instrumentations-bucket-4 53.01% <59.09%> (+3.68%) ⬆️
instrumentations-bucket-5 58.32% <59.09%> (+5.14%) ⬆️
instrumentations-bucket-6 61.66% <59.09%> (+5.89%) ⬆️
instrumentations-bucket-7 59.22% <59.09%> (+5.54%) ⬆️
instrumentations-bucket-8 60.48% <59.09%> (+5.68%) ⬆️
instrumentations-bucket-9 62.33% <59.09%> (+6.11%) ⬆️
instrumentations-instrumentation-couchbase 51.95% <59.09%> (+3.63%) ⬆️
instrumentations-integration-esbuild 34.07% <51.51%> (+0.01%) ⬆️
llmobs-ai_anthropic_bedrock 63.29% <59.09%> (+5.48%) ⬆️
llmobs-bucket-1 62.59% <59.09%> (+5.45%) ⬆️
llmobs-openai 63.26% <59.09%> (+5.72%) ⬆️
llmobs-sdk 65.36% <59.09%> (+5.25%) ⬆️
llmobs-vertex-ai 59.96% <59.09%> (+5.61%) ⬆️
master-coverage 98.35% <100.00%> (?)
openfeature 55.41% <78.51%> (+0.69%) ⬆️
openfeature-unit 54.21% <100.00%> (+4.23%) ⬆️
platform-core_esbuild_instrumentations-misc 40.72% <59.09%> (+2.17%) ⬆️
platform-integration 62.28% <55.90%> (-0.04%) ⬇️
platform-shimmer_unit-guardrails_webpack 39.40% <59.09%> (+2.06%) ⬆️
plugins-bucket-0 57.76% <59.09%> (+4.46%) ⬆️
plugins-bucket-1 55.16% <52.72%> (-0.03%) ⬇️
plugins-bucket-11 62.70% <59.09%> (+5.03%) ⬆️
plugins-bucket-17 62.74% <59.09%> (?)
plugins-bucket-18 63.56% <59.09%> (+6.37%) ⬆️
plugins-bucket-19 62.50% <59.09%> (+7.00%) ⬆️
plugins-bucket-20 66.75% <59.09%> (+9.04%) ⬆️
plugins-bucket-4 59.38% <59.09%> (+5.47%) ⬆️
plugins-bullmq_cassandra_cookie 62.65% <59.09%> (+5.14%) ⬆️
plugins-cookie-parser_crypto_dd-trace-api 57.56% <59.09%> (+5.10%) ⬆️
plugins-fetch_fs_generic-pool 59.66% <59.09%> (+5.03%) ⬆️
plugins-google-cloud-pubsub_grpc_handlebars 65.63% <59.09%> (+5.67%) ⬆️
plugins-hapi_hono_ioredis 61.08% <59.09%> (+5.19%) ⬆️
plugins-jest_knex_langgraph ?
plugins-jest_langgraph_ldapjs 56.33% <59.09%> (?)
plugins-ldapjs_light-my-request_limitd-client ?
plugins-light-my-request_limitd-client_lodash 59.47% <59.09%> (?)
plugins-lodash_mariadb_memcached ?
plugins-mariadb_memcached_mercurius 62.38% <59.09%> (?)
plugins-moleculer_mongodb_mongodb-core ?
plugins-mongodb_mongodb-core_mongoose 60.52% <59.09%> (?)
plugins-mongoose_multer_mysql ?
plugins-multer_mysql_mysql2 59.25% <59.09%> (?)
plugins-mysql2_nats_node-serialize ?
plugins-nats_node-serialize_opensearch 61.67% <59.09%> (?)
plugins-opensearch_passport-http_pino ?
plugins-passport-http_pino_postgres 59.49% <59.09%> (?)
plugins-postgres_process_pug ?
plugins-process_pug_redis 58.51% <59.09%> (?)
plugins-redis_router_sequelize ?
plugins-test-and-upstream-rhea_undici_url ?
plugins-undici_url_valkey 59.36% <59.09%> (?)
plugins-valkey_vm_winston ?
plugins-vm_winston_ws 60.79% <59.09%> (?)
plugins-ws ?
profiling 63.05% <59.09%> (+4.76%) ⬆️
serverless-aws-sdk-aws-sdk 55.72% <59.09%> (+4.92%) ⬆️
serverless-aws-sdk-bedrockruntime 55.44% <59.09%> (+4.66%) ⬆️
serverless-aws-sdk-client 57.17% <59.09%> (+5.04%) ⬆️
serverless-aws-sdk-dynamodb 56.38% <59.09%> (+4.54%) ⬆️
serverless-aws-sdk-eventbridge 49.97% <59.09%> (+3.48%) ⬆️
serverless-aws-sdk-kinesis 60.13% <59.09%> (+5.31%) ⬆️
serverless-aws-sdk-lambda 58.13% <59.09%> (+5.23%) ⬆️
serverless-aws-sdk-s3 56.31% <59.09%> (+4.68%) ⬆️
serverless-aws-sdk-serverless-peer-service 60.55% <59.09%> (+5.70%) ⬆️
serverless-aws-sdk-sns 60.95% <59.09%> (+5.37%) ⬆️
serverless-aws-sdk-sqs 61.38% <59.09%> (+5.32%) ⬆️
serverless-aws-sdk-stepfunctions 56.29% <59.09%> (+4.90%) ⬆️
serverless-aws-sdk-util 52.22% <59.09%> (+3.64%) ⬆️
serverless-bucket-0 55.21% <55.90%> (-0.02%) ⬇️
serverless-bucket-1 60.08% <59.09%> (+3.98%) ⬆️
test-optimization-cucumber 73.01% <78.78%> (+0.04%) ⬆️
test-optimization-cypress 66.42% <72.72%> (+0.07%) ⬆️
test-optimization-jest 74.36% <56.81%> (-0.06%) ⬇️
test-optimization-mocha 74.64% <56.81%> (-0.05%) ⬇️
test-optimization-playwright-playwright-atr 61.43% <72.72%> (-0.01%) ⬇️
test-optimization-playwright-playwright-efd 61.63% <72.72%> (-0.01%) ⬇️
test-optimization-playwright-playwright-final-status 61.60% <72.72%> (-0.01%) ⬇️
test-optimization-playwright-playwright-impacted-tests 61.32% <72.72%> (-0.02%) ⬇️
test-optimization-playwright-playwright-reporting 61.22% <72.72%> (+0.06%) ⬆️
test-optimization-playwright-playwright-test-management 61.94% <72.72%> (-0.28%) ⬇️
test-optimization-playwright-playwright-test-span 61.35% <72.72%> (-0.06%) ⬇️
test-optimization-selenium 60.72% <72.72%> (-0.18%) ⬇️
test-optimization-testopt 59.19% <52.72%> (+0.08%) ⬆️
test-optimization-vitest 71.39% <78.78%> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@leoromanovsky leoromanovsky changed the title Add agentless Feature Flagging configuration source feat(openfeature): add agentless Feature Flagging configuration source Jul 16, 2026
@leoromanovsky
leoromanovsky force-pushed the leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source branch from 3f66acc to a363424 Compare July 16, 2026 12:33
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.
@leoromanovsky
leoromanovsky force-pushed the leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source branch from bb4469e to b99ca54 Compare July 17, 2026 03:33
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.
@leoromanovsky
leoromanovsky force-pushed the leo.romanovsky/ffl-2697-nodejs-agentless-configuration-source branch from b99ca54 to ac9b55d Compare July 17, 2026 03:50
@leoromanovsky
leoromanovsky marked this pull request as ready for review July 17, 2026 04:06
@leoromanovsky
leoromanovsky requested review from a team as code owners July 17, 2026 04:06
@leoromanovsky
leoromanovsky requested review from BridgeAR, pavlokhrebto and sameerank and removed request for a team July 17, 2026 04:06

Copilot AI 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.

Pull request overview

Adds an agentless (CDN) Universal Flag Configuration delivery path for the existing OpenFeature flagging provider in dd-trace, making agentless the default configuration source while keeping Agent Remote Config as an explicit opt-in.

Changes:

  • Introduces a configuration-source resolver (agentless default vs remote_config opt-in) and wires it into OpenFeature feature registration/lifecycle.
  • Implements an agentless poller using built-in fetch (ETags/304, gzip, retries, timeouts, no overlap, shutdown) and attaches it to the provider.
  • Adds config/env-var plumbing + TypeScript type updates and broad unit/integration test coverage for both delivery modes.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/dd-trace/test/openfeature/remote_config.spec.js Adds coverage for capability advertisement without handler registration when agentless is selected.
packages/dd-trace/test/openfeature/register.spec.js Verifies configuration-source enablement is wired into OpenFeature registration and RC subscription is conditional.
packages/dd-trace/test/openfeature/flagging_provider.spec.js Adds provider lifecycle tests for attaching/stopping a configuration source and duplicate attachment behavior.
packages/dd-trace/test/openfeature/configuration_source.spec.js New unit tests for source selection normalization, endpoint derivation, GovCloud routing, URL validation, and timing bounds.
packages/dd-trace/test/openfeature/agentless_configuration_source.spec.js New unit tests for the agentless poller (gzip, JSON:API validation, ETags, retries/timeouts, no overlap, shutdown).
packages/dd-trace/test/config/index.spec.js Verifies default config values and new env var parsing for Feature Flagging configuration source.
packages/dd-trace/src/openfeature/remote_config.js Adds a subscribe switch so RC delivery is only installed when explicitly selected.
packages/dd-trace/src/openfeature/register.js Wires configuration-source enablement into OpenFeature startup and passes source selection into RC integration.
packages/dd-trace/src/openfeature/flagging_provider.js Adds configuration-source lifecycle attachment and shutdown handling to the provider.
packages/dd-trace/src/openfeature/configuration_source.js New resolver/enable module for selecting and starting the appropriate configuration delivery source.
packages/dd-trace/src/openfeature/agentless_configuration_source.js New agentless polling implementation using built-in fetch with strict JSON:API parsing and robust polling controls.
packages/dd-trace/src/config/supported-configurations.json Adds new env vars + defaults for selecting/configuring agentless FF config delivery.
packages/dd-trace/src/config/generated-config-types.d.ts Updates generated config typings for the new env vars and flaggingProvider fields.
integration-tests/openfeature/openfeature-exposure-events.spec.js Pins integration tests to remote_config source to preserve existing exposure path behavior.
integration-tests/openfeature/openfeature-agentless.spec.js New integration test validating agentless delivery and local evaluation against a controlled endpoint.
integration-tests/openfeature/app/agentless-evaluation.js Sandbox app used by the new agentless integration test.
index.d.v5.ts Documents and types new flaggingProvider options for v5 surface (agentless/remote_config + agentless tuning).
index.d.ts Documents and types new flaggingProvider options for current surface (agentless/remote_config + agentless tuning).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/dd-trace/src/openfeature/configuration_source.js
sameerank
sameerank previously approved these changes Jul 17, 2026

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

Nothing blocking from me and looks aligned overall with our design docs. The questions about signing and request parameters can be worked on later if needed

Comment thread packages/dd-trace/src/openfeature/agentless_configuration_source.js
Comment thread packages/dd-trace/src/openfeature/configuration_source.js
Comment thread packages/dd-trace/src/openfeature/configuration_source.js

@BridgeAR BridgeAR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just had a brief glimpse at the config

"default": "agentless"
}
],
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL": [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe that endpoint transports keys, so we have to mark this as sensitive, otherwise it transports telemetry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This particular configuration only creates a base URL; I dug into it and am not sure yet, as it does not explicitly contain headers or URL parameters. Is that still something you consider sensitive?

Comment thread packages/dd-trace/src/config/supported-configurations.json Outdated
Comment on lines +14 to +19
function getClientLibraryHeaders (language = LANGUAGE, version = VERSION) {
return {
'DD-Client-Library-Language': language,
'DD-Client-Library-Version': version,
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked the other tracer implementations: Go, Java, Python, Ruby, and .NET telemetry all use DD-Client-Library-Language and DD-Client-Library-Version.

HTTP field names are case-insensitive per RFC 9110, so the previous lowercase JS spelling was functionally equivalent.

Still, matching the established cross-SDK spelling lets us use one canonical helper for both telemetry and the new agentless UFC request.

I’ve updated the implementation accordingly and covered both request paths with tests. Datadog-Meta-* remains the trace-submission header family; the UFC request uses DD-Client-Library-*.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants