You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
alerts: NR + Prom rules for brevo_send_errors / goroutine_panics / entitlement_regrade_failed + drift fixes
Adds three NR alert JSON files + parallel Prometheus rules for metric
counters that exist in production but had NO alert coverage. Sweep of
BugBash 2026-05-20 master-ledger entries against the actual emitted
metrics in api + worker /metrics endpoints.
newrelic/alerts/brevo-send-errors-spike.json
brevo_send_errors_total > 5 over 10m → CRITICAL.
Worker-side counter. SEND-side of the email pipeline (worker → Brevo
/v3/smtp/email). Complements the existing email-delivery-ratio alert
which is RECEIVE-side (Brevo → /webhooks/brevo). Send errors mean
messages never queue at Brevo, so the ratio alert is silent.
Source: worker/internal/email/brevo/brevo.go, counter in metrics.go.
newrelic/alerts/goroutine-panics-recovered.json
instant_goroutine_panics_total + instant_worker_goroutine_panics_recovered_total > 0
over 5m → CRITICAL.
Both counters tick when safego.Go's deferred recover() catches a panic
that would otherwise crash a background goroutine. Pod stays up, but
the panic almost always indicates a code defect that escaped tests.
Source: common/safego.Go.
newrelic/alerts/entitlement-regrade-failed.json
instant_entitlement_regrade_failed_total > 0 over 10m, FACET service →
CRITICAL.
Worker calls provisioner.RegradeResource() when resource.tier <
team.plan_tier (post-upgrade drift). A failure here = paying customer
still on lower-tier backend limits despite paying for the higher tier.
Inverse failure mode to billing-charge-undeliverable. Source:
worker/internal/jobs/entitlement_reconciler.go.
k8s/prometheus-rules.yaml — adds three groups mirroring the NR alerts
for environments where Prometheus + Alertmanager are the primary path
(instant-code-defects, instant-worker-entitlements,
instant-worker-email-send).
prometheus/alert-rules.yml — same three rules added to the standalone-
Prometheus format file so both apply paths converge.
k8s/website.yaml — NodePort retirement drift fix. The ConfigMap claimed
http://localhost:30080 (NodePort) was the dev API URL, but NodePort
was retired 2026-05-11 (CLAUDE.md confirms — instant-api Service is
ClusterIP only). Switched to http://localhost:8080 + documented the
port-forward command. Without this fix any operator copying the
ConfigMap value into a dev shell sees connection-refused.
Validation:
- All three NR JSON files: `jq empty` clean.
- k8s/prometheus-rules.yaml + prometheus/alert-rules.yml: `python3 -c "import yaml; list(yaml.safe_load_all(...))"` clean.
Apply path:
- NR alerts: operator runs `infra/newrelic/apply.sh` (idempotent
delete-then-create).
- Prometheus rules: operator applies k8s/prometheus-rules.yaml via
kubectl OR reloads Prometheus with the file_sd alert-rules.yml.
Coverage block per CLAUDE.md rule 17:
Symptom: prod metrics with no alert (brevo_send_errors_total,
instant_goroutine_panics_total,
instant_worker_goroutine_panics_recovered_total,
instant_entitlement_regrade_failed_total)
Enumeration: grep 'Name: "' api/internal/metrics/metrics.go
worker/internal/metrics/metrics.go, then grep -rn
'<name>' infra/newrelic/ infra/prometheus/ infra/k8s/
Sites found: 4 metrics with zero alert coverage in either NR or Prom
Sites touched: 4 — one alert file per metric, mirrored to both apply
paths (NR json + k8s/prometheus-rules.yaml +
prometheus/alert-rules.yml)
Coverage test: Existing `for f in newrelic/alerts/*.json; do jq empty
"$f"; done` + python3 yaml.safe_load_all over the two
Prom rule files (already part of apply.sh validation).
Live verified: Not applicable — alert files codify intent; the
operator must run apply.sh + reload Prometheus to
activate. Verify-live: trigger a synthetic panic via
/admin/test-panic (if exposed) OR wait for a real
occurrence, confirm NR violation opens within 5m.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"name": "instant-worker — entitlement_regrade_failed > 0 [tier change not delivered to backend]",
3
+
"type": "NRQL",
4
+
"description": "Fires on ANY increment of `instant_entitlement_regrade_failed_total`. The entitlement_reconciler job notices when a resource.tier is below the team's current plan tier (post-upgrade drift) and calls provisioner.RegradeResource() to bring the backend (Postgres dedicated pod / Redis ACL / Mongo user / NATS account) up to the paid limits. A failure here means a CUSTOMER who already paid for a higher tier is still running on the lower-tier backend — they have NOT received what they paid for. This is a paying-customer-impact signal. Pair with billing-charge-undeliverable (which catches the inverse: tier-not-translated-to-DB) — both surface the same problem class. Source: worker/internal/jobs/entitlement_reconciler.go; counter labelled by service (postgres/redis/mongo/queue). BugBash 2026-05-20.",
5
+
"enabled": true,
6
+
"nrql": {
7
+
"query": "SELECT sum(instant_entitlement_regrade_failed_total) FROM Metric FACET service"
"description": "Page on ANY increment of `instant_goroutine_panics_total` (api) or `instant_worker_goroutine_panics_recovered_total` (worker). Both counters are incremented by the safego.Go() wrapper when a deferred recover() catches a panic that would otherwise crash a background goroutine. Recovered panics keep the pod up but ALMOST ALWAYS indicate a real code defect — a nil map access, a divide-by-zero, or a missed error path that should have been a regular return. Each occurrence is a signal that the code shipped with a bug the test suite didn't catch. Threshold is ABOVE 0 (any single panic pages) — but with thresholdDuration=300s + aggregationWindow=60s so a one-off panic with no recurrence still notifies the operator without alert-storming. Source: common/safego package; counters in api/internal/metrics/metrics.go + worker/internal/metrics/metrics.go. BugBash 2026-05-20.",
5
+
"enabled": true,
6
+
"nrql": {
7
+
"query": "SELECT sum(instant_goroutine_panics_total) + sum(instant_worker_goroutine_panics_recovered_total) FROM Metric"
0 commit comments