Skip to content

feat(flags): support semver comparisons in user_blast_radius#44596

Merged
dmarticus merged 23 commits intomasterfrom
feat/support-semver-comparisons-in-user-blast-radius
Jan 13, 2026
Merged

feat(flags): support semver comparisons in user_blast_radius#44596
dmarticus merged 23 commits intomasterfrom
feat/support-semver-comparisons-in-user-blast-radius

Conversation

@dmarticus
Copy link
Contributor

@dmarticus dmarticus commented Jan 9, 2026

Problem

We're building SemVer targeting for feature flags, so the first thing we need to do is make sure that the user_blast_radius function can correctly evaluate potential rollouts that have semver operators. This change should be no-op, as we don't support creating flags with semver operators yet, but this is just laying the groundwork, tinkering with some hogQL, and keeping the PR footprint small.

Changes

  1. Added 8 new semver operators (frontend/src/types.ts:820-827, posthog/schema.py):
    - semver_eq, semver_gt, semver_gte, semver_lt, semver_lte
    - semver_tilde (~1.2.3 matches >=1.2.3 <1.3.0)
    - semver_caret (^1.2.3 matches >=1.2.3 <2.0.0)
    - semver_wildcard (1.2.* matches >=1.2.0 <1.3.0)
  2. Extended HogQL property filtering (posthog/hogql/property.py:273-405):
    - Implemented all 8 operators using the existing sortableSemver function
    - Added proper error handling with QueryError
    - Fixed edge case bug for empty wildcard patterns
  3. Added comprehensive tests:
    - Integration tests in posthog/api/test/test_feature_flag.py:8362-8615
    - Unit tests in posthog/hogql/test/test_property.py:1084-1172
    - All 4 semver tests pass (2 unit + 2 integration)
    - All 24 blast radius tests pass
  4. Added frontend operator mappings (frontend/src/lib/utils.tsx:332-341):
    - Human-readable labels for all operators

Publish to changelog?

no

@dmarticus dmarticus changed the title feat(flags): support semver comparisons in user blast radius feat(flags): support semver comparisons in user_blast_radius Jan 9, 2026
Comment on lines 332 to 363
@@ -349,6 +360,7 @@ export const allOperatorsMapping: Record<string, string> = {
...cohortOperatorMap,
...featureFlagOperatorMap,
...cleanedPathOperatorMap,
...semverOperatorMap,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

these changes aren't relevant yet, since we're not making UI changes, but they'll matter soon. They're good to have for all of these schema validation changes.

elif operator == PropertyOperator.SEMVER_EQ:
return ast.CompareOperation(
op=ast.CompareOperationOp.Eq,
left=ast.Call(name="sortableSemver", args=[expr]),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

an important thing to note here is that we're using the sortableSemver hogQL function to implement the data extraction, but this method does not support all of valid semver, e.g. things like 1.0.0-alpha.1 aren't supported. I'll make this change in a future PR; just trying to get the broad strokes down here.

@dmarticus dmarticus marked this pull request as ready for review January 9, 2026 00:38
@dmarticus dmarticus requested a review from a team as a code owner January 9, 2026 00:38
@assign-reviewers-posthog assign-reviewers-posthog bot requested a review from a team January 9, 2026 00:39
@posthog-project-board-bot posthog-project-board-bot bot moved this to In Review in Feature Flags Jan 9, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 8 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="posthog/hogql/property.py">

<violation number="1" location="posthog/hogql/property.py:343">
P1: Caret operator doesn't handle 0.x.y versions per semver spec. According to semver, `^0.2.3` should mean `>=0.2.3 <0.3.0` (not `<1.0.0`), and `^0.0.3` should mean `>=0.0.3 <0.0.4`. The current implementation always bumps major version, which will cause incorrect feature flag targeting for pre-1.0 software versions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-project-automation github-project-automation bot moved this from In Review to Approved in Feature Flags Jan 9, 2026
@posthog-project-board-bot posthog-project-board-bot bot moved this from Approved to In Review in Feature Flags Jan 10, 2026
Base automatically changed from chore/refactor-user-blast-radius to master January 11, 2026 18:31
…f github.com:PostHog/posthog into feat/support-semver-comparisons-in-user-blast-radius
Copy link
Contributor

@haacked haacked left a comment

Choose a reason for hiding this comment

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

Nice work!

Some non-blocking comments.

Would like to see more tests of edge cases in order to document what behavior we expect with values like these.

  • 0.0.0 - minimal version
  • 1.2.3-alpha - We don't support it yet, but want to document what should happen
  • v1.2.3
  • 1.2.3 - (leading space)
  • 1.2.3 - (trailing space)
  • 01.02.03 - (Should be same as 1.2.3 even though I believe it violates Semver spec)
  • 1.2.3.4 - Too many version numbers. These are common in .NET land though
  • 1..2.3 - empty component
  • 1.2.3. - trailing dot
  • .1.2.3 - leading dot
  • 1.-2.3 - negative version part (invalid semver)

Comment on lines 309 to 310
if len(parts) < 2:
raise ValueError("Invalid semver format")
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to consider 1.0 as 1.0.0 down the road.
Also, if someone does pass 1.0.0-alpha.1 have we already stripped out the -alpha.1 by this point?

Comment on lines 345 to 347
major = parts[0]
minor = parts[1] if len(parts) > 1 else "0"
patch = parts[2] if len(parts) > 2 else "0"
Copy link
Contributor

Choose a reason for hiding this comment

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

This parsing does what I described earlier by treating 1.0 as 1.0.0. Would it make sense to have a standard function to do this parsing and return the parts?

),
]
)
elif operator == PropertyOperator.SEMVER_CARET:
Copy link
Contributor

Choose a reason for hiding this comment

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

Each of the three range operators (tilde, caret, wildcard) does these 4 steps:

  1. Validate input type
  2. Parse version parts
  3. Calculate bounds
  4. Return the same AST structure

Maybe extract that into a helper method?

op = ast.CompareOperationOp.NotIn if operator == PropertyOperator.NOT_IN else ast.CompareOperationOp.In
return ast.CompareOperation(op=op, left=expr, right=ast.Array(exprs=[ast.Constant(value=v) for v in value]))
elif operator == PropertyOperator.SEMVER_EQ:
return ast.CompareOperation(
Copy link
Contributor

Choose a reason for hiding this comment

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

The range operators all validate the input type:

if not isinstance(value, str):
    raise QueryError("Semver operators require a string value")

Would it make sense to have the basic operators also do this so errors are consistent rather than dependent on whatever sortableSemver does?

@github-project-automation github-project-automation bot moved this from In Review to Approved in Feature Flags Jan 13, 2026
@posthog-project-board-bot posthog-project-board-bot bot moved this from Approved to In Review in Feature Flags Jan 13, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 13, 2026

Greptile's behavior is changing!

From now on, if a review finishes with no comments, we will not post an additional "statistics" comment to confirm that our review found nothing to comment on. However, you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@dmarticus dmarticus enabled auto-merge (squash) January 13, 2026 20:04
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 13, 2026

What's Being Changed

The PR introduces comprehensive semver comparison operators that follow the semantic versioning specification:

  • Basic operators: semver_eq, semver_neq, semver_gt, semver_gte, semver_lt, semver_lte
  • Range operators: semver_tilde (~1.2.3 = >=1.2.3 <1.3.0), semver_caret (^1.2.3 = >=1.2.3 <2.0.0), semver_wildcard (1.2.* = >=1.2.0 <1.3.0)

The changes span multiple layers:

  1. Schema updates (frontend/src/queries/schema.json, frontend/src/queries/validators.js): Added new operators to validation schemas
  2. Type definitions (frontend/src/types.ts): Extended PropertyOperator enum with semver operators
  3. HogQL engine (posthog/hogql/property.py): Core implementation using ClickHouse's sortableSemver function
  4. UI mappings (frontend/src/lib/utils.tsx, ee/hogai/summarizers/property_filters.py): Human-readable labels for operators
  5. Comprehensive testing (posthog/hogql/test/test_property.py, posthog/api/test/test_feature_flag.py): Unit and integration test coverage

How It Works

The implementation leverages ClickHouse's existing sortableSemver function to convert version strings into comparable formats. Range operators like tilde, caret, and wildcard are converted into bounded AND conditions. For example, ^1.2.3 becomes sortableSemver(property) >= sortableSemver('1.2.3') AND sortableSemver(property) < sortableSemver('2.0.0').

The code includes proper semver parsing with support for pre-release suffixes, missing version components, and follows semver specification edge cases (e.g., ^0.2.3 means >=0.2.3 <0.3.0, not <1.0.0).

Test Coverage

  • Unit tests: Validate basic functionality, error handling, and edge cases
  • Integration tests: Test all operators through the user_blast_radius API endpoint
  • Edge case testing: Covers 0.x.y versions, pre-release suffixes, v-prefixes, whitespace handling

The testing strategy follows the custom rule of preferring parameterized tests where appropriate, with clear documentation of expected behavior.

Potential Issues

⚠️ Schema synchronization risk: Multiple auto-generated schema files were updated (frontend/src/queries/schema.json, frontend/src/queries/validators.js). If these get out of sync or weren't properly regenerated, it could cause validation failures.

⚠️ Error handling consistency: The parse_semver function in posthog/hogql/property.py raises generic ValueError exceptions which are then caught and converted to QueryError. This indirection could potentially mask debugging information if the error propagation isn't handled correctly.

⚠️ Performance consideration: The semver range operators generate complex AND conditions with multiple sortableSemver function calls, which could impact query performance for large datasets with many semver comparisons.

Confidence Score: 4/5

The implementation is well-structured and comprehensive with excellent test coverage. The risk level is moderate due to the complexity of the HogQL integration and potential for schema synchronization issues, but the foundational nature of the change and thorough testing provide good confidence in the implementation.

@dmarticus dmarticus merged commit 2e7a2a3 into master Jan 13, 2026
181 checks passed
@github-project-automation github-project-automation bot moved this from In Review to Done in Feature Flags Jan 13, 2026
@dmarticus dmarticus deleted the feat/support-semver-comparisons-in-user-blast-radius branch January 13, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants