Skip to content

Add disabled offline Feature Flagging contract#7305

Open
leoromanovsky wants to merge 6 commits into
mainfrom
agent/ffe-offline-contract
Open

Add disabled offline Feature Flagging contract#7305
leoromanovsky wants to merge 6 commits into
mainfrom
agent/ffe-offline-contract

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Motivation

Define the cross-SDK contract for startup-only offline Feature Flagging on main before each SDK implementation exists. Offline is not a cached agentless startup: the application supplies UFC JSON bytes when it creates the provider, evaluations must be ready immediately, and Feature Flagging must not activate either network configuration source.

Today, system-tests can deliver the canonical UFC fixtures through Remote Configuration or the mocked agentless HTTP endpoint, but it has no cross-SDK path for passing raw UFC bytes into provider construction. That leaves offline initialization, immediate readiness, immutability, and network isolation untestable.

flowchart LR
  fixtures["Canonical UFC fixtures"]

  subgraph available["Capabilities available before this PR"]
    rc["Inject UFC through Remote Configuration"]
    agentless["Serve UFC through the agentless mock endpoint"]
    async["Run evaluation assertions after asynchronous readiness"]
    rc --> async
    agentless --> async
  end

  subgraph missing["Missing offline capability"]
    noinput["No parametric input for UFC startup bytes"]
    noinit["Cannot construct a provider in offline mode"]
    noproof["Cannot prove immediate readiness, immutability, or FFE network isolation"]
    noinput --> noinit --> noproof
  end

  fixtures --> rc
  fixtures --> agentless
  fixtures -.-> noinput
Loading

The tests remain disabled in every SDK manifest. The stacked Java reference in DataDog/dd-trace-java#11933 exists only to prove that this contract and the harness work end to end before SDK owners implement it.

Changes

  • share the canonical UFC fixture bytes and evaluation runner across Remote Configuration, agentless, and offline delivery
  • let parametric applications receive base64-encoded UFC JSON bytes through /ffe/start
  • add 23 canonical evaluation fixture groups plus fail-closed empty/malformed startup and source-isolation coverage
  • make offline evaluations assert readiness on the first call instead of using the retry allowance needed by asynchronous sources
  • inject a conflicting FFE_FLAGS Remote Configuration payload and prove that it cannot replace the startup bytes
  • verify every observed RC request omits the FFE_FLAGS product and capability, while allowing unrelated tracer RC traffic or no RC traffic at all
  • keep the timed network-isolation observation in the three startup/isolation cases instead of repeating it for all 23 semantic fixture groups
  • keep framework fixture constants under utils so framework code does not import the parametric test package
  • document the additional dd-openfeature artifact required for local Java provider testing
  • register the contract as parity feature #562: Offline init
  • add disabled entries for every SDK manifest and tag each v1 implementation with its existing FFL ticket; C++ and Rust are marked irrelevant because they are outside the v1 server SDK scope
flowchart TB
  before["Delivery-specific fixture loading and evaluation logic"]
  shared["Shared UFC fixture bytes and evaluation runner"]
  before --> shared

  subgraph delivery["Configuration source adapters"]
    rc["Remote Configuration injection"]
    agentless["Agentless mock HTTP endpoint"]
    offline["Offline bytes sent through ffe start"]
  end

  shared --> rc
  shared --> agentless
  shared --> offline

  rc --> retry["Asynchronous readiness retry"]
  agentless --> retry
  offline --> immediate["First evaluation must be ready"]
  retry --> semantics["Canonical UFC and OpenFeature assertions"]
  immediate --> semantics

  offline --> failed["Empty or malformed bytes fail closed"]
  offline --> immutable["Conflicting FFE RC cannot replace startup configuration"]
  offline --> isolated["No agentless request and no FFE RC subscription"]

  semantics --> feature["Parity feature 562 Offline init"]
  failed --> feature
  immutable --> feature
  isolated --> feature
  feature --> manifests["All SDK manifests stay disabled until native implementations land"]
Loading

Tests added

The file contributes 26 pytest items. Each canonical fixture group can contain multiple flag-evaluation assertions.

Test Count What the test does Contract it proves
test_offline_configuration_evaluates_fixture 23 Starts the provider with the canonical UFC bytes, then runs every existing evaluation fixture (types, targeting, allocation, operators, disabled/missing flags, dates, regex, JSON, and special values). Startup succeeds; the very first evaluation is ready without retry; UFC/OpenFeature results match the other delivery modes; no agentless/CDN request occurs.
test_invalid_startup_configuration_fails_closed[empty] 1 Starts with zero bytes. Startup fails closed and neither the agentless source nor FFE RC becomes active.
test_invalid_startup_configuration_fails_closed[malformed] 1 Starts with malformed UFC JSON. Startup fails closed and neither the agentless source nor FFE RC becomes active.
test_offline_configuration_is_immutable_and_network_isolated 1 Starts with a fixture that evaluates to green, injects a conflicting RC fixture whose same variation evaluates to replacement, observes both network surfaces, then evaluates again. The result remains green; the agentless endpoint receives zero requests; all observed RC requests omit FFE_FLAGS and its capability. Unrelated tracer RC polling is permitted.

Decisions

  • offline reuses evaluation fixtures and UFC/OpenFeature semantics, but not agentless HTTP retry, ETag, or cold/warm cache scenarios
  • offline configuration is available synchronously at provider startup and immutable for the life of that provider
  • network isolation is scoped to the FFE configuration source; an unrelated tracer Remote Configuration poller may still run
  • zero RC requests is valid; the test inspects all requests that do occur instead of waiting for an unrelated capability request
  • this PR does not enable any SDK; each implementation gets a small follow-up manifest PR after its native and provisional cross-repo validation is green
flowchart LR
  A["system-tests main"] --> B["This draft offline contract with all manifests disabled"]
  C["dd-trace-java PR 11892 source selection"] --> D["dd-trace-java PR 11933 offline reference"]
  B --> D
  D --> E["Provisional Java proof with 26 passing tests"]
  B --> F["SDK implementations"]
  F --> G["Small per-SDK manifest enablement PRs"]
Loading

Validation

  • ./format.sh — passed mypy, Ruff, YAML formatting/lint, parser checks, ShellCheck, and Node.js linters

  • ./run.sh TEST_THE_TEST tests/test_the_test/test_conventions.py tests/test_the_test/test_mock_ffe_agentless_backend.py tests/test_the_test/test_features.py tests/test_the_test/test_manifest.py -q29 passed, 1 deselected

  • with locally built artifacts matching DataDog/dd-trace-java#11933 head 8d654e23b7:

    TEST_LIBRARY=java ./run.sh PARAMETRIC --skip-parametric-build \
      -F tests/parametric/test_ffe/test_offline_configuration.py \
      tests/parametric/test_ffe/test_offline_configuration.py

    Result: 26 passed in 32.29s.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

tests/parametric/test_ffe/test_offline_configuration.py                 @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core
docs/execute/binaries.md                                                @DataDog/system-tests-core
manifests/cpp.yml                                                       @DataDog/dd-trace-cpp
manifests/dotnet.yml                                                    @DataDog/apm-dotnet @DataDog/asm-dotnet
manifests/golang.yml                                                    @DataDog/dd-trace-go-guild
manifests/java.yml                                                      @DataDog/asm-java @DataDog/apm-java
manifests/nodejs.yml                                                    @DataDog/dd-trace-js
manifests/php.yml                                                       @DataDog/apm-php @DataDog/asm-php
manifests/python.yml                                                    @DataDog/apm-python @DataDog/asm-python
manifests/ruby.yml                                                      @DataDog/ruby-guild @DataDog/asm-ruby
manifests/rust.yml                                                      @DataDog/apm-rust
tests/parametric/test_ffe/test_configuration_sources.py                 @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core
tests/parametric/test_ffe/test_dynamic_evaluation.py                    @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core
tests/parametric/test_ffe/utils.py                                      @DataDog/feature-flagging-and-experimentation-sdk @DataDog/system-tests-core
utils/_features.py                                                      @DataDog/system-tests-core
utils/build/docker/java/parametric/src/main/java/com/datadoghq/trace/controller/FeatureFlagEvaluatorController.java  @DataDog/apm-java @DataDog/asm-java @DataDog/system-tests-core
utils/docker_fixtures/_test_clients/_test_client_parametric.py          @DataDog/system-tests-core

@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jul 13, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 2 Pipeline jobs failed

Testing the test | System Tests (python, prod) / parametric / parametric (1)   View in Datadog   GitHub Actions

🧪 1 Test failed

tests.parametric.test_telemetry.Test_TelemetrySSIConfigs.test_instrumentation_source_non_ssi[library_env0, parametric-python] from system_tests_suite   View in Datadog
AssertionError: No configuration found for any of instrumentation_source
assert None is not None

self = <tests.parametric.test_telemetry.Test_TelemetrySSIConfigs object at 0x7fbe4bd8b560>
test_agent = <utils.docker_fixtures._test_agent.TestAgentAPI object at 0x7fbe4b9790d0>
test_library = <utils.docker_fixtures._test_clients._test_client_parametric.ParametricTestClientApi object at 0x7fbe4b48cd40>

    @pytest.mark.parametrize("library_env", [{**DEFAULT_ENVVARS, "DD_SERVICE": "service_test"}])
    def test_instrumentation_source_non_ssi(self, test_agent: TestAgentAPI, test_library: APMLibrary):
        # Some libraries require a first span for telemetry to be emitted.
...

Testing the test | all-jobs-are-green   View in Datadog   GitHub Actions

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

Useful? React with 👍 / 👎

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

@leoromanovsky leoromanovsky marked this pull request as ready for review July 15, 2026 13:59
@leoromanovsky leoromanovsky requested review from a team as code owners July 15, 2026 13:59
@leoromanovsky leoromanovsky requested review from christophe-papazian, gyuheon0h and jandro996 and removed request for a team July 15, 2026 13:59
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.

1 participant