Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
16d7817
Add FFE evaluation completion hook
leoromanovsky May 22, 2026
eba4c86
Add FFE exposure writer
leoromanovsky May 22, 2026
fc69e9f
Load evaluation-completed hook classes in canonical FFE PHPT
leoromanovsky May 23, 2026
92ef9a3
Load evaluation-completed hook classes in canonical FFE PHPT
leoromanovsky May 23, 2026
49d53ef
docs(ffe): add PR-stack and system diagrams for PR #3909
leoromanovsky May 23, 2026
81c01a7
Migrate FFE exposure transport to libdatadog sidecar
leoromanovsky May 23, 2026
0c4b4b3
Update libdatadog submodule to pick up FFE dispatch fix
leoromanovsky May 23, 2026
64c4267
tooling: place mktemp dirs under OUTPUT_DIR to avoid silent no-op on …
leoromanovsky May 23, 2026
ea04875
docs(ffe): quote diagram titles, switch system to TD, re-render at hi…
leoromanovsky May 23, 2026
f319d47
docs(ffe): quote diagram titles, switch system to TD, re-render at hi…
leoromanovsky May 23, 2026
13aca28
docs(ffe): drop 'Hook seam' wording, use 'Hook layer'
leoromanovsky May 23, 2026
28782fb
ExposureWriter: surface first drop with a one-time warning + TODO
leoromanovsky May 24, 2026
f775277
ExposureWriter: flush-on-full so long-running runtimes don't silently…
leoromanovsky May 24, 2026
8eedb0e
chore(ffe): remove generated stack diagrams
leoromanovsky May 24, 2026
04adf69
chore(ffe): remove generated stack diagrams
leoromanovsky May 24, 2026
e9b713c
Merge branch 'leo.romanovsky/milestone-1-runtime-evaluation' into leo…
leoromanovsky May 26, 2026
f4a1546
Merge branch 'leo.romanovsky/m2-m3-evaluation-completed-base' into le…
leoromanovsky May 26, 2026
ec49560
Merge branch 'leo.romanovsky/milestone-1-runtime-evaluation' into leo…
leoromanovsky May 27, 2026
61f6cc1
Merge branch 'leo.romanovsky/m2-m3-evaluation-completed-base' into le…
leoromanovsky May 27, 2026
76118cb
fix(ffe): tidy native exposure branch base
leoromanovsky May 27, 2026
b28e705
Merge milestone 1 runtime evaluation base
leoromanovsky May 27, 2026
c4bceb1
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 27, 2026
e82793e
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
5e38adc
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
c8cb8f5
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
a30c3ef
chore(ffe): update libdatadog sidecar dependency
leoromanovsky May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions components-rs/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,61 @@ typedef struct ddog_TracerHeaderTags {
bool client_computed_stats;
} ddog_TracerHeaderTags;

typedef struct ddog_FfeTelemetryContext {
ddog_CharSlice service;
ddog_CharSlice env;
ddog_CharSlice version;
} ddog_FfeTelemetryContext;

typedef struct ddog_FfeExposure {
uint64_t timestamp_ms;
ddog_CharSlice flag_key;
ddog_CharSlice subject_id;
/**
* UTF-8 JSON object. Empty, invalid, or non-object JSON is serialized as
* an empty subject attribute object.
*/
ddog_CharSlice subject_attributes_json;
ddog_CharSlice allocation_key;
ddog_CharSlice variant;
} ddog_FfeExposure;

typedef struct ddog_Slice_FfeExposure {
/**
* Should be non-null and suitably aligned for the underlying type. It is
* allowed but not recommended for the pointer to be null when the len is
* zero.
*/
const struct ddog_FfeExposure *ptr;
/**
* The number of elements (not bytes) that `.ptr` points to. Must be less
* than or equal to [isize::MAX].
*/
uintptr_t len;
} ddog_Slice_FfeExposure;

typedef struct ddog_FfeEvaluationMetric {
ddog_CharSlice flag_key;
ddog_CharSlice variant;
ddog_CharSlice reason;
ddog_CharSlice error_type;
ddog_CharSlice allocation_key;
} ddog_FfeEvaluationMetric;

typedef struct ddog_Slice_FfeEvaluationMetric {
/**
* Should be non-null and suitably aligned for the underlying type. It is
* allowed but not recommended for the pointer to be null when the len is
* zero.
*/
const struct ddog_FfeEvaluationMetric *ptr;
/**
* The number of elements (not bytes) that `.ptr` points to. Must be less
* than or equal to [isize::MAX].
*/
uintptr_t len;
} ddog_Slice_FfeEvaluationMetric;

/**
* Holds the raw parts of a Rust Vec; it should only be created from Rust,
* never from C.
Expand Down
33 changes: 33 additions & 0 deletions components-rs/sidecar.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,39 @@ ddog_MaybeError ddog_sidecar_send_debugger_datum(struct ddog_SidecarTransport **
ddog_QueueId queue_id,
struct ddog_DebuggerPayload *payload);

/**
* Send structured FFE exposure events to the sidecar. The sidecar owns
* deduplication, JSON serialization, and Agent EVP delivery. This function is
* caller-driven; shared libdatadog evaluator calls do not log unless an SDK
* explicitly sends this action.
*
* # Safety
* `context` and every element in `exposures` must contain valid UTF-8
* `CharSlice` values. Empty `exposures` is a no-op.
*/
ddog_MaybeError ddog_sidecar_send_ffe_exposure_batch(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
const ddog_QueueId *queue_id,
const struct ddog_FfeTelemetryContext *context,
struct ddog_Slice_FfeExposure exposures);

/**
* Send structured FFE evaluation metric events to the sidecar. The sidecar
* owns aggregation, OTLP/protobuf serialization, and OTLP HTTP delivery. This
* function is caller-driven so SDKs with existing host-language hooks can
* safely coexist until they explicitly migrate.
*
* # Safety
* `endpoint`, `context`, and every element in `metrics` must contain valid
* UTF-8 `CharSlice` values. Empty `endpoint` or `metrics` is a no-op.
*/
ddog_MaybeError ddog_sidecar_send_ffe_evaluation_metrics(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
const ddog_QueueId *queue_id,
ddog_CharSlice endpoint,
const struct ddog_FfeTelemetryContext *context,
struct ddog_Slice_FfeEvaluationMetric metrics);

ddog_MaybeError ddog_sidecar_send_debugger_diagnostics(struct ddog_SidecarTransport **transport,
const struct ddog_InstanceId *instance_id,
ddog_QueueId queue_id,
Expand Down
49 changes: 49 additions & 0 deletions ext/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1932,6 +1932,8 @@ static PHP_RSHUTDOWN_FUNCTION(ddtrace) {
ddtrace_rshutdown_remote_config();
}

ddtrace_ffe_flush_exposures();

if (!ddtrace_disable) {
ddtrace_autoload_rshutdown();

Expand Down Expand Up @@ -2769,6 +2771,12 @@ PHP_FUNCTION(DDTrace_Internal_handle_fork) {
dd_internal_handle_fork();
}

PHP_FUNCTION(DDTrace_Internal_flush_ffe_exposures) {
ZEND_PARSE_PARAMETERS_NONE();

RETURN_BOOL(ddtrace_ffe_flush_exposures());
}

PHP_FUNCTION(DDTrace_dogstatsd_count) {
zend_string *metric;
zend_long value;
Expand Down Expand Up @@ -2975,6 +2983,16 @@ PHP_FUNCTION(DDTrace_Testing_ffe_load_config) {
RETURN_BOOL(ddog_ffe_load_config(dd_zend_string_to_CharSlice(json)));
}

static zend_string *ddtrace_ffe_attributes_json(zval *attrs_zv) {
smart_str buf = {0};
zai_json_encode(&buf, attrs_zv, 0);
if (!buf.s) {
return zend_string_init("{}", sizeof("{}") - 1, 0);
}
smart_str_0(&buf);
return smart_str_extract(&buf);
}

static void ddtrace_ffe_update_property(zval *object, const char *name, size_t name_len, zval *value) {
zend_string *property_name = zend_string_init(name, name_len, 0);
zend_update_property_ex(ddtrace_ce_ffe_result, Z_OBJ_P(object), property_name, value);
Expand Down Expand Up @@ -3017,6 +3035,22 @@ static void ddtrace_ffe_update_empty_array_property(zval *object, const char *na
zval_ptr_dtor(&property_value);
}

static void ddtrace_ffe_refresh_remote_config(void) {
if (!DDTRACE_G(remote_config_state)) {
return;
}

if (DDTRACE_G(reread_remote_configuration)) {
DDTRACE_G(reread_remote_configuration) = 0;
ddog_process_remote_configs(DDTRACE_G(remote_config_state));
return;
}

if (!ddog_ffe_has_config()) {
ddtrace_check_for_new_config_now();
}
}

PHP_FUNCTION(DDTrace_ffe_evaluate) {
zend_string *flag_key;
zend_long type_id_zl;
Expand All @@ -3041,6 +3075,7 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) {
ZEND_PARSE_PARAMETERS_END();

type_id = (int32_t) type_id_zl;
ddtrace_ffe_refresh_remote_config();
attributes = Z_ARRVAL_P(attrs_zv);
attrs_count = zend_hash_num_elements(attributes);

Expand Down Expand Up @@ -3117,6 +3152,20 @@ PHP_FUNCTION(DDTrace_ffe_evaluate) {
RETURN_NULL();
}

if (result.do_log && result.allocation_key && result.variant) {
zend_string *subject_attributes_json = ddtrace_ffe_attributes_json(attrs_zv);
ddtrace_ffe_record_exposure(
ZSTR_VAL(flag_key),
ZSTR_LEN(flag_key),
targeting_key ? ZSTR_VAL(targeting_key) : NULL,
targeting_key ? ZSTR_LEN(targeting_key) : 0,
subject_attributes_json,
ZSTR_VAL(result.allocation_key),
ZSTR_VAL(result.variant)
);
zend_string_release(subject_attributes_json);
}

object_init_ex(return_value, ddtrace_ce_ffe_result);
ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("valueJson"), result.value_json);
ddtrace_ffe_update_nullable_string_property(return_value, ZEND_STRL("variant"), result.variant);
Expand Down
3 changes: 3 additions & 0 deletions ext/ddtrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace)
ddog_SidecarTransport *sidecar;
ddog_QueueId sidecar_queue_id;
MUTEX_T sidecar_universal_service_tags_mutex;
void *ffe_exposure_buffer;
size_t ffe_exposure_buffer_len;
size_t ffe_exposure_buffer_cap;
ddog_AgentRemoteConfigReader *agent_config_reader;
ddog_RemoteConfigState *remote_config_state;
bool remote_config_writing; // true while RC WRITE mode INI update is in progress
Expand Down
7 changes: 7 additions & 0 deletions ext/ddtrace.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,13 @@ function add_span_flag(\DDTrace\SpanData $span, int $flag): void {}
*/
function handle_fork(): void {}

/**
* Flushes native FFE exposure batches for integration tests.
*
* @internal
*/
function flush_ffe_exposures(): bool {}

}

namespace datadog\appsec\v2 {
Expand Down
4 changes: 4 additions & 0 deletions ext/ddtrace_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ ZEND_END_ARG_INFO()

#define arginfo_DDTrace_Internal_handle_fork arginfo_DDTrace_flush

#define arginfo_DDTrace_Internal_flush_ffe_exposures arginfo_DDTrace_are_endpoints_collected

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_datadog_appsec_v2_track_user_login_success, 0, 1, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, login, IS_STRING, 0)
ZEND_ARG_TYPE_MASK(0, user, MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_NULL, "null")
Expand Down Expand Up @@ -428,6 +430,7 @@ ZEND_FUNCTION(DDTrace_Testing_emit_asm_event);
ZEND_FUNCTION(DDTrace_Testing_normalize_tag_value);
ZEND_FUNCTION(DDTrace_Internal_add_span_flag);
ZEND_FUNCTION(DDTrace_Internal_handle_fork);
ZEND_FUNCTION(DDTrace_Internal_flush_ffe_exposures);
ZEND_FUNCTION(datadog_appsec_v2_track_user_login_success);
ZEND_FUNCTION(datadog_appsec_v2_track_user_login_failure);
ZEND_FUNCTION(dd_trace_env_config);
Expand Down Expand Up @@ -527,6 +530,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Testing", "normalize_tag_value"), zif_DDTrace_Testing_normalize_tag_value, arginfo_DDTrace_Testing_normalize_tag_value, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "add_span_flag"), zif_DDTrace_Internal_add_span_flag, arginfo_DDTrace_Internal_add_span_flag, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "handle_fork"), zif_DDTrace_Internal_handle_fork, arginfo_DDTrace_Internal_handle_fork, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Internal", "flush_ffe_exposures"), zif_DDTrace_Internal_flush_ffe_exposures, arginfo_DDTrace_Internal_flush_ffe_exposures, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("datadog\\appsec\\v2", "track_user_login_success"), zif_datadog_appsec_v2_track_user_login_success, arginfo_datadog_appsec_v2_track_user_login_success, 0, NULL, NULL)
ZEND_RAW_FENTRY(ZEND_NS_NAME("datadog\\appsec\\v2", "track_user_login_failure"), zif_datadog_appsec_v2_track_user_login_failure, arginfo_datadog_appsec_v2_track_user_login_failure, 0, NULL, NULL)
ZEND_FE(dd_trace_env_config, arginfo_dd_trace_env_config)
Expand Down
Loading
Loading