feat(datadog-ffe): server-side EVP flagevaluation payload + bincode-safe sidecar delivery#2117
feat(datadog-ffe): server-side EVP flagevaluation payload + bincode-safe sidecar delivery#2117leoromanovsky wants to merge 17 commits into
Conversation
…ure gate - Add telemetry/flagevaluation.rs with FfeFlagEvaluationBatch and FfeFlagEvaluationEvent types modeled on exposures.rs - Required fields: timestamp, flag.key, first_evaluation, last_evaluation, evaluation_count - Optional fields use skip_serializing_if = Option::is_none for omitempty semantics (reviewer concern #2 review:4477935835) - Context pruning helper prune_context() enforces 256 fields / 256 chars skip-not-truncate (reviewer concern #1 review:4477935835) - New Cargo feature flagevaluation-evp gates the module (parallel to exposure-events) - Gate telemetry module in lib.rs to include flagevaluation-evp feature - 30 tests pass (25 existing + 5 new); OTel evaluation_metrics.rs and exposures.rs are byte-for-byte unchanged
…P flusher - Add ffe_flagevaluation_flusher.rs: structural copy of ffe_exposures_flusher.rs with path constant EVP_FLAGEVALUATIONS_PATH = /evp_proxy/v2/api/v2/flagevaluations and batch type FfeFlagEvaluationBatch; fire-and-forget send_batch with non-2xx warn+drop and timeout via biased tokio::select! - Add SidecarAction::FfeFlagEvaluationBatch variant to mod.rs enum; re-export FfeFlagEvaluationBatch from datadog-ffe telemetry module - Route SidecarAction::FfeFlagEvaluationBatch in sidecar_server.rs to ffe_flagevaluation_flusher::send_batch (parallel to FfeExposureBatch arm) - Add exhaust arm for new variant in telemetry.rs process_actions match - Enable flagevaluation-evp feature on datadog-ffe dep in sidecar Cargo.toml - 40 sidecar tests pass (3 new: posts_to_evp_proxy, non_2xx_does_not_panic, timeout_returns_without_waiting); OTel FfeEvaluationMetric path untouched
The worker->sidecar IPC serializes SidecarAction with bincode (non-self-describing). serde_json::Value (deserialize_any) and #[serde(skip_serializing_if)] both make bincode deserialize fail, so the sidecar silently dropped every FfeFlagEvaluationBatch ('IPC serve: failed to decode request') while the worker enqueue still returned ok.
- Carry pruned context as a JSON-object string (Option<String>); remove all skip_serializing_if from the wire types (keep #[serde(default)] for deserialize).
- Re-expand the context string into a JSON object and strip null/false/empty placeholders in ffe_flagevaluation_flusher::build_payload (POST shape unchanged; degraded tier carries no null placeholders).
- Add enqueue_actions_reliable (checked blocking send + reconnect-retry) for one-shot FFE batches; best-effort enqueue_actions left unchanged for high-volume telemetry.
- Add a bincode round-trip test for FfeFlagEvaluationBatch (mixed Some/None fields) to lock the wire-codec contract.
Clippy Allow Annotation ReportComparing clippy allow annotations between branches:
Summary by Rule
Annotation Counts by File
Annotation Stats by Crate
About This ReportThis report tracks Clippy allow annotations for specific rules, showing how they've changed in this PR. Decreasing the number of these annotations generally improves code quality. |
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 4d38cc5 | Docs | Datadog PR Page | Give us feedback! |
📚 Documentation Check Results📦
|
🔒 Cargo Deny Results📦
|
Artifact Size Benchmark Reportaarch64-alpine-linux-musl
aarch64-unknown-linux-gnu
libdatadog-x64-windows
libdatadog-x86-windows
x86_64-alpine-linux-musl
x86_64-unknown-linux-gnu
|
…46-ffe-flagevaluation-evp
…46-ffe-flagevaluation-evp
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60fe825c39
ℹ️ 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".
| fn is_placeholder(key: &str, value: &serde_json::Value) -> bool { | ||
| match value { | ||
| serde_json::Value::Null => true, | ||
| serde_json::Value::Bool(b) => key == "runtime_default_used" && !b, |
There was a problem hiding this comment.
Strip empty env/version before posting flagevaluation payloads
When a tracer has no env or version configured, FfeTelemetryContext serializes those fields as empty strings, but this placeholder filter only removes empty service values. The existing exposure payload skips empty service, env, and version, and this flusher's own contract is to remove empty placeholders before the EVP POST; leaving "env":""/"version":"" in common unset configurations can make flagevaluation payloads diverge from the schema or be rejected by the worker.
Useful? React with 👍 / 👎.
Motivation
PHP relies on libdatadog and the sidecar for server-side flag-evaluation delivery, so the shared sidecar path must preserve the same worker-facing EVP contract as the direct SDK implementations. This contribution gives PHP a reusable, backend-verifiable flagevaluation transport without adding a one-off EVP writer in the PHP extension, which keeps cross-SDK adoption smoother for SDK owners and APM review.
Changes
FfeFlagEvaluationBatchafter existing variants.context.evaluationover IPC as a JSON-object string and re-expands it to an object for outbound EVP JSON.false,"",{}, and[]in user context.reasonargument.targeting_rule.key.targeting_keyandcontext; drops and logs only if the degraded row still cannot fit.Decisions
reasonis not part of the native ABI, sidecar event, outbound EVP payload, or aggregation contract.flowchart TD A[sidecar receives/coalesces batches] --> B[clean and encode event JSON] B --> C{candidate POST <= 5 MiB?} C -- yes --> D[POST through Agent EVP proxy] C -- no --> E{single full row can degrade?} E -- yes --> F[omit targeting_key and context] F --> B E -- no --> G[drop and log/count]Validation Evidence
Dogfooding App
ffe-dogfoodingapp-php7andapp-php8-openfeaturewere run with local PHP artifacts using this libdatadog sidecar path.DD_EVP_PROXY_CONFIG_ADDITIONAL_ENDPOINTS={},DD_SKIP_SSL_VALIDATION=false, andDD_REMOTE_CONFIGURATION_NO_TLS_VALIDATION=false.ffe-dogfooding-string-flag:libdd-php7-batch-20260623T020845Z-alphalibdd-php7-batch-20260623T020845Z-bravolibdd-php8of-batch-20260623T020845Z-alphalibdd-php8of-batch-20260623T020845Z-bravovariant_2.System Tests
Staging End-To-End
--datacenter us1.staging.dog --customer-auth=skipagainst theflagevaluationtrack for the exact targeting keys above.flag.key=ffe-dogfooding-string-flag,variant.key=variant_2,allocation.key=allocation-override-392dd7c149f8, andevaluation_count=12:libdd-php7-batch-20260623T020845Z-alpha:first_evaluation=1782180525878,last_evaluation=1782180525985,timestamp=1782180525985,evaluation_count=12libdd-php7-batch-20260623T020845Z-bravo:first_evaluation=1782180525997,last_evaluation=1782180526103,timestamp=1782180526103,evaluation_count=12libdd-php8of-batch-20260623T020845Z-alpha:first_evaluation=1782180526245,last_evaluation=1782180526344,timestamp=1782180526344,evaluation_count=12libdd-php8of-batch-20260623T020845Z-bravo:first_evaluation=1782180526356,last_evaluation=1782180526451,timestamp=1782180526451,evaluation_count=12