Skip to content

Sentry alerts

Sentry classifies every captured event so alert rules can route by surface (auction.*, payment.*, identity.*, moderation.*, infra.*). The classifier itself ships from packages/observability; per-spec classifiers are owned by their sub-system spec.

Spec 03 §11 #9 — moderation PR-E ships the concrete error-rate alert rules below. The exact thresholds are tuned for the moderation queue’s expected traffic (~50 ads/day MVP); revisit at Wave 2 when the post-launch baseline is established.

SeverityMeansRouting
criticalUser-visible outage or data integrity at riskPages the rotation immediately; declare an incident
highDegraded but not outage; on-call investigates this shiftSentry digest within 15 min
mediumAnomalous; review within 24hDaily digest
lowNoise budget; ignore unless trendingWeekly digest
ClassifierOwner specExamplesDefault severity
auction.bid-rejected04Validation order failure, rank desyncmedium
auction.close-missed0400:00 UTC close did not runcritical
auction.do-stuck04AuctionDO alarm fired but RPC did not advancecritical
payment.saga-failed05Saga ended in failed statehigh
payment.cascade-fired05Cascade to runner-up triggeredmedium (per-event), critical (rate spike)
payment.webhook-replay-stuck05Replay queue depth > thresholdhigh
identity.kyc-stalled02Stripe Identity verification stuck > 24hmedium
identity.setupintent-failed02First-bid SetupIntent failurehigh
moderation.queue-overflow03Pending moderation queue > thresholdhigh
moderation.classifier-disagrees03AI pre-classifier and human reviewer disagree above noise floormedium
infra.deploy-failed01wrangler deploy failed after 3 retrieshigh
infra.terraform-drift01terraform plan non-empty on schedulemedium
ws.fanout-lag06WS broadcast lag > 2smedium

The moderation pipeline emits structured logs with stable error_ids. PR-E pages on-call when any single error_id exceeds 10 events / 15 min (spec §11 #9). The rule shape is identical for every bucket; the table below lists each bucket and its severity so an operator triaging the page knows whether to escalate.

Upload pipeline (apps/api — owned by PR-A #88)

Section titled “Upload pipeline (apps/api — owned by PR-A #88)”
error_idSeverityWhat it meansLikely cause
ads.upload.dedupe_lookup_failedhighD1 outage on the per-user sha256 lookupD1 transient 5xx, deploy-time index drift
ads.upload.dedupe_cross_usercriticalDefence-in-depth assert fired — findCreativeAssetBySha256 returned a foreign assetSQL regression dropping the user_id predicate; data-integrity bug
ads.upload.r2_puthighR2 PUT at upload time failedR2 transient 5xx, quota, signature mismatch
ads.upload.d1_after_r2_failedmediumD1 insert failed after R2 PUT, R2 object successfully deleted (compensated)Concurrent UNIQUE collision on the dedupe race
ads.upload.r2_orphan_uncompensatedcriticalD1 insert AND the compensating R2 delete both failed — orphan R2 key recordedR2 sustained outage; needs sweeper backfill
ads.upload.moderation_post_commitcriticalq-moderation send failed after D1 commit — the ad is pending with no classifier signalQueues outage; classifier orphan
ads.upload.transforms_post_commithighq-transforms send failed after moderation send succeeded — renditions delayedQueues outage; sweeper-recoverable
ads.upload.deduped_moderation_post_commitcriticalSame as moderation_post_commit but on the dedupe pathQueues outage on dedupe twin
ads.upload.malformed_imagelowClient uploaded a structurally-corrupt PNG/JPEG/WebP (400 to caller)User-side; only paged on a rate spike (possible scan attempt)
ads.upload.bad_user_idcriticalasUserId re-brand failed on session.user_id — data-corruption signalUser-table corruption; needs immediate triage

Transforms consumer (apps/queue — owned by PR-B #106)

Section titled “Transforms consumer (apps/queue — owned by PR-B #106)”
error_idSeverityWhat it meansLikely cause
transforms.r2_gethighR2 GET on the EXIF-stripped original threwR2 transient outage
transforms.r2_object_missingmediumR2 GET returned null — object was deleted between upload and consumerGDPR purge race; uploader DELETE before classifier runs
transforms.runmediumPhoton decode / WASM errorMalformed image bytes slipped past the upload sniff (rare)
transforms.r2_puthighOne of the three rendition PUTs failed; orphan_keys payload names the leaked R2 keysR2 transient outage
transforms.d1_updatehighD1 UPDATE failed after R2 PUTs succeeded; full orphan_keys triple in payloadD1 transient outage
transforms.bad_user_idcriticalasUserId re-brand on creative_assets.user_id failedAsset-table corruption
transforms.unexpectedcriticalOuter-catch escape — real handler bugCode defect; treat as P1

Admin queue API + UI (apps/admin — owned by PR-C #108 + PR-D #110)

Section titled “Admin queue API + UI (apps/admin — owned by PR-C #108 + PR-D #110)”
error_idSeverityWhat it meansLikely cause
mod.already-claimedlowTwo admins clicked claim on the same ad within 10 minExpected UX collision; only paged on a sustained spike
mod.state-conflictmedium§10 E9 race — SLA cron or other admin flipped the row firstAdmin lost a decision race; check the audit log for the other decision
mod.bad-jsonlowAdmin client posted malformed JSON to /api/moderation/:ad_id/decideAdmin UI bug; only paged on a rate spike
mod.bad-payloadlowSchema fail (verdict, reason_code, notes type, verdict↔reason_code mismatch)Admin UI bug; only paged on a rate spike

All critical classifiers above page the on-call rotation immediately (no debounce). All high classifiers digest every 15 minutes to #oncall-moderation. All medium and low classifiers digest to the daily #sentry-digest.

When the official Sentry provider for our setup is available, infra/terraform/sentry.tf should declare each row in the tables above as a sentry_metric_alert resource keyed off the error_id tag with aggregate = "count()", threshold = 10, and window = 15 (minutes). Until then the rules are manually configured in the Sentry UI; the rule names should match the error_id 1:1 so an audit (or a future Terraform import) is mechanical. The on-call runbook below documents the manual-setup steps.

Manual Sentry rule setup (Wave 1, until Terraform lands)

Section titled “Manual Sentry rule setup (Wave 1, until Terraform lands)”
  1. Sentry → Alerts → Create Alert Rule → Metric Alert.
  2. Conditions: event.tags["error_id"] equals "<bucket>".
  3. Trigger: count() above 10 over 15m.
  4. Action: PagerDuty integration nyl-oncall-moderation (critical) or #sentry-digest Slack webhook (medium / low / high).
  5. Save with name = the error_id verbatim.

Repeat for every row in the spec-03 tables above. The infra/runbook/sentry-rules.csv (TBD — placeholder) tracks which rules are set and by whom; review on the first Monday of each month.