Skip to content

Parse observeFullEvaluationData and hash targeting_key [FFL-2963] - #6178

Draft
vjfridge wants to merge 2 commits into
masterfrom
vickie/FFL-2963-flagevaluation-pii-protection
Draft

Parse observeFullEvaluationData and hash targeting_key [FFL-2963]#6178
vjfridge wants to merge 2 commits into
masterfrom
vickie/FFL-2963-flagevaluation-pii-protection

Conversation

@vjfridge

Copy link
Copy Markdown

Summary

This change makes the no-PII path the default for EVP flagevaluation events in the Ruby SDK.

When the top-level UFC boolean observeFullEvaluationData is false (the default), the SDK:

  • Hashes the targeting_key with the sha256_ prefix (unsalted SHA-256, 71 chars).
  • Omits the context.evaluation key entirely (absent, not nil, not {}).
  • Redacts the error.message to the error_code so it cannot carry raw context data.

When observeFullEvaluationData is true, the SDK emits the raw targeting_key, the full context.evaluation, and the raw error.message.

The kill switch DD_FLAGGING_EVALUATION_COUNTS_ENABLED still wins over everything and emits nothing. DoLog has no effect on this track.

Contract

This work does the Ruby fan-out of the PII-hashing contract (FFL-2780). The pilot reference is dd-trace-java#12042 (FFL-2790). The contract of record is FFL-2784.

Canonical cross-SDK vector (every SDK must produce this exact digest):

"jane.doe@datadoghq.com" -> "sha256_b4698f9b6d186781fa8dc59e533578fa2d8379a46b1cf6db85cda6aa9c99e51b"

Consent lifecycle

The consent value travels on the event, not in live config. No component downstream of the evaluator reads live config. This prevents a later Remote Config update from applying a different environment consent.

  • The evaluator reads observeFullEvaluationData from the UFC root (a sibling of environment, not a field on it).
  • The provider stamps the value onto the evaluation metadata under the key observe_full_evaluation_data.
  • The hook reads the value from the metadata only.
  • The aggregator puts the value in the bucket key.
  • The writer obeys the value at serialization time.

Ruby has no global config accessor in this path, so the consent-lifecycle race that the Java pilot fixed does not occur here.

Changes

File Change
native_evaluator.rb Parse observeFullEvaluationData from the UFC JSON root. Absent, null, or wrong-typed values return false. Malformed JSON does not raise.
evaluation_engine.rb Expose observe_full_evaluation_data.
provider.rb Stamp consent onto the evaluation metadata.
flag_eval_evp_hook.rb Read consent from the metadata. Skip the context copy on the hot path when consent is off. Capture the error code.
aggregator.rb Put consent in the bucket key. Drop the context dimension from the key when consent is off. Do not store context_attrs when off.
writer.rb Hash the targeting_key and omit the context when consent is off. Redact the error.message to the error_code when off. Add a pre-queue capacity check before the bounded copy. Add three telemetry signals.
*.rbs Update the RBS signatures in lockstep.

Telemetry signals

The writer now emits three telemetry signals:

  1. Pre-queue overflow: the queue is full before the bounded copy (O(1) drop, not copy-then-discard).
  2. Context truncated: a context cap was hit during the pruning step.
  3. Enqueue drop: the queue is full at offer() time (a race; this is rare when the pre-queue check is in place).

Tests

All tests assert on raw wire bytes: the hash is present AND the raw subject string appears nowhere in the payload.

  • Canonical cross-SDK hash vector.
  • Consent absent / false / true / null / wrong-typed: the consent value is false for all but true.
  • Consent off: the targeting_key is hashed, the context is absent, the raw subject string appears nowhere.
  • Consent on: the raw targeting_key and the full context.evaluation are emitted.
  • error.message redaction: the error message carries only the error code when consent is off; the raw context string appears nowhere.
  • Consent in the bucket key: consent-on and consent-off evaluations go into distinct buckets. The context dimension is not in the key when consent is off.
  • DoLog non-impact: the emitted shape is the same for all DoLog values.
  • Hook reads consent from the metadata, not from live config.

bundle exec rake standard typecheck is clean. bundle exec rake test:open_feature is 256 examples, 0 failures.

L3 caveat

system-tests/manifests/ruby.yml still gates the whole of tests/ffe/test_flag_eval_evp.py as missing_feature (FFL-2446) even though the base track merged on 2026-06-30. The PII system-tests do not run until that file-level gate is addressed. This PR does not change the manifest. The L3 tests must be activated in a separate change (FFL-2783) before this PR can use them as a contract test.

Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

…g_key [FFL-2963]

Make the no-PII path the default for EVP flagevaluation events. The SDK
hashes the targeting key with the sha256_ prefix and omits the evaluation
context when the top-level UFC boolean observeFullEvaluationData is false
(the default). When the value is true, the SDK emits the raw targeting key
and the full context.

The consent value travels on the event, not in live config. The evaluator
reads the value from the UFC it evaluated against and stamps it onto the
evaluation metadata. The hook and the aggregator read only this value.

Changes:
- native_evaluator: parse observeFullEvaluationData from the UFC root
  (sibling of environment). Absent, null, or wrong-typed values return
  false. Malformed JSON does not raise.
- evaluation_engine: expose observe_full_evaluation_data.
- provider: stamp consent onto evaluation metadata.
- flag_eval_evp_hook: read consent from metadata. Skip the context copy
  on the hot path when consent is off. Capture the error code.
- aggregator: put consent in the bucket key. Drop the context dimension
  from the key when consent is off. Do not store context_attrs when off.
- writer: hash the targeting key and omit the context when consent is off.
  Redact the error message to the error code when consent is off. Add a
  pre-queue capacity check before the bounded copy. Add three telemetry
  signals: pre-queue overflow, context truncated, enqueue drop.

The kill switch DD_FLAGGING_EVALUATION_COUNTS_ENABLED still wins over
everything and emits nothing. DoLog has no effect on this track.

Canonical cross-SDK vector:
  "jane.doe@datadoghq.com" ->
  sha256_b4698f9b6d186781fa8dc59e533578fa2d8379a46b1cf6db85cda6aa9c99e51b

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@vjfridge
vjfridge requested review from a team as code owners August 11, 2026 15:10
@vjfridge
vjfridge requested a review from sameerank August 11, 2026 15:10
@vjfridge vjfridge added the feature Involves a product feature label Aug 11, 2026
@vjfridge
vjfridge requested a review from pavlokhrebto August 11, 2026 15:10
@vjfridge vjfridge added AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos openfeature A new component that provider an ability to configure feature flags labels Aug 11, 2026
@dd-octo-sts

dd-octo-sts Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

👋 Hey @DataDog/ruby-guild, please fill "Change log entry" section in the pull request description.

If changes need to be present in CHANGELOG.md you can state it this way

**Change log entry**

Yes. A brief summary to be placed into the CHANGELOG.md

(possible answers Yes/Yep/Yeah)

Or you can opt out like that

**Change log entry**

None.

(possible answers No/Nope/None)

Visited at: 2026-08-11 15:10:31 UTC

@dd-octo-sts

dd-octo-sts Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Typing analysis

Note: Ignored files are excluded from the next sections.

Untyped methods

This PR introduces 1 untyped method and 23 partially typed methods, and clears 23 partially typed methods. It increases the percentage of typed methods from 69.52% to 69.58% (+0.06%).

Untyped methods (+1-0)Introduced:
sig/datadog/open_feature/hooks/flag_eval_evp_hook.rbs:31
└── def extract_error_code: (untyped evaluation_details) -> untyped
Partially typed methods (+23-23)Introduced:
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:69
└── def record: (
          flag_key: ::String,
          variant: ::String?,
          allocation_key: ::String?,
          targeting_key: ::String?,
          eval_time_ms: ::Integer,
          attrs: ::Hash[::String, untyped]?,
          ?error_message: ::String?,
          ?runtime_default: bool?,
          ?observe_full_evaluation_data: bool,
          ?error_code: untyped
        ) -> void
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:82
└── def flush_and_reset: () -> ::Hash[::Symbol, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:84
└── def prune_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:86
└── def self.prune_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:88
└── def self.flatten_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:90
└── def self.flatten_value: (
          ::String prefix,
          untyped value,
          ::Hash[::String, untyped] output,
          ::Hash[::Integer, bool] seen,
          ::Integer depth
        ) -> void
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:98
└── def canonical_context_key: (::Hash[::String, untyped]? attrs) -> ::String
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:102
└── def context_value_bytes: (untyped value) -> ::String
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:106
└── def new_entry: (
          ::Integer evaluation_time_ms,
          runtime_default: bool,
          ?error_message: ::String?,
          ?targeting_key: ::String?,
          ?context_attrs: ::Hash[::String, untyped]?,
          ?observe_full_evaluation_data: bool,
          ?error_code: untyped
        ) -> ::Hash[::Symbol, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:116
└── def observe: (::Hash[::Symbol, untyped] entry, ::Integer evaluation_time_ms) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:79
└── def enqueue: (**untyped event) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:89
└── def snapshot_context_value: (
          untyped value,
          ::Hash[::Integer, bool] seen,
          ::Integer depth
        ) -> untyped
sig/datadog/open_feature/flag_evaluation/writer.rbs:123
└── def build_events: (::Hash[::Symbol, untyped] snapshot) -> ::Array[::Hash[::String, untyped]]
sig/datadog/open_feature/flag_evaluation/writer.rbs:125
└── def build_event: (
          flag_key: ::String,
          variant: ::String?,
          allocation_key: ::String?,
          targeting_key: ::String?,
          entry: ::Hash[::Symbol, untyped],
          flush_time_ms: ::Integer,
          tier: ::Symbol,
          observe_full_evaluation_data: bool
        ) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/writer.rbs:138
└── def send_payload_batches: (::Array[::Hash[::String, untyped]] events) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:140
└── def send_payload_batch: (::Array[::Hash[::String, untyped]] events) -> untyped
sig/datadog/open_feature/flag_evaluation/writer.rbs:142
└── def encoded_event_for_payload: (
          ::Hash[::String, untyped] event,
          ::Integer base_payload_size
        ) -> [::Hash[::String, untyped], ::Integer, bool]?
sig/datadog/open_feature/flag_evaluation/writer.rbs:147
└── def encoded_event: (::Hash[::String, untyped] event) -> [::Hash[::String, untyped], ::Integer]
sig/datadog/open_feature/flag_evaluation/writer.rbs:151
└── def degrade_event_for_payload_limit: (
          ::Hash[::String, untyped] event
        ) -> ::Hash[::String, untyped]?
sig/datadog/open_feature/flag_evaluation/writer.rbs:157
└── def event_count: (::Hash[::String, untyped] event) -> ::Integer
sig/datadog/open_feature/hooks/flag_eval_evp_hook.rbs:33
└── def runtime_default?: (untyped evaluation_details) -> bool
sig/datadog/open_feature/native_evaluator.rbs:14
└── def get_assignment: (
        ::String flag_key,
        default_value: untyped,
        context: ::OpenFeature::SDK::EvaluationContext::fields_t,
        expected_type: ::Symbol
      ) -> (Core::FeatureFlags::ResolutionDetails | ResolutionDetails)
sig/datadog/open_feature/native_evaluator.rbs:27
└── def invalid_flag_configuration_error: (untyped default_value) -> ResolutionDetails
Cleared:
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:69
└── def record: (
          flag_key: ::String,
          variant: ::String?,
          allocation_key: ::String?,
          targeting_key: ::String?,
          eval_time_ms: ::Integer,
          attrs: ::Hash[::String, untyped]?,
          ?error_message: ::String?,
          ?runtime_default: bool?
        ) -> void
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:80
└── def flush_and_reset: () -> ::Hash[::Symbol, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:82
└── def prune_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:84
└── def self.prune_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:86
└── def self.flatten_context: (::Hash[::String, untyped]? attrs) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:88
└── def self.flatten_value: (
          ::String prefix,
          untyped value,
          ::Hash[::String, untyped] output,
          ::Hash[::Integer, bool] seen,
          ::Integer depth
        ) -> void
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:96
└── def canonical_context_key: (::Hash[::String, untyped]? attrs) -> ::String
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:100
└── def context_value_bytes: (untyped value) -> ::String
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:104
└── def new_entry: (
          ::Integer evaluation_time_ms,
          runtime_default: bool,
          ?error_message: ::String?,
          ?targeting_key: ::String?,
          ?context_attrs: ::Hash[::String, untyped]?
        ) -> ::Hash[::Symbol, untyped]
sig/datadog/open_feature/flag_evaluation/aggregator.rbs:112
└── def observe: (::Hash[::Symbol, untyped] entry, ::Integer evaluation_time_ms) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:65
└── def enqueue: (**untyped event) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:75
└── def snapshot_context_value: (
          untyped value,
          ::Hash[::Integer, bool] seen,
          ::Integer depth
        ) -> untyped
sig/datadog/open_feature/flag_evaluation/writer.rbs:103
└── def build_events: (::Hash[::Symbol, untyped] snapshot) -> ::Array[::Hash[::String, untyped]]
sig/datadog/open_feature/flag_evaluation/writer.rbs:105
└── def build_event: (
          flag_key: ::String,
          variant: ::String?,
          allocation_key: ::String?,
          targeting_key: ::String?,
          entry: ::Hash[::Symbol, untyped],
          flush_time_ms: ::Integer,
          tier: ::Symbol
        ) -> ::Hash[::String, untyped]
sig/datadog/open_feature/flag_evaluation/writer.rbs:115
└── def send_payload_batches: (::Array[::Hash[::String, untyped]] events) -> void
sig/datadog/open_feature/flag_evaluation/writer.rbs:117
└── def send_payload_batch: (::Array[::Hash[::String, untyped]] events) -> untyped
sig/datadog/open_feature/flag_evaluation/writer.rbs:119
└── def encoded_event_for_payload: (
          ::Hash[::String, untyped] event,
          ::Integer base_payload_size
        ) -> [::Hash[::String, untyped], ::Integer, bool]?
sig/datadog/open_feature/flag_evaluation/writer.rbs:124
└── def encoded_event: (::Hash[::String, untyped] event) -> [::Hash[::String, untyped], ::Integer]
sig/datadog/open_feature/flag_evaluation/writer.rbs:128
└── def degrade_event_for_payload_limit: (
          ::Hash[::String, untyped] event
        ) -> ::Hash[::String, untyped]?
sig/datadog/open_feature/flag_evaluation/writer.rbs:134
└── def event_count: (::Hash[::String, untyped] event) -> ::Integer
sig/datadog/open_feature/hooks/flag_eval_evp_hook.rbs:31
└── def runtime_default?: (untyped evaluation_details) -> bool
sig/datadog/open_feature/native_evaluator.rbs:10
└── def get_assignment: (
        ::String flag_key,
        default_value: untyped,
        context: ::OpenFeature::SDK::EvaluationContext::fields_t,
        expected_type: ::Symbol
      ) -> (Core::FeatureFlags::ResolutionDetails | ResolutionDetails)
sig/datadog/open_feature/native_evaluator.rbs:21
└── def invalid_flag_configuration_error: (untyped default_value) -> ResolutionDetails

If you believe a method or an attribute is rightfully untyped or partially typed, you can add # untyped:accept on the line before the definition to remove it from the stats.

@datadog-datadog-us1-prod

datadog-datadog-us1-prod Bot commented Aug 11, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 98.77%
Overall Coverage: 90.01% (+0.05%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: da353b2 | Docs | Datadog PR Page | Give us feedback!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 008eb827dc

ℹ️ 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".

# Stamp the consent value from the engine that did the evaluation. The hook reads
# this value from metadata, not from live config. This is the authoritative stamp:
# the C extension result has no flag_metadata setter, so consent goes here.
flag_meta[Ext::METADATA_OBSERVE_FULL_EVALUATION_DATA] = engine.observe_full_evaluation_data

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind consent to the evaluator result

If Remote Config changes from observeFullEvaluationData: false to true between fetch_engine_value and this separate accessor call, the result was evaluated under the old configuration but is stamped with the new consent; the hook can then emit its raw targeting key and context despite the configuration used for that evaluation forbidding it. Capture the evaluator and its consent atomically inside EvaluationEngine#fetch_value, and carry that snapshot with the result.

AGENTS.md reference: AGENTS.md:L112-L112

Useful? React with 👍 / 👎.

Comment on lines +97 to +100
full_key = [
flag_key, variant, allocation_key, runtime_default, error_message,
targeting_key, false
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Key redacted errors by their emitted code

When consent is off, serialization replaces error_message with error_code, but this bucket key still uses the raw message. Evaluations for the same flag and targeting key with messages that differ only in embedded input therefore create separate buckets that serialize as duplicate, schema-identical rows, wasting the per-flag cap and fragmenting counts; conversely, equal messages with different codes merge and report the first code. Use the emitted error code as the consent-off aggregation dimension, including in the degraded key.

Useful? React with 👍 / 👎.

Comment on lines 361 to +366
snapshot[:degraded].each do |key, entry|
flag_key, variant, allocation_key, _runtime_default, _error_message = key
flag_key, variant, allocation_key, _runtime_default, _error_message, _consent = key
event = build_event(
flag_key: flag_key, variant: variant, allocation_key: allocation_key,
targeting_key: nil, entry: entry, flush_time_ms: flush_time_ms, tier: :degraded,
observe_full_evaluation_data: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve consent and error codes after degradation

When a cardinality cap routes an error-bearing event to the degraded tier, add_to_degraded stores neither its consent nor its error code, and this call hardcodes consent to false. Consequently, degraded consent-on errors lose the raw error message, while degraded consent-off errors also lose the replacement error code, so both paths silently omit the error field that was emitted before this change. Carry the degraded key's consent and the event's error code into the entry and pass the consent through here.

AGENTS.md reference: lib/datadog/open_feature/AGENTS.md:L185-L187

Useful? React with 👍 / 👎.

Comment on lines +267 to +271
dropped_pre_queue = take_dropped_pre_queue_overflow
context_truncated = take_context_truncated_count

emit_drop_counts(dropped_queue, dropped_overflow)
emit_drop_counts(dropped_queue, dropped_overflow, dropped_pre_queue)
emit_context_truncated_counts(context_truncated)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Increment context-truncation telemetry

When pruning drops fields because there are more than 256 entries or a string exceeds 256 characters, nothing increments @context_truncated_count; repository-wide uses only initialize, read, and reset it. Thus this value is always zero and flagevaluation.context.truncated is never emitted (and max_field_length is never distinguishable), leaving the newly added observability path dead. Have the pruning step report both truncation reasons to the writer's counter.

AGENTS.md reference: lib/datadog/open_feature/AGENTS.md:L185-L187

Useful? React with 👍 / 👎.

Comment on lines +431 to +432
def hash_targeting_key(targeting_key)
TARGETING_KEY_HASH_PREFIX + Digest::SHA256.hexdigest(targeting_key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Encode targeting keys as UTF-8 before hashing

When a targeting key uses a non-UTF-8 Ruby encoding, Digest::SHA256 hashes its current byte representation rather than the UTF-8 bytes promised by this cross-SDK format. For example, the logical key é encoded as ISO-8859-1 hashes byte e9, while other SDKs and a UTF-8 Ruby string hash bytes c3 a9, producing different identifiers for the same subject. Encode the string to UTF-8 before hashing and define how invalid byte sequences are handled.

Useful? React with 👍 / 👎.

RuboCop enforces trailing commas after the last item of a multiline
array literal. Add the missing commas in the two full_key arrays.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

@Strech Strech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to request few changes before it goes into master

  1. Remove unnecessary AI generated comments explaining usage of something, it's not helpful and duplicates code
  2. Complete typespecs, some types are untyped, even tho they are known and easy to type
  3. Remove unnecessary comments in tests
  4. Remove unnecessary programming in tests

Comment on lines +49 to +51
# Literal prefix for the SHA-256 hash of the targeting key. The hash is
# unsalted SHA-256 over the raw UTF-8 bytes, lowercase hex, 71 chars total.
# This prefix is the cross-SDK contract: every SDK emits the same digest.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not relevant to the simple constant with sha256_ value (i.e AI slop)

Comment on lines +89 to +91
# Pre-queue capacity check: a full queue is an O(1) drop + counter, not
# a copy-then-discard. Do the bounded context copy only when the queue
# has room.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 lines of comments vs 3 lines of same-logic code (i.e AI slop)

Comment on lines +291 to +293
# Read-and-reset the pre-queue overflow counter. This counter fires when
# the queue is full before the bounded context copy, so a full queue is
# an O(1) drop, not a copy-then-discard.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment explain the usage, when should explain the method (i.e AI slop)

# Read-and-reset the pre-queue overflow counter. This counter fires when
# the queue is full before the bounded context copy, so a full queue is
# an O(1) drop, not a copy-then-discard.
def take_dropped_pre_queue_overflow

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby-style is to avoid take/get/set prefixes and use human-form

def read_and_reset_dropped_pre_queue_overflow


# Emit the context-truncated count with a reason label so the operator can
# tell which cap was hit.
def emit_context_truncated_counts(context_truncated)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name doesn't correspond the method body

?runtime_default: bool?
?runtime_default: bool?,
?observe_full_evaluation_data: bool,
?error_code: untyped

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be typed

Comment on lines +134 to +137
it "returns false for a nil configuration" do
evaluator = described_class.new(nil)
expect(evaluator.observe_full_evaluation_data).to be(false)
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it "returns false for a nil configuration" do
evaluator = described_class.new(nil)
expect(evaluator.observe_full_evaluation_data).to be(false)
end
it { expect(described_class.new(nil).observe_full_evaluation_data).to be(false) }

Comment on lines +86 to +93
def ufc(observe: :absent)
base = "{\"format\":\"SERVER\",\"environment\":{\"name\":\"test\"},\"flags\":{}}"
return base if observe == :absent

value = (observe == true) ? "true" : "false"
extra = "\"observeFullEvaluationData\":#{value}"
base.sub('{"format"', "{#{extra},\"format\"")
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done via 2 distinct let statements with 0 programming

@vjfridge
vjfridge marked this pull request as draft August 11, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos feature Involves a product feature openfeature A new component that provider an ability to configure feature flags

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants