Skip to content

feat(opevents): attempt.success and attempt.failed operator events#989

Open
alexluong wants to merge 5 commits into
feat/logmq-opevent-parallelismfrom
feat/opevents-attempt-lifecycle
Open

feat(opevents): attempt.success and attempt.failed operator events#989
alexluong wants to merge 5 commits into
feat/logmq-opevent-parallelismfrom
feat/opevents-attempt-lifecycle

Conversation

@alexluong

@alexluong alexluong commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

implements #981

Adds two operator events, plus config-based optimizations along the way.

New topics: attempt.success and attempt.failed — one event per delivery attempt, emitted from logmq after the attempt is persisted. The payload matches the exhausted_retries shape. Note: existing * subscribers start receiving every attempt event on upgrade — worth a release-note callout.

Skip unnecessary work when config doesn't need it — with alerting disabled, failed attempts skip the alert logic and its Redis calls; with attempt events also unsubscribed, entries just persist and ack.

alexluong and others added 5 commits July 4, 2026 00:25
One shared payload shape (tenant, event, attempt, destination projection),
mirroring alert.attempt.exhausted_retries; the topic split exists for
subscription filtering. These fire once per delivery attempt, so the docs
call out the volume implication — including for "*" subscribers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The failed event joins plan()'s owed-events list, riding the existing
errgroup send + replay gate + ack/nack semantics. The success path sends
its event ungated before the ack — gating would cost a Redis key per
successful attempt (the dominant traffic) to dedup a rare redelivery
re-emit, and opevents are at-least-once anyway.

Every failed attempt's expected topic multiset in the characterization
suite gains one attempt.failed (and successes one attempt.success);
assertions that pinned arrival order now filter by topic. The sink gains
a composite attemptID/topic fail key so the exhausted-window test can
fail only the exhausted send — failing the whole attempt would cancel
the suppression Exec mid-flight instead of exercising clear-on-failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surfaced by the exhausted-window suppression test: a canceled ctx fails
both the exec and the cleanup Del, leaving the key claimed until its TTL.
Deliberately unfixed — bounded, self-healing, and rare.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
logmq's audit line inferred "delivered" from Emit returning nil, which is
also what a filtered topic or the noop emitter returns — with per-attempt
events that becomes a false audit line per attempt on deployments that
never subscribed. The emitter knows the difference, so the line moves to
where the knowledge lives: it fires iff an event was actually sent, and
gains the generated opevent id (previously unloggable — minted inside
Emit). Event.LogFields carries caller context; the payload constructors
populate attempt_id/event_id/destination so the line keeps the fields the
logmq version had, and emit call sites stay unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With every alert signal off, the failed path's replay gate protects
nothing — Evaluate is a stateless no-op and attempt.failed is
at-least-once like attempt.success — yet it still cost two Redis round
trips per failed attempt. Skip it: emit attempt.failed ungated and ack.
When no attempt topic is subscribed either, the pipeline can't produce
anything, so entries ack straight after persistence with no goroutine
spawned.

Both bools derive from existing config at construction — Evaluator
gains SignalsEnabled(), Emitter gains Enabled(topic) — so logmq never
re-implements signal or topic-filter semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexluong alexluong marked this pull request as ready for review July 3, 2026 19:43
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.

1 participant