Skip to content

feat(eventrecorder): replace event output schema - #5409

Open
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/events.v2
Open

feat(eventrecorder): replace event output schema#5409
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/events.v2

Conversation

@siavashs

@siavashs siavashs commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Replace the original event recorder schema with events/v2. Alert labels,
alert annotations, group labels, silence annotations, and muted-alert labels
are now encoded as maps instead of nested ordered label pairs.

This is a breaking change for every event recorder output. JSON consumers must
handle the new map-based fields, and protobuf consumers must regenerate their
bindings from proto/eventrecorder/events/v2/events.proto.

Remove the legacy eventrecorderpb schema and generated bindings. Register the
new schema as the event recorder Buf module and use it directly for all file,
webhook, Kafka, and stdout outputs without version selection or conversion.

Event producers now construct opaque eventrecorder.Event values through
snapshotting constructors instead of depending on protobuf types. Destinations
receive the typed Event and serialize it as JSON or protobuf. Event metadata is
attached without mutating the constructed payload.

Also include silence annotations in recorded events.

BREAKING CHANGE: Event recorder outputs now use the events/v2 schema and
map-based label and annotation fields. The legacy eventrecorderpb wire format
is no longer supported.

Signed-off-by: Siavash Safi siavash@cloudflare.com

[CHANGE] eventrecorder: The output data format now uses the new `events/v2` schema. This is a breaking change: alert labels, alert annotations, group labels, silence annotations, and muted-alert labels are JSON maps, and protobuf consumers must use the new schema.

@Spaceman1701 Spaceman1701 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this change is good in principle, but I don't love that we have to maintain a mapping between v1 and v2 events... I'm generally a little nervous about introducing v2 in general.

How would you feel about changing the v1/v2 selection to be at event construction time? Each New*Event function could pick a v1 or v2 version dynamically. This would at least make it harder to forget about the v1 events when making a change.

Comment thread eventrecorder/kafka.go Outdated
// protobuf) and queues it for asynchronous delivery. It returns the
// serialized size (for the bytes-written metric).
func (ko *KafkaOutput) SendEvent(event *eventrecorderpb.Event) (int, error) {
func (ko *KafkaOutput) SendEvent(event proto.Message) (int, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if there could be a common interface that's more specific than proto.Message to user here. As it is, it makes the API a fair bit less type safe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Outputs shouldn't really care about the message contents, that what the proto.Message does here.
Considering you other comment with this one, if we want to make the outputs use a mode specific type, then we have to delay the v1/v2 translation to be requested by the outputs before encoding.

@siavashs

Copy link
Copy Markdown
Contributor Author

I think this change is good in principle, but I don't love that we have to maintain a mapping between v1 and v2 events... I'm generally a little nervous about introducing v2 in general.

My assumption was that eventrecorder v1 will be deprecated and removed until we reach release v1, then there will be no translation until we have v3, etc.

How would you feel about changing the v1/v2 selection to be at event construction time? Each New*Event function could pick a v1 or v2 version dynamically. This would at least make it harder to forget about the v1 events when making a change.

That is fine with me.

@Spaceman1701

Copy link
Copy Markdown
Contributor

How would you feel about changing the v1/v2 selection to be at event construction time? Each New*Event function could pick a v1 or v2 version dynamically. This would at least make it harder to forget about the v1 events when making a change.

That is fine with me.

Alright, that'd be my preference then - I just want to make sure it's easy and safe to add new events. And I'm fine with the idea of deprecating v1 pretty quickly - I doubt it's widely used at this point (though I'm not sure if we're considering it experimental).

@siavashs
siavashs marked this pull request as ready for review July 28, 2026 19:32
@siavashs
siavashs requested a review from a team as a code owner July 28, 2026 19:32
@siavashs
siavashs requested a review from Spaceman1701 July 28, 2026 19:32
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The event recorder now uses the breaking events/v2 protobuf schema. A structured Event wrapper handles construction, metadata, and serialization. Recorder outputs and Alertmanager event producers no longer depend on the removed v1 protobuf types.

Changes

Event recorder v2 migration

Layer / File(s) Summary
Schema contracts and event construction
proto/eventrecorder/..., eventrecorder/events.go, eventrecorder/events_test.go, buf.yaml, CHANGELOG.md, docs/configuration.md
Adds the v2 protobuf model, immutable event snapshots, constructors, map conversions, notification reasons, and constructor tests. Updates schema configuration and documentation.
Version-aware recorder and outputs
eventrecorder/recorder.go, eventrecorder/*_test.go, eventrecorder/file.go, eventrecorder/kafka.go, eventrecorder/stdout.go, eventrecorder/webhook.go
Queues Event values, attaches metadata, and delegates JSON or protobuf serialization through Event methods across all outputs.
Alertmanager producer migration
app/app.go, dispatch/dispatch.go, notify/*.go, silence/silence.go, inhibit/inhibit.go, provider/mem/mem.go
Updates startup, alert, notification, silence, inhibition, and memory-provider recording to use the new event constructors and callback type.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the event recorder output schema replacement.
Description check ✅ Passed The description clearly explains the breaking schema change, implementation details, release notes, and sign-off, but omits the template checklist.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

🧹 Nitpick comments (5)
eventrecorder/recorder.go (2)

326-331: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reload-induced drops are indistinguishable from queue-full drops.

Both paths increment eventsDropped with "unknown"/real event types, so operators can't tell a reload window from sustained backpressure. A distinct label value (e.g. "config_reload") would keep the signal readable, since the event type genuinely isn't known here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/recorder.go` around lines 326 - 331, Update the reload
contention path in RecordEvent, specifically the TryRLock failure branch, to
increment eventsDropped with the distinct "config_reload" label instead of
"unknown". Leave queue-full drop labeling by real event type unchanged.

232-240: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer a labeled break over goto for the drain loop.

Functionally correct, but a labeled loop expresses the intent without a forward jump.

♻️ Labeled break
-				for {
-					select {
-					case req := <-c.events:
-						c.marshalAndSend(req, outputs)
-					default:
-						goto drained
-					}
-				}
-			drained:
+			drain:
+				for {
+					select {
+					case req := <-c.events:
+						c.marshalAndSend(req, outputs)
+					default:
+						break drain
+					}
+				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/recorder.go` around lines 232 - 240, Replace the forward goto
in the event-draining loop within the recorder logic with a labeled break on the
enclosing for loop. Preserve the select behavior: continue processing available
events from c.events and exit the loop when the default branch is reached,
without changing marshalAndSend or the drained continuation.
eventrecorder/events.go (2)

409-427: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Nil matchers become empty {} entries in the encoded output.

Both matchersToV1/matchersToV2 here and the silence conversions (Lines 463-473 and 488-498) pre-size the slice and leave nil holes, which protobuf/protojson encode as empty messages rather than omitting them. Consumers then see phantom matchers with unspecified type and empty name. Appending only non-nil entries avoids that.

♻️ Skip nil entries instead of leaving holes
 func matchersToV2(matchers labels.Matchers) []*eventsv2.Matcher {
-	result := make([]*eventsv2.Matcher, len(matchers))
-	for i, matcher := range matchers {
-		if matcher != nil {
-			result[i] = &eventsv2.Matcher{Type: matcherTypeToV2(matcher.Type), Name: matcher.Name, Pattern: matcher.Value, Rendered: matcher.String()}
-		}
-	}
+	result := make([]*eventsv2.Matcher, 0, len(matchers))
+	for _, matcher := range matchers {
+		if matcher == nil {
+			continue
+		}
+		result = append(result, &eventsv2.Matcher{Type: matcherTypeToV2(matcher.Type), Name: matcher.Name, Pattern: matcher.Value, Rendered: matcher.String()})
+	}
 	return result
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events.go` around lines 409 - 427, Update matchersToV1 and
matchersToV2 to build result slices by appending only non-nil matchers instead
of pre-sizing them and leaving nil holes. Apply the same append-only handling to
the referenced silence conversion functions, preserving the existing field
mappings for non-nil entries.

345-354: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Panicking on schema mismatch will crash Alertmanager from a producer goroutine.

requireSchemaVersion is reached from RecordEvent on the request/dispatch path, so a single mis-versioned constructor call takes the process down instead of degrading recording. Since the event recorder is an observability side-channel, consider failing safe (log at error level and drop the event) and keeping the panic for tests only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events.go` around lines 345 - 354, Update requireSchemaVersion
and its RecordEvent call path to avoid panicking on production schema
mismatches: log the mismatch at error level and drop the event instead. Preserve
panic behavior only in test-specific validation, if an existing mechanism
supports it, and ensure valid schema versions continue recording unchanged.
eventrecorder/events_test.go (1)

86-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend nil-matcher coverage to serialization.

Construction is the cheap half; the interesting behavior is what a nil matcher/matcher-set produces on the wire (see the nil-hole note in eventrecorder/events.go). Adding a MarshalJSON/MarshalProtobuf assertion here would lock the encoded shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events_test.go` around lines 86 - 92, Extend
TestConstructorsHandleNilMatchers to serialize both the nil matcher in the alert
group and the nil matcher-set in NewSilenceCreatedEvent, using MarshalJSON and
MarshalProtobuf as appropriate. Assert the encoded output matches the documented
nil-hole shape in events.go, including preservation of the nil entries rather
than silently dropping or panicking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@eventrecorder/events_test.go`:
- Around line 86-92: Extend TestConstructorsHandleNilMatchers to serialize both
the nil matcher in the alert group and the nil matcher-set in
NewSilenceCreatedEvent, using MarshalJSON and MarshalProtobuf as appropriate.
Assert the encoded output matches the documented nil-hole shape in events.go,
including preservation of the nil entries rather than silently dropping or
panicking.

In `@eventrecorder/events.go`:
- Around line 409-427: Update matchersToV1 and matchersToV2 to build result
slices by appending only non-nil matchers instead of pre-sizing them and leaving
nil holes. Apply the same append-only handling to the referenced silence
conversion functions, preserving the existing field mappings for non-nil
entries.
- Around line 345-354: Update requireSchemaVersion and its RecordEvent call path
to avoid panicking on production schema mismatches: log the mismatch at error
level and drop the event instead. Preserve panic behavior only in test-specific
validation, if an existing mechanism supports it, and ensure valid schema
versions continue recording unchanged.

In `@eventrecorder/recorder.go`:
- Around line 326-331: Update the reload contention path in RecordEvent,
specifically the TryRLock failure branch, to increment eventsDropped with the
distinct "config_reload" label instead of "unknown". Leave queue-full drop
labeling by real event type unchanged.
- Around line 232-240: Replace the forward goto in the event-draining loop
within the recorder logic with a labeled break on the enclosing for loop.
Preserve the select behavior: continue processing available events from c.events
and exit the loop when the default branch is reached, without changing
marshalAndSend or the drained continuation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca68abff-5a35-433b-9f83-90d312cc9f52

📥 Commits

Reviewing files that changed from the base of the PR and between 846bdbf and 8f4037c.

⛔ Files ignored due to path filters (1)
  • eventrecorder/events/v2/events.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • CHANGELOG.md
  • app/app.go
  • buf.yaml
  • config/config_test.go
  • dispatch/dispatch.go
  • docs/configuration.md
  • eventrecorder/config.go
  • eventrecorder/events.go
  • eventrecorder/events_test.go
  • eventrecorder/file.go
  • eventrecorder/kafka.go
  • eventrecorder/kafka_test.go
  • eventrecorder/recorder.go
  • eventrecorder/recorder_test.go
  • eventrecorder/stdout.go
  • eventrecorder/webhook.go
  • eventrecorder/webhook_test.go
  • inhibit/inhibit.go
  • notify/event.go
  • notify/retry_stage.go
  • proto/eventrecorder/events/v2/events.proto
  • provider/mem/mem.go
  • silence/silence.go

@siavashs
siavashs marked this pull request as draft August 4, 2026 08:29
Replace the original event recorder schema with events/v2. Alert labels,
alert annotations, group labels, silence annotations, and muted-alert labels
are now encoded as maps instead of nested ordered label pairs.

This is a breaking change for every event recorder output. JSON consumers must
handle the new map-based fields, and protobuf consumers must regenerate their
bindings from proto/eventrecorder/events/v2/events.proto.

Remove the legacy eventrecorderpb schema and generated bindings. Register the
new schema as the event recorder Buf module and use it directly for all file,
webhook, Kafka, and stdout outputs without version selection or conversion.

Event producers now construct opaque eventrecorder.Event values through
snapshotting constructors instead of depending on protobuf types. Destinations
receive the typed Event and serialize it as JSON or protobuf. Event metadata is
attached without mutating the constructed payload.

Also include silence annotations in recorded events.

BREAKING CHANGE: Event recorder outputs now use the events/v2 schema and
map-based label and annotation fields. The legacy eventrecorderpb wire format
is no longer supported.

Signed-off-by: Siavash Safi <siavash@cloudflare.com>
@siavashs siavashs changed the title feat(eventrecorder): add v2 event schema feat(eventrecorder): replace event output schema Aug 4, 2026
@siavashs
siavashs marked this pull request as ready for review August 4, 2026 11:43

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@eventrecorder/events.go`:
- Around line 291-310: Extend the v2 events.Silence contract with a
receiver_matcher_sets field, regenerate its bindings, and update silenceToEvents
to snapshot silence.ReceiverMatcherSets using the same conversion and
nil-preservation behavior as MatcherSets. Add constructor coverage confirming
receiver-scoped matcher sets are retained in the recorded silence.
- Around line 302-305: Update the conversion logic around the matchers variable
to derive events.Silence.Matchers directly from silence.Matchers when legacy
matchers are present, and use silence.MatcherSets[0].Matchers only when the
legacy field is absent or empty. Add a regression test covering a silence with
legacy Matchers and no MatcherSets, ensuring the converted silence preserves its
selectors.
- Around line 331-345: The silenceMatcherRendered function currently defaults
unknown silencepb.Matcher types to labels.MatchEqual; instead, explicitly accept
only the four supported enum values and return an empty rendered value for any
unknown or unspecified type before calling labels.NewMatcher. Add a test
covering an unknown enum value and asserting an empty result.

In `@eventrecorder/recorder.go`:
- Around line 311-315: Update the clusterPosition handling in the event metadata
construction to avoid converting peer.Position() directly to uint32; use uint or
the protobuf field’s uint representation so the full cluster-size range is
preserved. Keep the nil-peer default at zero and continue passing
clusterPosition to event.withMetadata.

In `@notify/event.go`:
- Around line 106-111: The exported functions NewAlertResolvedEvent and
NewAlertGroupedEvent are missing documentation comments required by Go coding
standards. Add a full-sentence comment (ending with a period) above each
function that describes what it does. NewAlertResolvedEvent should document that
it creates an alert-resolved event, and NewAlertGroupedEvent should document
that it creates an alert-grouped event.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67c06043-497f-467e-894e-7c3cbfa6efaa

📥 Commits

Reviewing files that changed from the base of the PR and between 8f4037c and c24de91.

⛔ Files ignored due to path filters (2)
  • eventrecorder/eventrecorderpb/eventrecorder.pb.go is excluded by !**/*.pb.go
  • eventrecorder/events/v2/events.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (22)
  • CHANGELOG.md
  • app/app.go
  • buf.yaml
  • dispatch/dispatch.go
  • docs/configuration.md
  • eventrecorder/eventrecorderpb/eventrecorder.proto
  • eventrecorder/events.go
  • eventrecorder/events_test.go
  • eventrecorder/file.go
  • eventrecorder/kafka.go
  • eventrecorder/kafka_test.go
  • eventrecorder/recorder.go
  • eventrecorder/recorder_test.go
  • eventrecorder/stdout.go
  • eventrecorder/webhook.go
  • eventrecorder/webhook_test.go
  • inhibit/inhibit.go
  • notify/event.go
  • notify/retry_stage.go
  • proto/eventrecorder/events/v2/events.proto
  • provider/mem/mem.go
  • silence/silence.go
💤 Files with no reviewable changes (1)
  • eventrecorder/eventrecorderpb/eventrecorder.proto
🚧 Files skipped from review as they are similar to previous changes (13)
  • CHANGELOG.md
  • app/app.go
  • docs/configuration.md
  • eventrecorder/file.go
  • silence/silence.go
  • eventrecorder/kafka_test.go
  • notify/retry_stage.go
  • eventrecorder/kafka.go
  • provider/mem/mem.go
  • eventrecorder/webhook_test.go
  • eventrecorder/events_test.go
  • eventrecorder/stdout.go
  • proto/eventrecorder/events/v2/events.proto

Comment thread eventrecorder/events.go
Comment on lines +291 to 310
matcherSets := make([]*events.MatcherSet, len(silence.MatcherSets))
for i, set := range silence.MatcherSets {
if set == nil {
continue
}
for j, m := range ms.Matchers {
matcherSet.Matchers[j] = SilenceMatcherAsProto(m)
matchers := make([]*events.Matcher, len(set.Matchers))
for j, matcher := range set.Matchers {
matchers[j] = silenceMatcherToEvents(matcher)
}
matcherSets[i] = matcherSet
matcherSets[i] = &events.MatcherSet{Matchers: matchers}
}

var matchers []*eventrecorderpb.Matcher
if len(matcherSets) > 0 {
var matchers []*events.Matcher
if len(matcherSets) > 0 && matcherSets[0] != nil {
matchers = matcherSets[0].Matchers
}

return &eventrecorderpb.Silence{
Id: sil.Id,
Matchers: matchers,
MatcherSets: matcherSets,
StartsAt: sil.StartsAt,
EndsAt: sil.EndsAt,
UpdatedAt: sil.UpdatedAt,
CreatedBy: sil.CreatedBy,
Comment: sil.Comment,
return &events.Silence{
Id: silence.Id, Matchers: matchers, Annotations: stringMap(silence.Annotations), StartsAt: cloneTimestamp(silence.StartsAt),
EndsAt: cloneTimestamp(silence.EndsAt), UpdatedAt: cloneTimestamp(silence.UpdatedAt), CreatedBy: silence.CreatedBy,
Comment: silence.Comment, MatcherSets: matcherSets,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Record receiver matcher sets.

silenceToEvents snapshots MatcherSets but drops silence.ReceiverMatcherSets. The v2 events.Silence contract also has no receiver-matcher-set field. A receiver-scoped silence is recorded without its receiver restriction.

Add receiver_matcher_sets to the v2 proto, regenerate bindings, snapshot the field here, and add constructor coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events.go` around lines 291 - 310, Extend the v2 events.Silence
contract with a receiver_matcher_sets field, regenerate its bindings, and update
silenceToEvents to snapshot silence.ReceiverMatcherSets using the same
conversion and nil-preservation behavior as MatcherSets. Add constructor
coverage confirming receiver-scoped matcher sets are retained in the recorded
silence.

Comment thread eventrecorder/events.go
Comment on lines +302 to 305
var matchers []*events.Matcher
if len(matcherSets) > 0 && matcherSets[0] != nil {
matchers = matcherSets[0].Matchers
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve legacy Silence.Matchers values.

At Line 302, the converter derives events.Silence.Matchers only from silence.MatcherSets[0]. A valid silencepb.Silence can contain legacy Matchers with no MatcherSets. This path records no selector for that silence.

Convert silence.Matchers directly. Use the first matcher set only as a fallback. Add a regression test for legacy matchers without matcher sets.

Proposed conversion
 	var matchers []*events.Matcher
-	if len(matcherSets) > 0 && matcherSets[0] != nil {
+	if len(silence.Matchers) > 0 {
+		matchers = make([]*events.Matcher, len(silence.Matchers))
+		for i, matcher := range silence.Matchers {
+			matchers[i] = silenceMatcherToEvents(matcher)
+		}
+	} else if len(matcherSets) > 0 && matcherSets[0] != nil {
 		matchers = matcherSets[0].Matchers
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var matchers []*events.Matcher
if len(matcherSets) > 0 && matcherSets[0] != nil {
matchers = matcherSets[0].Matchers
}
var matchers []*events.Matcher
if len(silence.Matchers) > 0 {
matchers = make([]*events.Matcher, len(silence.Matchers))
for i, matcher := range silence.Matchers {
matchers[i] = silenceMatcherToEvents(matcher)
}
} else if len(matcherSets) > 0 && matcherSets[0] != nil {
matchers = matcherSets[0].Matchers
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events.go` around lines 302 - 305, Update the conversion logic
around the matchers variable to derive events.Silence.Matchers directly from
silence.Matchers when legacy matchers are present, and use
silence.MatcherSets[0].Matchers only when the legacy field is absent or empty.
Add a regression test covering a silence with legacy Matchers and no
MatcherSets, ensuring the converted silence preserves its selectors.

Comment thread eventrecorder/events.go
Comment on lines +331 to +345
func silenceMatcherRendered(matcher *silencepb.Matcher) string {
matcherType := labels.MatchEqual
switch matcher.Type {
case silencepb.Matcher_REGEXP:
matcherType = labels.MatchRegexp
case silencepb.Matcher_NOT_EQUAL:
matcherType = labels.MatchNotEqual
case silencepb.Matcher_NOT_REGEXP:
matcherType = labels.MatchNotRegexp
}
}

// NewSilenceCreatedEvent constructs a SilenceCreated event.
func NewSilenceCreatedEvent(silence *eventrecorderpb.Silence) *eventrecorderpb.EventData {
return &eventrecorderpb.EventData{
EventType: &eventrecorderpb.EventData_SilenceCreated{
SilenceCreated: &eventrecorderpb.SilenceCreatedEvent{
Silence: silence,
},
},
rendered := ""
if parsed, err := labels.NewMatcher(matcherType, matcher.Name, matcher.Pattern); err == nil {
rendered = parsed.String()
}
return rendered

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not render an unknown matcher type as equality.

At Line 332, the default matcher type is equality. For an unknown source enum, Line 328 emits TYPE_UNSPECIFIED but Line 343 emits an equality-form rendered matcher.

Return an empty rendered value for unknown types. Add a test with an unknown enum value.

Proposed fix
 func silenceMatcherRendered(matcher *silencepb.Matcher) string {
-	matcherType := labels.MatchEqual
+	var matcherType labels.MatchType
 	switch matcher.Type {
+	case silencepb.Matcher_EQUAL:
+		matcherType = labels.MatchEqual
 	case silencepb.Matcher_REGEXP:
 		matcherType = labels.MatchRegexp
 	case silencepb.Matcher_NOT_EQUAL:
 		matcherType = labels.MatchNotEqual
 	case silencepb.Matcher_NOT_REGEXP:
 		matcherType = labels.MatchNotRegexp
+	default:
+		return ""
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func silenceMatcherRendered(matcher *silencepb.Matcher) string {
matcherType := labels.MatchEqual
switch matcher.Type {
case silencepb.Matcher_REGEXP:
matcherType = labels.MatchRegexp
case silencepb.Matcher_NOT_EQUAL:
matcherType = labels.MatchNotEqual
case silencepb.Matcher_NOT_REGEXP:
matcherType = labels.MatchNotRegexp
}
}
// NewSilenceCreatedEvent constructs a SilenceCreated event.
func NewSilenceCreatedEvent(silence *eventrecorderpb.Silence) *eventrecorderpb.EventData {
return &eventrecorderpb.EventData{
EventType: &eventrecorderpb.EventData_SilenceCreated{
SilenceCreated: &eventrecorderpb.SilenceCreatedEvent{
Silence: silence,
},
},
rendered := ""
if parsed, err := labels.NewMatcher(matcherType, matcher.Name, matcher.Pattern); err == nil {
rendered = parsed.String()
}
return rendered
func silenceMatcherRendered(matcher *silencepb.Matcher) string {
var matcherType labels.MatchType
switch matcher.Type {
case silencepb.Matcher_EQUAL:
matcherType = labels.MatchEqual
case silencepb.Matcher_REGEXP:
matcherType = labels.MatchRegexp
case silencepb.Matcher_NOT_EQUAL:
matcherType = labels.MatchNotEqual
case silencepb.Matcher_NOT_REGEXP:
matcherType = labels.MatchNotRegexp
default:
return ""
}
rendered := ""
if parsed, err := labels.NewMatcher(matcherType, matcher.Name, matcher.Pattern); err == nil {
rendered = parsed.String()
}
return rendered
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/events.go` around lines 331 - 345, The silenceMatcherRendered
function currently defaults unknown silencepb.Matcher types to
labels.MatchEqual; instead, explicitly accept only the four supported enum
values and return an empty rendered value for any unknown or unspecified type
before calling labels.NewMatcher. Add a test covering an unknown enum value and
asserting an empty result.

Comment thread eventrecorder/recorder.go
Comment on lines +311 to +315
clusterPosition := uint32(0)
if peer := r.core.peer.Load(); peer != nil {
wrappedEvent.ClusterPosition = uint32(peer.Position())
clusterPosition = uint32(peer.Position())
}
event = event.withMetadata(timestamppb.Now(), r.core.instance, clusterPosition)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the cluster API before inspecting the Position implementation.
ast-grep outline cluster --items all --type method,function --match Position

# Inspect the return type, assignments, and range controls for Peer.Position.
rg -nP -C 8 'func\s+\([^)]*\bPeer\b[^)]*\)\s+Position\s*\(' cluster --glob '*.go'
rg -nP -C 5 '\bposition\s*=' cluster --glob '*.go'

# Confirm the receiving protobuf field type and all local uses.
rg -n -C 3 'cluster_position|ClusterPosition' proto/eventrecorder eventrecorder \
  --glob '*.proto' --glob '*.go'

Repository: prometheus/alertmanager

Length of output: 924


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cluster/cluster.go Position implementation =="
sed -n '650,675p' cluster/cluster.go

echo
echo "== cluster/cluster.go relevant position/index declarations =="
rg -n -C 4 '\bIndex\b|position|Position\s*\(' cluster/cluster.go

echo
echo "== eventrecorder/proto cluster_position usage =="
rg -n -C 4 'cluster_position|ClusterPosition|cluster_position ' proto eventrecorder --glob '*.proto' --glob '*.go' || true

echo
echo "== Go semantic simulation of out-of-range uint32 conversion =="
python3 - <<'PY'
for v in [-1, 0, 4294967294, 4294967295, 4294967296, 9223372036854775807, -2**63]:
    def to_uint32(v):
        return v & ((1 << 32) - 1)
    print(f"{v}: int(v)%2**32={v%2**32}, conversion-like={to_uint32(v)}")
PY

Repository: prometheus/alertmanager

Length of output: 5282


Remove the uint32 conversion for Peer.Position.

Peer.Position() returns the zero-based index in the sorted member slice. Since all is only returned by Peers(), the result is bounded by the current cluster size, not a generic int. Cast through uint or store the value as a uint in the protobuf message to preserve the full cluster-size range and avoid the non-portable signed-to-unsigned conversion.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 312-312: Narrowing a non-constant integer to a smaller fixed-width type (int8/int16/int32, uint8/uint16/uint32) can silently overflow or wrap, yielding negative or truncated values that are dangerous in size, length, or index logic. Validate the source value is within the target type's range before converting (e.g. bounds-check, or use a checked helper), and avoid narrowing untrusted or len()/parsed values.
Context: uint32(peer.Position())
Note: [CWE-190] Integer Overflow or Wraparound.

(integer-overflow-narrowing-conversion-go)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eventrecorder/recorder.go` around lines 311 - 315, Update the clusterPosition
handling in the event metadata construction to avoid converting peer.Position()
directly to uint32; use uint or the protobuf field’s uint representation so the
full cluster-size range is preserved. Keep the nil-peer default at zero and
continue passing clusterPosition to event.withMetadata.

Source: Linters/SAST tools

Comment thread notify/event.go
Comment on lines +106 to +111
func NewAlertResolvedEvent(groupInfo eventrecorder.AlertGroup, alert *types.Alert) eventrecorder.Event {
return eventrecorder.NewAlertResolvedEvent(groupInfo, groupedAlertEvent(alert))
}

func NewAlertGroupedEvent(groupInfo *eventrecorderpb.AlertGroupInfo, alert *types.Alert) *eventrecorderpb.EventData {
return &eventrecorderpb.EventData{
EventType: &eventrecorderpb.EventData_AlertGrouped{
AlertGrouped: &eventrecorderpb.AlertGroupedEvent{
Alert: groupedAlertAsProto(alert),
GroupInfo: groupInfo,
},
},
}
func NewAlertGroupedEvent(groupInfo eventrecorder.AlertGroup, alert *types.Alert) eventrecorder.Event {
return eventrecorder.NewAlertGroupedEvent(groupInfo, groupedAlertEvent(alert))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add comments for the exported constructors.

NewAlertResolvedEvent and NewAlertGroupedEvent have no exported-identifier comments. Add a full sentence for each function.

As per coding guidelines, “Comments on exported Go identifiers must be full sentences ending with a period.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@notify/event.go` around lines 106 - 111, The exported functions
NewAlertResolvedEvent and NewAlertGroupedEvent are missing documentation
comments required by Go coding standards. Add a full-sentence comment (ending
with a period) above each function that describes what it does.
NewAlertResolvedEvent should document that it creates an alert-resolved event,
and NewAlertGroupedEvent should document that it creates an alert-grouped event.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants