Skip to content

feat(generic-filters): Add a release rule condition - #6430

Merged
shellmayr merged 18 commits into
masterfrom
shellmayr/feat/version-rule-condition
Oct 8, 2026
Merged

shellmayr merged 18 commits into
masterfrom
shellmayr/feat/version-rule-condition

Conversation

@shellmayr

@shellmayr shellmayr commented Sep 25, 2026 •

Copy link
Copy Markdown
Member
  • Add a release rule condition, next to glob and cidr. It compares the version of a release, so generic inbound filters can express version ranges.
  • The comparator is a field of the condition {"op": "release", "name": "event.release", "comparator": "gte", "value": "1.2.0"}. Comparators are eq, gt, gte, lt, and lte. eq compares by version, so it ignores the build code.
  • Releases are parsed with sentry-release-parser, the way Sentry reads them. A version has one to four numeric components, and missing components are zero, so 1.2 compares as 1.2.0. A pre-release ranks below its final release, and build codes are ignored. A release without a version, such as a commit hash, never matches.
  • Packages: a value without a package, such as 1.2.0, matches releases of every package. A value with a package, such as myapp@1.2.0, only matches releases of that package.

Closes TET-3061

shellmayr and others added 5 commits September 25, 2026 10:09
Generic inbound filters can only match releases by glob, so a range such
as "every release below 2.0" is not expressible. Add a "version" op that
parses constraints like ">=1.2.0, <2.0.0" or "~>1.2" and compares them
against the version part of a Sentry release.

Versions order by semver precedence, so a pre-release ranks below its
final release and build codes are ignored. A release without a version,
such as a commit hash, never matches. Entries that do not parse are
skipped, like invalid glob patterns. Old Relays read the op as
unsupported, so a filter using it is inert there and other filters keep
working.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep only ">", ">=", "<", and "<=". A value without an operator, "=",
"!=", and "~>" are no longer parsed, so they are skipped like any
invalid entry.

Drop the semver crate and order versions with the release parser's
own components instead. This handles four components and compares
pre-release tags as text, which is how Sentry orders releases. Missing
components are zero, so ">1.2" matches "1.2.3", like Sentry's
release search.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the dedicated "version" op with a release type on the data.
Getters return release fields as Val::Release, and the gt, gte, lt,
and lte conditions order that type by version instead of as text. The
eq and glob conditions keep treating a release as a string, so
existing release filters are unchanged.

Sentry can now emit {"op": "gte", "name": "event.release", "value":
"1.2.0"} without a new op in the DSL. A range is an "and" of two
conditions. Relays that predate this change compare the same JSON
lexicographically, which is accepted since managed Relays update within
days.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(protocol): Add a version rule condition for release constraints feat(protocol): Compare releases by version in comparison conditions Sep 25, 2026
Keep the item getters, the sampling context, and the schema untouched.
The generic filter wrapper that already adds the envelope client IP
retypes the four release paths as Val::Release, so only generic
inbound filters compare releases by version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(protocol): Compare releases by version in comparison conditions feat(filters): Compare releases by version in generic inbound filters Sep 25, 2026
Move the version ordering into a Release type local to relay-filter,
and evaluate the gt, gte, lt, and lte conditions there. A string field
in a generic filter comparison is parsed as a release on both sides
and ordered by version; any other field compares as the condition does
itself. No release paths are listed, and relay-protocol is untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(filters): Compare releases by version in generic inbound filters feat(filters): Compare strings as release versions in generic inbound filters Sep 25, 2026
Replace the release parser with a Semver type built on the semver
crate. A release must carry a full semantic version, optionally behind
a Sentry package prefix such as "myapp@1.2.3". Ordering follows semver
precedence, so build metadata is ignored and pre-release identifiers
compare per the spec.

Add an integration test that filters events by a release range through
a processing Relay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(filters): Compare strings as release versions in generic inbound filters feat(filters): Compare releases as semantic versions in generic inbound filters Sep 28, 2026
Replace the semver interpretation of gt, gte, lt, and lte in generic
filters with a dedicated "semver" condition, like "glob" and "cidr".
The comparator is a field of the condition, so the author can validate
it and the version before emitting the condition:

  {"op": "semver", "name": "event.release",
   "comparator": "gte", "value": "1.2.0"}

Comparators are eq, gt, gte, lt, and lte. An unknown comparator or a
value that is not a semantic version makes the condition unsupported,
which relay_validate_rule_condition reports, and it never matches.

The filter crate needs no special evaluation anymore and goes back to
RuleCondition::matches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(filters): Compare releases as semantic versions in generic inbound filters feat(protocol): Add a semver rule condition Sep 29, 2026
shellmayr and others added 2 commits September 29, 2026 11:46
Test each layer for what only it owns. The Semver type covers parsing
and precedence. The condition covers a truth table over all comparators,
the inputs that never match, and the unknown comparator. One
integration test proves the filter end to end with two events.

Drop the filter crate test, since the crate has no production change,
and the roundtrip test, since the snapshot covers the serialized shape.
Reduce the Semver type to parsing and one comparison method.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr shellmayr changed the title feat(protocol): Add a semver rule condition feat(generic-filters): Add a semver rule condition Sep 29, 2026
@shellmayr
shellmayr marked this pull request as ready for review September 29, 2026 10:27
@shellmayr
shellmayr requested a review from a team as a code owner September 29, 2026 10:27
@shellmayr
shellmayr requested a review from a team September 29, 2026 10:32
Comment thread relay-protocol/src/semver.rs Outdated
///
/// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package,
/// such as `myapp@1.2.3`. The version must be a full semantic version with three components.
pub(crate) struct Semver(Version);

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.

Why do we need another wrapper here?

There is already sentry_release_parser::Release, can we use that?

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.

sentry_release_parser::Release afaik matches more as it allows the package specifiers. Depending whether that's wanted I think we either can go with sentry_release_parser::Release or just semver.

I almost assume that supporting the package specifiers is an upside. Though not sure how the matching for partial versions is. For example:

org.example.FooApp@1.0 >= 1.0 # is this true if the package is missing? Should it be?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Switched to using sentry_release_parser

Comment thread relay-protocol/src/condition.rs Outdated
/// The comparison to apply between the field and the value.
pub comparator: SemverComparator,
/// The semantic version to compare the field against.
pub value: String,

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.

As with IpNetworks, we should make value a parsed semver type (Version or whatever). This then requires Serialize + Deserialize, but it is better than parsing on every evaluation.

Comment thread relay-protocol/src/semver.rs Outdated
///
/// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package,
/// such as `myapp@1.2.3`. The version must be a full semantic version with three components.
pub(crate) struct Semver(Version);

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 can be public as the module itself isn't public. We generally try to avoid these restricted visibility modifiers as pub(crate).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed

Comment thread relay-protocol/src/semver.rs Outdated
///
/// A release is either a version such as `1.2.3-rc.1+build` or a Sentry release with a package,
/// such as `myapp@1.2.3`. The version must be a full semantic version with three components.
pub(crate) struct Semver(Version);

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.

sentry_release_parser::Release afaik matches more as it allows the package specifiers. Depending whether that's wanted I think we either can go with sentry_release_parser::Release or just semver.

I almost assume that supporting the package specifiers is an upside. Though not sure how the matching for partial versions is. For example:

org.example.FooApp@1.0 >= 1.0 # is this true if the package is missing? Should it be?

shellmayr and others added 3 commits September 29, 2026 14:51
Use sentry-release-parser instead of the semver crate, so the condition
reads releases the way Sentry does. Versions have one to four
components, and missing components are zero.

Define how packages compare. A value without a package matches releases
of every package. A value with a package only matches releases of that
package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The module is private, so restricted visibility adds nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Make the value of the semver condition a parsed type, like IpNetworks,
so the release is parsed when the condition is deserialized and not on
every evaluation.

A string without a version still deserializes and keeps its text, so
one bad condition cannot fail a project config. The condition reports
it as unsupported and never matches.

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

@Dav1dde Dav1dde 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.

LGTM, would rename semver to release as it is now matching releases no longer just semver (open for bikeshedding here).

Also left some nits.

Comment thread relay-protocol/src/condition.rs Outdated
/// condition with such a `value` is not [supported](RuleCondition::supported), so that the author
/// of the condition can reject it.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SemverCondition {

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 think we should call this ReleaseCondition now, the docs also talk of a release.

Comment thread relay-protocol/src/semver.rs Outdated
/// compares with no release.
#[derive(Debug, Clone, PartialEq)]
pub struct Semver {
raw: String,

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 think raw is redundant with Parsed since we can reconstruct a for comparing equivalent version specifier from the parsed variant.

That should also make the second separate type unnecessary 🤔

Comment thread relay-protocol/src/semver.rs Outdated
///
/// Versions order by their numeric components, then by semver precedence of the pre-release.
/// A pre-release orders below its final release. Build codes are ignored.
pub fn compare(&self, release: &str) -> Option<Ordering> {

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.

Why did you opt for a method rather than implementing PartialOrd / Ord?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't think we can make PartialOrd hold w.r.t. the asymmetric nature of the comparison: a rule that filters all packages at verison 1.2.3 (1.2.3) needs to filter mypackage@1.2.3, but when the rule is mypackage@1.2.3, it should not filter any 1.2.3 - maybe I'm missing something here, but I don't think we can do that with PartialOrd without violating some of the conditions that the docs point out it must hold?

shellmayr and others added 3 commits October 5, 2026 16:16
Keep the package, the numeric components, and the pre-release of the
configured release, and rebuild the release string from them when
serializing. This removes the raw string and the second parsed type.

A configured release without a version, such as a commit hash, now
deserializes to a value of None. The condition stays unsupported and
never matches, as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The condition compares releases, which carry an optional package in
front of the version. Rename the types, the constructor, and the op
name from semver to release to match. The op is not emitted by Sentry
yet, so the wire format can still change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;

pub use crate::release::Release;

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.

Why is this pub use?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tried running CI locally without pub use and the docs generation fails, so I think we need it despite it being unused outside of this module.

@shellmayr shellmayr changed the title feat(generic-filters): Add a semver rule condition feat(generic-filters): Add a release rule condition Oct 6, 2026
@linear-code

linear-code Bot commented Oct 6, 2026

Copy link
Copy Markdown

TET-3061

shellmayr added a commit to getsentry/sentry that referenced this pull request Oct 6, 2026
Relay renamed the version comparison condition from `semver` to `release`
in getsentry/relay#6430. Emit the new op so a Relay with the condition
matches it, and name the types after it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shellmayr
shellmayr added this pull request to the merge queue Oct 8, 2026
Merged via the queue into master with commit 4a5550e Oct 8, 2026
38 checks passed
@shellmayr
shellmayr deleted the shellmayr/feat/version-rule-condition branch October 8, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants