Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Redact secrets in opevents sink configs with json:"-" tags - Fix router_test emitter harness to not silently ignore injected emitters - Add comment clarifying Nack safety (InsertMany is idempotent by attempt ID) - Fix %% escaping in e2e test assertion message - Drain HTTP response body for connection reuse; include body in 4xx errors - Panic early in NewAlertMonitor if emitter is nil - Skip exhausted_retries check when retryMaxLimit=0 (retries disabled) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
QA Results: Operation EventsPASS (14/14) — all 4 opevent topics verified end-to-end against a local HTTP sink.
Config overrides used: Test Casestenant.subscription.updatedTrigger: Destination create, update (topics change), delete, disable, enable — when tenant-level `topics` or `destinations_count` changes.
alert.destination.consecutive_failureTrigger: Delivery failures hitting threshold percentages of `consecutive_failure_count`.
alert.destination.disabledTrigger: Destination auto-disabled at 100% consecutive failure threshold (requires `auto_disable_destination: true`).
alert.event.exhausted_retriesTrigger: Delivery exhausts all retry attempts (`attempt_number > retry_max_limit`) for a retry-eligible event.
Payload SchemasOperationEvent envelopeAll events share this envelope: ```json
tenant.subscription.updated```json alert.destination.consecutive_failure```json alert.destination.disabled```json alert.event.exhausted_retries```json 🤖 Generated with Claude Code |
| Topics []string `json:"topics"` | ||
| PreviousTopics []string `json:"previous_topics"` |
There was a problem hiding this comment.
Is all topic represented as ['*'] or do we sent a list of topics regardless?
There was a problem hiding this comment.
It uses the same logic as when we retrieve Tenant. So * -> when any destination subscribes to *. Otherwise it's a list.
|
2 things I'm doubtful about
|
|
1: maybe |
|
We don't use the delivery terminology from an operator POV anymore, so |
Doesn't hurt until we need a new event type that's not associated with a tenant 😅 |
|
|
Introduces internal/opevents/ — the foundation for the operation events system. Includes OperationEvent envelope, Emitter with topic filtering and retry, Sink interface, NoopSink, and config integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HTTP sink with HMAC-SHA256 payload signing (X-Outpost-Signature header). MQ sink as thin adapter wrapping mqs.Queue for SQS, Pub/Sub, RabbitMQ. Config with nested sub-structs per sink type and NewSink() factory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…events emitter Replaces AlertNotifier with AlertEmitter (satisfied by opevents.Emitter). Consecutive failure alert evaluation now happens in logmq batch processor after log persistence, with per-entry ack/nack for at-least-once delivery. LogEntry carries Destination for alert context. Disable logic (DestinationDisabler, disabled alert) removed — deferred to Phase 4. - alert/: replace AlertNotifier with AlertEmitter, update data types to RFC format - logmq/: add AlertMonitor to BatchProcessor, per-entry alert eval post-InsertMany - deliverymq/: remove fire-and-forget alert goroutine, populate LogEntry.Destination - services/: move alert wiring from BuildDeliveryWorker to BuildLogWorker Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When consecutive failures reach 100%, disables the destination via DestinationDisabler and emits alert.destination.disabled event. Both disable action and alert emission return errors on failure, causing the logmq entry to be nacked and redelivered. Replay is safe: SADD is idempotent, DisableDestination is idempotent, consumers deduplicate events by ID. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Emit alert.event.exhausted_retries when a delivery exhausts all retry attempts. Uses the idempotence package to suppress duplicate alerts per destination within a configurable TTL window (default 1h). On emit failure, the idempotency key is cleared so MQ replay retries correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snapshot tenant topics and destinations_count before each destination mutation (create, update, delete, disable, enable), re-fetch after, and emit tenant.subscription.updated if either value changed. Best-effort: emission errors are logged but do not block the API response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove legacy alert HTTP callback tests and mock server. Add opevents mock server that captures OperationEvent envelopes with HMAC signature verification. Three e2e test flows: consecutive failure + auto-disable, exhausted retries, and subscription updated (create, update topics, delete). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Redact secrets in opevents sink configs with json:"-" tags - Fix router_test emitter harness to not silently ignore injected emitters - Add comment clarifying Nack safety (InsertMany is idempotent by attempt ID) - Fix %% escaping in e2e test assertion message - Drain HTTP response body for connection reuse; include body in 4xx errors - Panic early in NewAlertMonitor if emitter is nil - Skip exhausted_retries check when retryMaxLimit=0 (retries disabled) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the key was destination-only, so if two distinct events exhausted retries on the same destination within the TTL window, only the first emitted an alert. The key now includes event ID so each event's exhaustion is tracked independently. Also fixes the WindowSuppression test which was accidentally asserting the buggy behavior (two different events, expecting 1 alert). It now tests same-event replay suppression. A new PerEvent test covers the distinct-event case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… sink - Remove duplicate private topic consts from alert/monitor.go; use the exported opevents.Topic* constants from internal/opevents/event.go - NewSink now returns an error when topics are configured but no sink is set, preventing silent event loss Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change X-Outpost-Signature header from `sha256=<hex>` to `v0=<hex>` to
match the default webhook destination signature format configured in
config.go (SignatureHeaderTemplate: "v0={{.Signatures | join ","}}").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConsecutiveFailureCount 20→100, AutoDisableDestination true→false. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sted_retries Per review feedback — "attempt" better describes the entity that exhausted its retries than "event". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4c8472d to
590e16e
Compare
Summary
internal/opevents/package:Emitterinterface with topic filtering + 3-retry,Sinkinterface (HTTP with HMAC-SHA256 signing, MQ adapter wrappinginternal/mqs, Noop),OperationEventenvelope, config with nested sink sub-structsalert.destination.consecutive_failure— emitted at percentage thresholds (hard-coded 50/70/90/100%)alert.destination.disabled— emitted when destination auto-disabled at 100% thresholdalert.event.exhausted_retries— emitted when delivery exhausts all retry attempts, with per-destination suppression window via idempotence packagetenant.subscription.updated— emitted from API handlers on destination create/update/delete/disable/enable when tenant-level topics or destinations_count changes (best-effort)HTTP Sink Signature
The HTTP sink signs each request body with HMAC-SHA256 and sends the signature in the
X-Outpost-Signatureheader. The format matches the default webhook destination signature scheme:X-Outpost-Signature: v0=<hex>Verification example:
Commits
4941a835— opevents core (envelope, emitter, sink interface, noop sink, config)362e7496— HTTP sink (HMAC-SHA256) + MQ sink (adapter wrapping mqs.Queue)dec44cc0— Move consecutive_failure alert from deliverymq → logmqc8ab9f67— Destination auto-disable + disabled alert at 100% threshold7d7c8e5d— exhausted_retries with idempotency window suppression5bff3d18— tenant.subscription.updated from API handlersf31cb810— Replace alert e2e suite with opevents e2e suite50652d61— Align HTTP sink signature format with webhook default (v0=)Test plan
go test ./internal/alert/... ./internal/opevents/... ./internal/logmq/... ./internal/deliverymq/... ./internal/apirouter/... -count=1make test/e2e(requires full infra)🤖 Generated with Claude Code