Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
42 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
59d0ea1
Fix FFE exposure sidecar activation
leoromanovsky May 28, 2026
59be76d
Update FFE exposure dependency and result ABI
leoromanovsky May 28, 2026
416749d
Bump libdatadog exposure runtime
leoromanovsky May 28, 2026
94de19a
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
11cc8fa
Remove unreachable FFE exposure cleanup branch
leoromanovsky Jun 2, 2026
643272b
Drop redundant FFE exposure release guards
leoromanovsky Jun 2, 2026
e4d254e
Pass FFE exposures as zend strings
leoromanovsky Jun 2, 2026
31ccc9f
Document FFE exposure CLI flush timing
leoromanovsky Jun 2, 2026
436f80e
Move FFE exposure flush helper to testing namespace
leoromanovsky Jun 2, 2026
4557494
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
1cb2a0b
Merge remote-tracking branch 'origin/master' into leo.romanovsky/m2-f…
leoromanovsky Jun 2, 2026
203281e
Move FFE exposure buffering into tracer
leoromanovsky Jun 2, 2026
bcf1342
Fully move FFE to tracer/
bwoebi Jun 2, 2026
bdaca4f
Fix compile error
bwoebi Jun 2, 2026
00564ae
Remove FFE evaluation RC polling
leoromanovsky Jun 2, 2026
a802e10
Proper empty string
bwoebi Jun 2, 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
7 changes: 6 additions & 1 deletion src/DDTrace/OpenFeature/DataDogProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use DDTrace\FeatureFlags\EvaluationDetails;
use DDTrace\FeatureFlags\EvaluationErrorCode;
use DDTrace\FeatureFlags\EvaluationReason;
use DDTrace\FeatureFlags\Internal\Exposure\ExposureHook;
use DDTrace\FeatureFlags\Internal\NoopWarningEmitter;
use DDTrace\FeatureFlags\Internal\TriggerErrorWarningEmitter;
use DDTrace\FeatureFlags\Internal\WarningEmitter;
Expand All @@ -30,7 +31,11 @@ final class DataDogProvider extends AbstractProvider

public function __construct()
{
$this->client = FeatureFlagsClient::createWithDependencies(null, new NoopWarningEmitter());
$this->client = FeatureFlagsClient::createWithDependencies(
null,
new NoopWarningEmitter(),
ExposureHook::createDefault()
);
$this->warningEmitter = new TriggerErrorWarningEmitter();
}

Expand Down
38 changes: 34 additions & 4 deletions src/api/FeatureFlags/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,43 @@
namespace DDTrace\FeatureFlags;

use DDTrace\FeatureFlags\Internal\Evaluator;
use DDTrace\FeatureFlags\Internal\EvaluationCompleted;
use DDTrace\FeatureFlags\Internal\EvaluationCompletedHook;
use DDTrace\FeatureFlags\Internal\Exposure\ExposureHook;
use DDTrace\FeatureFlags\Internal\NativeEvaluator;
use DDTrace\FeatureFlags\Internal\NoopEvaluationCompletedHook;
use DDTrace\FeatureFlags\Internal\TriggerErrorWarningEmitter;
use DDTrace\FeatureFlags\Internal\WarningEmitter;

final class Client
{
private $evaluator;
private $warningEmitter;
private $evaluationCompletedHook;
private $warnedAboutNonProductionRuntime = false;

private function __construct(
Evaluator $evaluator,
WarningEmitter $warningEmitter
WarningEmitter $warningEmitter,
EvaluationCompletedHook $evaluationCompletedHook
) {
$this->evaluator = $evaluator;
$this->warningEmitter = $warningEmitter;
$this->evaluationCompletedHook = $evaluationCompletedHook;
}

public static function create()
{
return self::createWithDependencies();
return self::createWithDependencies(null, null, ExposureHook::createDefault());
}

/**
* @internal Tests and Datadog-owned bridge adapters only.
*/
public static function createWithDependencies(
$evaluator = null,
$warningEmitter = null
$warningEmitter = null,
$evaluationCompletedHook = null
) {
if ($evaluator !== null && !$evaluator instanceof Evaluator) {
throw new \InvalidArgumentException('Expected an Evaluator instance');
Expand All @@ -41,9 +49,14 @@ public static function createWithDependencies(
throw new \InvalidArgumentException('Expected a WarningEmitter instance');
}

if ($evaluationCompletedHook !== null && !$evaluationCompletedHook instanceof EvaluationCompletedHook) {
throw new \InvalidArgumentException('Expected an EvaluationCompletedHook instance');
}

return new self(
$evaluator ?: NativeEvaluator::createOrUnavailable(),
$warningEmitter ?: new TriggerErrorWarningEmitter()
$warningEmitter ?: new TriggerErrorWarningEmitter(),
$evaluationCompletedHook ?: new NoopEvaluationCompletedHook()
);
}

Expand Down Expand Up @@ -111,10 +124,27 @@ private function evaluate($flagKey, $expectedType, $defaultValue, array $context
);

$this->warnIfNonProductionRuntime($details);
$this->evaluationCompleted(new EvaluationCompleted(
$flagKey,
$expectedType,
$defaultValue,
$targetingKey,
$attributes,
$details
));

return $details;
}

private function evaluationCompleted(EvaluationCompleted $evaluation)
{
try {
$this->evaluationCompletedHook->evaluationCompleted($evaluation);
} catch (\Throwable $throwable) {
// Internal exposure/metric hooks must never affect flag evaluation results.
}
}

private function normalizeContext(array $context)
{
$targetingKey = null;
Expand Down
118 changes: 118 additions & 0 deletions src/api/FeatureFlags/Internal/EvaluationCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace DDTrace\FeatureFlags\Internal;

use DDTrace\FeatureFlags\EvaluationDetails;
use DDTrace\FeatureFlags\EvaluationType;

final class EvaluationCompleted
{
private $flagKey;
private $valueType;
private $defaultValue;
private $targetingKey;
private $attributes;
private $details;

/**
* @param string $flagKey
* @param string $valueType One of EvaluationType::*.
* @param mixed $defaultValue
* @param string|null $targetingKey
* @param array<string, bool|int|float|string> $attributes
*/
public function __construct(
$flagKey,
$valueType,
$defaultValue,
$targetingKey,
array $attributes,
EvaluationDetails $details
) {
if (!is_string($flagKey) || $flagKey === '') {
throw new \InvalidArgumentException('Feature flag key must be a non-empty string');
}

if (!EvaluationType::isValid($valueType)) {
throw new \InvalidArgumentException('Unknown feature flag value type: ' . (string) $valueType);
}

if ($targetingKey !== null && !is_string($targetingKey)) {
throw new \InvalidArgumentException('Feature flag targeting key must be a string or null');
}

$this->flagKey = $flagKey;
$this->valueType = $valueType;
$this->defaultValue = $defaultValue;
$this->targetingKey = $targetingKey;
$this->attributes = $attributes;
$this->details = $details;
}

public function getFlagKey()
{
return $this->flagKey;
}

public function getValueType()
{
return $this->valueType;
}

public function getDefaultValue()
{
return $this->defaultValue;
}

public function getTargetingKey()
{
return $this->targetingKey;
}

public function getAttributes()
{
return $this->attributes;
}

public function getValue()
{
return $this->details->getValue();
}

public function getReason()
{
return $this->details->getReason();
}

public function getVariant()
{
return $this->details->getVariant();
}

public function getErrorCode()
{
return $this->details->getErrorCode();
}

public function getErrorMessage()
{
return $this->details->getErrorMessage();
}

public function getAllocationKey()
{
$exposureData = $this->details->getExposureData();
if (!isset($exposureData['allocationKey']) || !is_string($exposureData['allocationKey'])) {
return null;
}

return $exposureData['allocationKey'] === '' ? null : $exposureData['allocationKey'];
}

public function shouldLogExposure()
{
$exposureData = $this->details->getExposureData();

return isset($exposureData['doLog']) && $exposureData['doLog'] === true;
}
}
11 changes: 11 additions & 0 deletions src/api/FeatureFlags/Internal/EvaluationCompletedHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace DDTrace\FeatureFlags\Internal;

interface EvaluationCompletedHook
{
/**
* @return void
*/
public function evaluationCompleted(EvaluationCompleted $evaluation);
}
Loading
Loading