feat(api): accept and return multiple alert notification channels - #2846
Conversation
🦋 Changeset detectedLatest commit: 57f6d58 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThe PR exposes multi-channel alert configuration through the internal API, external v2 API, and MCP alert tool while retaining the singular compatibility field.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains within the eligible follow-up scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/api/src/mcp/tools/alerts/saveAlert.ts | Converts singular or plural MCP channel input into the canonical array before invoking shared alert controllers. |
| packages/api/src/mcp/tools/alerts/schemas.ts | Extends the MCP schema with bounded channel arrays and runtime cross-field validation. |
| packages/api/src/routers/api/alerts.ts | Adds masked plural channels to internal alert read responses while preserving the established response policy. |
| packages/api/src/routers/external-api/v2/alerts.ts | Exposes plural channels through external alert requests, responses, and generated API documentation. |
| packages/api/src/utils/externalApi.ts | Translates legacy and multi-channel alert documents into compatible external response shapes. |
| packages/api/openapi.json | Documents channel-array constraints, compatibility behavior, and full-replacement update semantics. |
Reviews (2): Last reviewed commit: "test(api): pin the legacy channel-only u..." | Re-trigger Greptile
Deep Review✅ No critical issues found. This is a well-scoped, additive change with strong test coverage. The team-ownership check for referenced webhooks was independently verified as sound (deduped ids counted against a team-scoped query, so a cross-team or non-existent id always lowers the count and rejects), no cross-tenant exposure is introduced, the changeset is present and correctly bumped, and OpenAPI JSDoc and the generated 🟡 P2 -- recommended
🔵 P3 nitpicks (7)
Reviewers (10): correctness, testing, maintainability, api-contract, security, kieran-typescript, adversarial, project-standards, agent-native, learnings-researcher. Testing gaps:
|
v2 alert responses carry the full channels array with the legacy channel field mirroring the first entry, so existing consumers are unaffected and a response body can still be PUT back unchanged. OpenAPI components document the new shape and the channel/channels input rule.
The internal /api/alerts route gained `channels` with no test coverage: create with several channels, a legacy single-channel alert gaining channels in the list response, a PUT round-trip, and the rejection cases (neither field, channel/channels mismatch, duplicates, over the cap, and a cross-team webhook hidden among valid ones). Also document that updates replace rather than merge -- a client sending only the legacy `channel` reduces a multi-channel alert to one target -- in the OpenAPI update schema, the MCP tool description an agent reads, and the changeset.
Updates are a full replace, so a client that predates `channels` and sends only `channel` reduces a multi-channel alert to that one target. That behaviour is documented but was asserted nowhere -- the round-trip test echoes both fields back, so it never exercised the collapse.
2125c82 to
57f6d58
Compare
🔴 Tier 4 — CriticalTouches authentication, tenancy data models, the public API or shipped database config — or substantially changes 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
|
E2E Test Results✅ All tests passed • 276 passed • 1 skipped • 885s
Tests ran across 4 shards in parallel. |
Exposes multi-channel alerts through the v2 external API, the internal API, and the MCP
clickstack_save_alerttool. Writes accept achannelsarray, and every alert response carries bothchannelsand the legacychannel.What changed
alertSchema(shared by/api/alertsand/api/v2/alerts) takeschanneland/orchannels, replacing its local channel definition with the shared one from common-utils.validateAlertInputchecks every referenced webhook exists and belongs to the team in a single query instead of one per channel.makeAlertpersists the canonicalchannelsarray withchannelmirrored tochannels[0].v2 responses gain
channels; the internal API also returnschannelsbut maskswebhookIdto{type}, matching the existing policy forchannel. OpenAPI documents the new shape and the input rule.The MCP tool accepts
channelswith hand-rolled cross-field validation, since the MCP SDK cannot serialiseZodEffects.Key decisions
Updates replace rather than merge. A client that sends only the legacy
channelwhen updating a multi-channel alert reduces it to that one target. Merging would make channels unremovable by legacy clients, so the behaviour stands and is documented in the changeset, the OpenAPI update schema, and the MCP tool description.Bulk webhook check by count.
countDocuments({_id: {$in: uniqueIds}, team})compared against the deduplicated id count. A cross-team or non-existent id can only lower the count, so it still rejects — and ids are validated as ObjectIds before reaching$in.Impact
Additive for existing consumers:
channelis still accepted on input and present on every response.channelwas removed from therequiredlist of the create and update request schemas, since either field now satisfies the requirement.Editing a multi-channel alert in the app still replaces
channelswith the single UI-selected channel, until the alert forms support the full list.Implementation detail
The round-trip case is covered end to end: a response body is PUT back unchanged and the alert keeps both channels. Rejections are covered for a channel/channels mismatch, neither field, duplicates, exceeding the cap, and a cross-team webhook hidden among valid ones — on both the external and internal routes, and on the MCP tool.
Verification: 52 external-api, 43 internal, 31 MCP integration tests;
yarn lint:openapiclean.