Repository navigation
Conversation
processAlert hard-codes threshold evaluation after its scheduling and config setup. Add an AlertEvaluator interface: the first registered evaluator whose appliesTo matches evaluates the alert through an AlertEvaluationContext (the same history, notification, and persistence helpers the threshold path uses); otherwise the threshold path runs unchanged. An evaluator can also shift the evaluation clock, applied in processAlert and in getAlertWindowStart so window and history stay aligned. Evaluators are registered from @/extensions, called once on first lookup. It registers none here. The evaluator name is recorded on alerts.evaluate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
🦋 Changeset detectedLatest commit: b563d79 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🔴 Tier 4 — CriticalTouches authentication, tenancy data models, the public API or shipped database config — or substantially changes the query rendering engine, background tasks, the OTel pipeline, image build, or release CI. Why this tier:
Review process: Deep review from a domain expert. Synchronous walkthrough may be required. Stats
|
Duplicate code analysisNo new duplicate code introduced by this PR. jscpd compared this branch against |
| } | ||
| }; | ||
|
|
||
| const persistHistories = async ( |
There was a problem hiding this comment.
🔵 minor — persistHistories copies the threshold persist step, which still keeps its own two copies
The single-value branch (~1596-1606) and the time-series branch (~1830-1840) still do flush → attach analytics → updateAlertState by hand, and they already differ from persistHistories: they always attach analytics, while the helper skips analytics when the object is empty. Make both threshold exits call persistHistories(Array.from(histories.values())) so the 'same persistence behavior' the PR promises comes from one place.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
There was a problem hiding this comment.
Fixed in bf29ce8: both threshold exits now call persistHistories. Both already set backfilledBuckets, so the skip-empty-analytics branch never applies to them and their behavior is unchanged.
PR Review
2 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 2 minor 1 posted as inline comment(s) on the changed lines. 1 listed below. Findings outside the changed lines1 minor
Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides. |
Deep Review✅ No critical issues found. No P0/P1: this diff registers zero evaluators, so the threshold path is unchanged and the new extension-point machinery is latent. The findings below concern the contract the extension point establishes (which future evaluators will hit) plus one verified inefficiency in the shipped control flow. 🟡 P2 — recommended
🔵 P3 nitpicks (4)
Previous-comments reviewer verified all six prior review threads (lookup-inside-try, registration latch/rollback, wall-clock history clamp, context-contract tests, per-alert prefetch containment, non-persisting-evaluator error writes) are genuinely resolved in the current diff, with no regressions. Reviewers (11): correctness, adversarial, reliability, testing, maintainability, kieran-typescript, api-contract, project-standards, previous-comments, agent-native, learnings-researcher. Testing gaps:
No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond |
E2E Test Results✅ All tests passed • 462 passed • 1 skipped • 1270s
Tests ran across 4 shards in parallel. |
…clock - Resolve the evaluator and clock inside processAlert's try, so a throwing appliesTo/evaluationNow/registerExtensions is recorded on the alert and counted on alerts.evaluate instead of escaping. - Mark extensions registered only after registerExtensions succeeds; roll back a partial registration and retry on the next lookup, rather than silently routing alerts to the threshold path. - Ignore an evaluationNow later than the wall clock: previous-state lookups bound history by the wall clock, which only covers every window when the evaluation clock is not ahead of it. - Test the context contract: suppressed vs attempted sends, resolve via the context, merged persist errors, conditional analytics, and the up-front history window under a shifted or clamped clock. - Note that AlertEvaluationContext is not a stable API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… results through persistHistories Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Deep Review findings:
Unit tests (60) and |
| evaluators.push(evaluator); | ||
| }; | ||
|
|
||
| /** The registered evaluator for this alert, or undefined for the threshold |
There was a problem hiding this comment.
🔵 minor — findAlertEvaluator's doc comment is orphaned above ensureExtensionsRegistered
Two JSDoc blocks are stacked: "The registered evaluator for this alert, or undefined for the threshold path" sits directly above the ensureExtensionsRegistered doc. Editors and TypeDoc attach neither one to findAlertEvaluator. Fix: move that first comment onto findAlertEvaluator.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
…nd keep errors from non-persisting evaluators - getConsecutiveWindowHistories catches a failing window-start lookup per alert, so one broken evaluator no longer rejects every alert's prefetch - an evaluator that returns without persistHistories still writes the collected execution errors to the alert - replace `as any` casts in evaluators.test.ts, which put the api package over its ESLint warning ceiling - add a changeset Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Round 2 addressed in 0d94bcf:
Unit tests (62) and |
| attributes: { alert_source: alert.source ?? 'unknown' }, | ||
| attributes: { | ||
| alert_source: alert.source ?? 'unknown', | ||
| alert_evaluator: evaluator?.name ?? THRESHOLD_EVALUATOR_NAME, |
There was a problem hiding this comment.
🔵 minor — A failed evaluator lookup is recorded on alerts.evaluate as a threshold error
If findAlertEvaluator throws (because registerExtensions or an appliesTo throws), evaluator is still undefined when the finally runs. The error is then labelled alert_evaluator: 'threshold', so extension failures inflate the threshold path's error rate. Do the lookup inside its own try block and record a distinct label (for example 'unknown') when it fails.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Summary
processAlerthard-codes threshold evaluation after its scheduling and chart-config setup. This adds an extension point for other detection strategies, and the threshold path is unchanged when none is registered.AlertEvaluator(tasks/checkAlerts/evaluators.ts):appliesTo(alert),evaluate(ctx), and an optionalevaluationNow(alert, now)clock shift. The first registered evaluator that applies takes over; otherwise the threshold query runs as before.AlertEvaluationContext: whatprocessAlerthas already resolved (window, date range, unoptimized chart config, clients), plus the helpers the threshold path uses:getOrCreateHistory,trySendNotification,sendNotificationIfResolved, and a newpersistHistories. Evaluators therefore get the same silencing, resolve and persistence behavior.evaluationNowis applied in bothprocessAlertandgetAlertWindowStart, so the scheduled window and the history fetched up front stay aligned.@/extensions: aregisterExtensions()entry point, called once on the first evaluator lookup. It registers nothing in this repo.Supporting changes:
processAlert, the notification helpers now come before the threshold query (both paths use them); otherwise unchanged.trySendNotificationreturnsfalsewhen the send was suppressed (silenced) andtrueotherwise.alerts.evaluaterecords analert_evaluatorattribute:threshold, or the evaluator's name.How to test locally or on Vercel
cd packages/api && yarn ci:unit src/tasks/checkAlerts: covers evaluator lookup, a registered evaluator bypassing the threshold query, the shifted clock, error recording, and the fallback to the threshold path.make dev-int FILE=checkAlerts: the threshold path is unchanged (335 tests).🤖 Generated with Claude Code