Repository navigation
feat(web): anomalies live in the alerts hub - #2320
Confidence 6/10 · 2 issues to address
🟡 Confidence 6/10 · needs attention. Two untested correctness gaps on the new anomaly editor paths; the incident, anomalies-detail and lab files went unread as the pass ran out of time.
2 warnings −2 · 1 note · tests partial −1 · risk medium · 0/1 new units observable −1
Moves anomalies into the Alerts hub: an Anomaly rule mode, an anomaly evidence incident page, and /anomalies redirecting to the hub. Two correctness gaps remain in the new anomaly branches. The McpQueryError issues in page/registry.ts are unrelated MCP tool failures the diff does not touch. Unread: incident page, anomalies-detail, kit and lab files.
- The Inbox gains an Anomalies view driven by
signalandanomalyStatus - Rule editor gains an Anomaly mode with scope and per-tier routing
- Anomaly incidents draw the detector's evidence instead of the rule chart
/anomaliesredirects to/alerts?signal=anomaly; the list page is removed
Findings
🟠 Warning · F12 · An anomaly rule routed by an escalation chain cannot be saved
correctness · apps/web/src/page/alerts/editor/derive.ts:136-138
anomalyIssues counts only destinationIds and escalation.destinationIds, while isNotifyMissingOf (editor/view.ts:112) also counts escalationChainId and escalation.escalationChainId as routing. A scoped anomaly rule whose tier starts a chain shows "Who gets notified" as done but saveIssues adds "Who gets notified", so the action bar prints "Fix before saving" and Save stays disabled.
Treat a chain as routing here too: add `form.escalationChainId === null` and `(form.escalation?.escalationChainId ?? null) === null` to the destination condition, mirroring `isNotifyMissingOf`.
🟠 Warning · F13 · Leaving Anomaly mode for SQL skips carryThreshold, saving a threshold 100× too high
correctness · packages/web-data/src/alerts/rule-modes.ts:162-163
leaveAnomaly restores the Built-in error-rate default threshold (a percent form value), and the SQL branch returns it verbatim, skipping the carryThreshold the Built-in → SQL branch at line 165 applies. formThresholdToDomain converts percent only for percent signals, so raw_query stores the raw number: switching an anomaly rule to Query mode saves 5 where an equivalent SQL rule saves 0.05.
if (current === "anomaly") {
const left = leaveAnomaly(form)
if (next === "builtin") return { form: left, notice: null }
const moved = moveToKind(left, next)
return { form: carryThreshold(left, moved.form), notice: moved.notice }
}
Still open from earlier reviews
- 🔵 Note · F5 · The Signals row's label points at a
div, not at the toggle group ·apps/web/src/page/alerts/editor/view-query.ts:381
Fixed since the last review
- ✅
F10 · A just-resolved anomaly reads "now ago" in the resolved list - ✅
F11 · Test named for carrying both tiers' destinations asserts neither
Production impact
Open errors in the changed files (2)
| Issue | Service | Occurrences | File |
|---|---|---|---|
| @maple/mcp/errors/McpQueryError (5 issues) | maple-chat | 33 | apps/web/src/page/registry.ts |
| @maple/mcp/errors/McpQueryError (5 issues) | maple-ai | 15 | apps/web/src/page/registry.ts |
After this merges, Maple checks whether they stop.
Production traffic of the changed files (last 7 days)
| File | Calls/day | Busiest operations |
|---|---|---|
apps/web/src/page/alerts/incident/view.ts |
26 | preview 26/day, 8.9% err |
apps/web/src/page/alerts/rule/view-overview.ts |
26 | preview 26/day, 8.9% err |
What was checked
- Resolved anomalies now read "just now" via
formatRelativeFrom(hub/view-anomalies.ts:136) leaveAnomalycarries both tiers' destinations, asserted inanomaly-rule.test.ts:52- Hub Anomalies view reads
openIncidentsfor the open side (hub/view-anomalies.ts:207)
Observability coverage: 0 of 1 changes observable
| Change | Kind | Observable | Evidence |
|---|---|---|---|
| incident page → anomalies.getIncident / getIncidentTimeseries | outbound call | no | FetchIncidentAnomaly/Timeseries (incident/command.ts) reuse the existing MapleApiV2Client calls in apps/web, which emit no client spans; the dashboard sends no telemetry of its own |
5301042 · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.
Annotations
Check warning on line 138 in apps/web/src/page/alerts/editor/derive.ts
maple-review-bot / Maple / review
correctness: An anomaly rule routed by an escalation chain cannot be saved
`anomalyIssues` counts only `destinationIds` and `escalation.destinationIds`, while `isNotifyMissingOf` (`editor/view.ts:112`) also counts `escalationChainId` and `escalation.escalationChainId` as routing. A scoped anomaly rule whose tier starts a chain shows "Who gets notified" as `done` but `saveIssues` adds "Who gets notified", so the action bar prints "Fix before saving" and Save stays disabled.
Check warning on line 163 in packages/web-data/src/alerts/rule-modes.ts
maple-review-bot / Maple / review
correctness: Leaving Anomaly mode for SQL skips `carryThreshold`, saving a threshold 100× too high
`leaveAnomaly` restores the Built-in error-rate default threshold (a percent form value), and the SQL branch returns it verbatim, skipping the `carryThreshold` the Built-in → SQL branch at line 165 applies. `formThresholdToDomain` converts percent only for percent signals, so `raw_query` stores the raw number: switching an anomaly rule to Query mode saves `5` where an equivalent SQL rule saves `0.05`.