From 9d1e29ee2cb9f187bbb64d705faf2617fa76687a Mon Sep 17 00:00:00 2001 From: Helen Han Date: Mon, 2 Nov 2026 14:05:08 -0500 Subject: [PATCH] chore(openfeature): prepare evaluator-gated dependent flag capabilities Gate configuration negotiation on evaluation and exposure support; the current evaluator remains opted out. Environment: Datadog workspace Co-Authored-By: Codex GPT-6 --- components-rs/ffe.rs | 8 ++++++ components-rs/remote_config.rs | 45 ++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/components-rs/ffe.rs b/components-rs/ffe.rs index a576d9aa85..c32559dc8a 100644 --- a/components-rs/ffe.rs +++ b/components-rs/ffe.rs @@ -9,6 +9,14 @@ use std::collections::HashMap; use std::sync::Arc; use tracing::debug; +pub(crate) const DEPENDENT_FLAG_VARIANT_KEY_V1: &str = "dependent-flag-variant-key-v1"; + +// Opt-in requires support in both the linked evaluator and the PHP binding's +// error propagation and exposure reporting. Declaring an RC enum is insufficient. +pub(crate) fn evaluator_capabilities() -> &'static [&'static str] { + &[] +} + struct FfeState { config: Option, version: u64, diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs index 199b9e1e44..a29e550489 100644 --- a/components-rs/remote_config.rs +++ b/components-rs/remote_config.rs @@ -133,6 +133,14 @@ pub struct RemoteConfigFlags { pub ffe_enabled: bool, } +fn ffe_capabilities(evaluator_capabilities: &[&str]) -> Vec { + let mut capabilities = vec![RemoteConfigCapabilities::FfeFlagConfigurationRules]; + if evaluator_capabilities.contains(&crate::ffe::DEPENDENT_FLAG_VARIANT_KEY_V1) { + capabilities.push(RemoteConfigCapabilities::FfeFlagDependentFlagVariantKeyV1); + } + capabilities +} + #[no_mangle] #[allow(static_mut_refs)] pub unsafe extern "C" fn ddog_init_remote_config(flags: RemoteConfigFlags) { @@ -164,8 +172,9 @@ pub unsafe extern "C" fn ddog_init_remote_config(flags: RemoteConfigFlags) { if ffe_enabled { DATADOG_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::FfeFlags); - DATADOG_REMOTE_CONFIG_CAPABILITIES - .push(RemoteConfigCapabilities::FfeFlagConfigurationRules); + for capability in ffe_capabilities(crate::ffe::evaluator_capabilities()) { + DATADOG_REMOTE_CONFIG_CAPABILITIES.push(capability); + } } if live_debugging_enabled { @@ -938,3 +947,35 @@ pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>( ddog_sidecar_send_debugger_diagnostics(transport, payload) } + +#[cfg(test)] +mod capability_tests { + use super::*; + + #[test] + fn dependent_flags_require_explicit_evaluator_support() { + for (reported, expected) in [ + (vec![], vec![46]), + (vec!["unknown-v1"], vec![46]), + (vec!["dependent-flag-variant-key-v1"], vec![46, 50]), + ( + vec!["unknown-v1", "dependent-flag-variant-key-v1"], + vec![46, 50], + ), + ] { + let actual: Vec<_> = ffe_capabilities(&reported) + .into_iter() + .map(|capability| capability as u32) + .collect(); + assert_eq!(actual, expected); + } + } + + #[test] + fn linked_evaluator_does_not_advertise_dependent_flags() { + assert!(crate::ffe::evaluator_capabilities().is_empty()); + let capabilities = ffe_capabilities(crate::ffe::evaluator_capabilities()); + assert_eq!(capabilities.len(), 1); + assert_eq!(capabilities[0] as u32, 46); + } +}