From e9e88caf2aad484ffcc3dd0071a48b9b4d6a73e0 Mon Sep 17 00:00:00 2001 From: Vickie Boettcher Date: Fri, 23 Oct 2026 02:56:25 +0000 Subject: [PATCH] fix(ffe): adopt sidecar application identity for PHP evaluations Update libdatadog to the FFL-3438 implementation and require application attribution in native and OpenFeature wire tests. Keep per-evaluation FFI context empty and align required inherited dependencies and generated bindings. Co-Authored-By: Codex --- Cargo.lock | 32 +++++++++++++--- Cargo.toml | 5 ++- components-rs/crashtracker.h | 28 -------------- libdatadog | 2 +- .../FeatureFlags/flag_evaluation_wire.inc | 15 +++++--- .../flag_evaluation_openfeature_wire.phpt | 2 +- .../FeatureFlags/flag_evaluation_wire.phpt | 2 +- tests/ext/ffe/flag_evaluation_identity.phpt | 38 ++++++++++++++----- .../stubs/flag_evaluation_identity_app.php | 18 ++++++--- tracer/ffe.c | 4 +- 10 files changed, 86 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c217522482..75ca3ab1f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2802,6 +2802,8 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tower-service", + "webpki-root-certs", + "webpki-roots", "windows-sys 0.52.0", ] @@ -2981,12 +2983,19 @@ dependencies = [ name = "libdd-ffe" version = "2.0.0" dependencies = [ + "base64 0.22.1", "bincode", "chrono", "derive_more", + "fastrand", "faststr", + "flate2", + "futures-util", "http 1.4.2", + "http-body-util", + "httpdate", "httpmock", + "hyper", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", @@ -2995,6 +3004,7 @@ dependencies = [ "log", "lru", "md5", + "percent-encoding", "prost", "pyo3", "semver", @@ -3005,6 +3015,8 @@ dependencies = [ "strum_macros", "thiserror 2.0.18", "tokio", + "tokio-util", + "url", ] [[package]] @@ -3305,6 +3317,7 @@ name = "libdd-tinybytes" version = "1.1.5" dependencies = [ "libdd-tinybytes", + "libdd-trace-model", "once_cell", "pretty_assertions", "proptest", @@ -3314,6 +3327,10 @@ dependencies = [ "test-case", ] +[[package]] +name = "libdd-trace-model" +version = "0.1.0" + [[package]] name = "libdd-trace-normalization" version = "4.2.0" @@ -3336,6 +3353,7 @@ dependencies = [ "fluent-uri", "libdd-common", "libdd-tinybytes", + "libdd-trace-model", "libdd-trace-protobuf", "libdd-trace-utils", "log", @@ -3393,6 +3411,7 @@ dependencies = [ name = "libdd-trace-utils" version = "14.0.0" dependencies = [ + "allocator-api2", "anyhow", "base64 0.22.1", "bolero", @@ -3417,6 +3436,7 @@ dependencies = [ "libdd-capabilities-impl", "libdd-common", "libdd-tinybytes", + "libdd-trace-model", "libdd-trace-normalization", "libdd-trace-protobuf", "libdd-trace-utils", @@ -4998,9 +5018,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -6558,18 +6578,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index afd72d2309..6b60c8ed40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -219,6 +219,7 @@ hex = { version = "0.4", default-features = false } http = { version = "1.1", default-features = false } http-body = { version = "1", default-features = false } http-body-util = { version = "0.1", default-features = false } +httpdate = { version = "1.0", default-features = false } # allow(workspace-deps-features): httpmock 0.8.0-alpha.1 does not compile # without the `cookies` feature: its own `readers.rs` calls `req.cookies()` # unconditionally while that method is gated behind `cookies`. Keep it as a @@ -286,7 +287,7 @@ rmpv = { version = "1.3.0", default-features = false } rustc-hash = { version = "2.1.1", default-features = false } rustls = { version = "0.23", default-features = false } rustls-platform-verifier = { version = "0.6", default-features = false } -rustls-webpki = { version = ">=0.103.13", default-features = false } +rustls-webpki = { version = "0.103.16", default-features = false } schemars = { version = "0.8.21", default-features = false } semver = { version = "1.0", default-features = false } serde = { version = "1.0", default-features = false } @@ -343,6 +344,8 @@ walkdir = { version = "2.4", default-features = false } wasm-bindgen-futures = { version = "0.4", default-features = false } wasm-bindgen-test = { version = "=0.3.50", default-features = false } web-time = { version = "1", default-features = false } +webpki-root-certs = { version = "1.0.10", default-features = false } +webpki-roots = { version = "1.0.10", default-features = false } windows = { version = "0.59.0", default-features = false } windows-sys = { version = "0.52", default-features = false } winver = { version = "1.0.0", default-features = false } diff --git a/components-rs/crashtracker.h b/components-rs/crashtracker.h index d7110398ea..ddb891bb50 100644 --- a/components-rs/crashtracker.h +++ b/components-rs/crashtracker.h @@ -407,34 +407,6 @@ bool ddog_crasht_init_windows(ddog_CharSlice module, struct ddog_crasht_Metadata metadata); #endif -#if (defined(_CRASHTRACKING_COLLECTOR) && defined(_WIN32)) -HRESULT OutOfProcessExceptionEventSignatureCallback(const void *_context, - const WER_RUNTIME_EXCEPTION_INFORMATION *_exception_information, - int32_t _index, - uint16_t *_name, - uint32_t *_name_size, - uint16_t *_value, - uint32_t *_value_size); -#endif - -#if (defined(_CRASHTRACKING_COLLECTOR) && defined(_WIN32)) -HRESULT OutOfProcessExceptionEventDebuggerLaunchCallback(const void *_context, - const WER_RUNTIME_EXCEPTION_INFORMATION *_exception_information, - BOOL *_is_custom_debugger, - uint16_t *_debugger_launch, - uint32_t *_debugger_launch_size, - BOOL *_is_debugger_auto_launch); -#endif - -#if (defined(_CRASHTRACKING_COLLECTOR) && defined(_WIN32)) -HRESULT OutOfProcessExceptionEventCallback(const void *context, - const WER_RUNTIME_EXCEPTION_INFORMATION *exception_information, - BOOL *_ownership_claimed, - uint16_t *_event_name, - uint32_t *_size, - uint32_t *_signature_count); -#endif - /** * # Safety * The `crash_info` can be null, but if non-null it must point to a Builder made by this module, diff --git a/libdatadog b/libdatadog index 634af2c847..58b738075f 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 634af2c847b1d0b7f6f89dff4f715a7828da93c4 +Subproject commit 58b738075f440d4a1d94ea49a545a0fac2e52e2c diff --git a/tests/Common/FeatureFlags/flag_evaluation_wire.inc b/tests/Common/FeatureFlags/flag_evaluation_wire.inc index 08ef6b7f63..b3a25cf275 100644 --- a/tests/Common/FeatureFlags/flag_evaluation_wire.inc +++ b/tests/Common/FeatureFlags/flag_evaluation_wire.inc @@ -77,7 +77,7 @@ try { $rows = array(); $exposureCount = 0; $identity = true; - $evaluationIdentityAbsent = true; + $evaluationIdentity = true; $protectedBytesAbsent = true; foreach ($captures as $capture) { list($request, $headers, $payload) = $capture; @@ -95,10 +95,15 @@ try { if (strpos($capture[3], 'ffe-error-pii-canary@example.test') !== false) { throw new RuntimeException('raw error text reached the payload'); } - // Application attribution is deferred to FFL-3438/FFL-3439. - $evaluationIdentityAbsent = $evaluationIdentityAbsent && $payload['context'] === array(); + $context = $payload['context']; + ksort($context); + $evaluationIdentity = $evaluationIdentity + && $context === array('env' => 'test', 'service' => 'php-ffe-wire', 'version' => 'wire-version'); foreach ($payload['flagEvaluations'] as $row) { - $evaluationIdentityAbsent = $evaluationIdentityAbsent && !isset($row['context']['dd']); + // Environment and application version belong only on the batch. + $evaluationIdentity = $evaluationIdentity + && isset($row['context']['dd']) + && $row['context']['dd'] === array('service' => 'php-ffe-wire'); $key = $row['flag']['key']; if (isset($rows[$key])) { $rows[$key]['evaluation_count'] += $row['evaluation_count']; @@ -120,7 +125,7 @@ try { } echo 'exposure_service=php-ffe-wire', "\n"; echo 'identity='; var_dump($identity); - echo 'evaluation_application_identity_absent='; var_dump($evaluationIdentityAbsent); + echo 'evaluation_application_identity='; var_dump($evaluationIdentity); echo 'exposures='; var_dump($exposureCount); echo 'consented_count='; var_dump($rows['consented']['evaluation_count']); echo 'consented_subject='; var_dump($rows['consented']['targeting_key']); diff --git a/tests/OpenFeature/flag_evaluation_openfeature_wire.phpt b/tests/OpenFeature/flag_evaluation_openfeature_wire.phpt index 042390095d..bd98af77ac 100644 --- a/tests/OpenFeature/flag_evaluation_openfeature_wire.phpt +++ b/tests/OpenFeature/flag_evaluation_openfeature_wire.phpt @@ -25,7 +25,7 @@ require __DIR__ . '/../Common/FeatureFlags/flag_evaluation_wire.inc'; --EXPECT-- exposure_service=php-ffe-wire identity=bool(true) -evaluation_application_identity_absent=bool(true) +evaluation_application_identity=bool(true) exposures=int(2) consented_count=int(2) consented_subject=string(7) "subject" diff --git a/tests/api/Unit/FeatureFlags/flag_evaluation_wire.phpt b/tests/api/Unit/FeatureFlags/flag_evaluation_wire.phpt index 264b0b8682..54f765e965 100644 --- a/tests/api/Unit/FeatureFlags/flag_evaluation_wire.phpt +++ b/tests/api/Unit/FeatureFlags/flag_evaluation_wire.phpt @@ -23,7 +23,7 @@ require __DIR__ . '/../../../Common/FeatureFlags/flag_evaluation_wire.inc'; --EXPECT-- exposure_service=php-ffe-wire identity=bool(true) -evaluation_application_identity_absent=bool(true) +evaluation_application_identity=bool(true) exposures=int(2) consented_count=int(2) consented_subject=string(7) "subject" diff --git a/tests/ext/ffe/flag_evaluation_identity.phpt b/tests/ext/ffe/flag_evaluation_identity.phpt index 121281e6a5..cce9abac11 100644 --- a/tests/ext/ffe/flag_evaluation_identity.phpt +++ b/tests/ext/ffe/flag_evaluation_identity.phpt @@ -1,5 +1,5 @@ --TEST-- -FFE evaluations remain deliverable without application identity pending sidecar attribution +FFE evaluations retain resolved application identity across changes before flush --SKIPIF-- $context['service']) + && $row['evaluation_count'] === 1 + && $row['first_evaluation'] === 1700000000123 + && $row['last_evaluation'] === 1700000000123; } } fwrite($connection, "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}"); @@ -59,9 +68,16 @@ try { if (!$status['running']) { $exitCode = $status['exitcode']; break; } } if ($exitCode !== 0) throw new RuntimeException('child failed: ' . ffeTestProcessRead($pipes[2])); - foreach (array('default', 'configured') as $flag) { - echo $flag, '='; var_dump($contexts[$flag]); + foreach (array( + 'flag_evaluation_identity_app.php|none|', + 'app-a|test|v1', + 'app-b|test|v1', + 'app-b|production|v1', + 'app-b|production|v2', + ) as $key) { + echo $key, '='; var_dump(isset($contexts[$key]) && $contexts[$key]); } + echo 'application_groups='; var_dump(count($contexts)); } finally { if ($exitCode === null) proc_terminate($process); foreach ($pipes as $pipe) fclose($pipe); @@ -70,5 +86,9 @@ try { } ?> --EXPECT-- -default=bool(true) -configured=bool(true) +flag_evaluation_identity_app.php|none|=bool(true) +app-a|test|v1=bool(true) +app-b|test|v1=bool(true) +app-b|production|v1=bool(true) +app-b|production|v2=bool(true) +application_groups=int(5) diff --git a/tests/ext/ffe/stubs/flag_evaluation_identity_app.php b/tests/ext/ffe/stubs/flag_evaluation_identity_app.php index 320245f5a3..e120ed2366 100644 --- a/tests/ext/ffe/stubs/flag_evaluation_identity_app.php +++ b/tests/ext/ffe/stubs/flag_evaluation_identity_app.php @@ -1,12 +1,18 @@ 'app-a', 'env' => 'test', 'version' => 'v1'), + array('service' => 'app-b'), + array('env' => 'production'), + array('version' => 'v2'), +) as $settings) { + foreach ($settings as $key => $value) { + ini_set('datadog.' . $key, $value); } if (!\DDTrace\Internal\record_ffe_flag_evaluation( - $flag, 'blue', null, null, array(), null, false, false, 1700000000123 + 'same-flag', 'blue', null, null, array(), null, false, false, 1700000000123 )) throw new RuntimeException('submission rejected'); } dd_trace_synchronous_flush(); diff --git a/tracer/ffe.c b/tracer/ffe.c index a88bfb0d64..cc81bb1faa 100644 --- a/tracer/ffe.c +++ b/tracer/ffe.c @@ -94,8 +94,8 @@ bool ddtrace_ffe_record_flag_evaluation(zend_string *flag_key, zend_string *vari .runtime_default_used = runtime_default_used, .observe_full_evaluation_data = observe_full_evaluation_data, }; - // Sidecar application attribution is deferred to FFL-3438/FFL-3439. - // The current FFI still requires a context argument. + // The sidecar snapshots the connection's application before aggregation. + // Do not copy service, environment, and version with every evaluation. ddog_FfeTelemetryContext context = {0}; ddog_FfeSubmissionStatus status = ddog_sidecar_try_submit_ffe_flag_evaluation( DATADOG_G(sidecar), &context, &event,