Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/versioned-property-matching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"posthog-ruby": patch
---

Honor the definitions response's `property_matching_version` during local flag evaluation, including groups, cohorts, dependencies, and external definition caches. Version 2 uses explicit equality with the service's empty-filter truthiness rule and JSON null/composite representations. Missing/1 now intentionally matches the service's legacy aggregate boolean truthiness instead of Ruby's previous unversioned explicit behavior (for example, `false` matches `"banana"` in legacy mode). Definition refreshes retain matching rules with their snapshot and version-only changes take effect on the next evaluation. Cache providers must preserve the version alongside definitions; older entries default to legacy.
9 changes: 5 additions & 4 deletions lib/posthog/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -656,15 +656,16 @@ def evaluate_flags(
flag_keys_set = flag_keys&.to_set(&:to_s)

@feature_flags_poller.load_feature_flags
poller_flags_by_key = @feature_flags_poller.feature_flags_by_key || {}
definition_snapshot = @feature_flags_poller._evaluation_snapshot
poller_flags_by_key = definition_snapshot[:flags_by_key] || {}

poller_flags_by_key.each do |key, definition|
next if flag_keys_set && !flag_keys_set.include?(key.to_s)

begin
match = @feature_flags_poller.send(
:_compute_flag_locally,
definition, distinct_id, groups, person_properties, group_properties
definition, distinct_id, groups, person_properties, group_properties, snapshot: definition_snapshot
)
rescue PostHog::RequiresServerEvaluation, PostHog::InconclusiveMatchError, StandardError
next
Expand All @@ -677,7 +678,7 @@ def evaluate_flags(
enabled: match.is_a?(String) || (match ? true : false),
variant: match.is_a?(String) ? match : nil,
payload: FeatureFlagResult.parse_payload(
@feature_flags_poller.send(:_compute_flag_payload_locally, key, match)
@feature_flags_poller.send(:_compute_flag_payload_locally, key, match, snapshot: definition_snapshot)
),
id: definition[:id],
version: nil,
Expand All @@ -696,7 +697,7 @@ def evaluate_flags(
# the snapshot uses a remote /flags response, the response's top-level
# `minimalFlagCalledEvents` field governs; a local-only snapshot reads
# the gate polled with the flag definitions.
minimal_flag_called_events = @feature_flags_poller.minimal_flag_called_events
minimal_flag_called_events = definition_snapshot[:minimal_flag_called_events]

# Skip the remote `/flags` round-trip when the caller scoped the request
# to a fixed set of `flag_keys` and we've already resolved every one of
Expand Down
Loading