@checkstack/incident-backend 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +388 -0
- package/drizzle/0005_real_leper_queen.sql +4 -0
- package/drizzle/0006_last_princess_powerful.sql +1 -0
- package/drizzle/0007_colorful_true_believers.sql +2 -0
- package/drizzle/meta/0005_snapshot.json +346 -0
- package/drizzle/meta/0006_snapshot.json +353 -0
- package/drizzle/meta/0007_snapshot.json +391 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +15 -15
- package/src/ai/incident-add-link.test.ts +1 -0
- package/src/ai/incident-add-update.test.ts +6 -1
- package/src/ai/incident-delete-update.test.ts +59 -0
- package/src/ai/incident-delete-update.ts +73 -0
- package/src/ai/register-ai-tools.ts +2 -0
- package/src/automations.test.ts +70 -0
- package/src/automations.ts +77 -4
- package/src/hooks.ts +54 -4
- package/src/index.ts +4 -0
- package/src/notifications.test.ts +181 -0
- package/src/notifications.ts +12 -1
- package/src/read-visibility.test.ts +158 -0
- package/src/read-visibility.ts +99 -0
- package/src/router.test.ts +69 -1
- package/src/router.ts +195 -15
- package/src/schema.ts +55 -10
- package/src/service-reads.it.test.ts +353 -0
- package/src/service-updates.it.test.ts +340 -0
- package/src/service.it.test.ts +57 -0
- package/src/service.test.ts +235 -1
- package/src/service.ts +489 -177
- package/src/status-page-widget.test.ts +149 -0
- package/src/status-page-widget.ts +136 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,393 @@
|
|
|
1
1
|
# @checkstack/incident-backend
|
|
2
2
|
|
|
3
|
+
## 1.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bd41130: perf(incident): add indexes for reverse system lookup and update status derivation
|
|
8
|
+
|
|
9
|
+
Add two Postgres indexes to speed up hot read paths:
|
|
10
|
+
|
|
11
|
+
- `incident_systems_system_idx` on `incident_systems (system_id)` - the junction
|
|
12
|
+
primary key leads with `incident_id`, leaving the `system_id` direction
|
|
13
|
+
unindexed. This index serves the reverse lookup used by
|
|
14
|
+
`getIncidentsForSystem` / `getActiveHealthOverrides`, which fans out per
|
|
15
|
+
system on every status-page render.
|
|
16
|
+
- `incident_updates_incident_created_idx` on
|
|
17
|
+
`incident_updates (incident_id, created_at)` - serves the status-derivation
|
|
18
|
+
query (`WHERE incident_id, status_change IS NOT NULL ORDER BY created_at DESC
|
|
19
|
+
LIMIT 1`) and the bulk timeline fetch.
|
|
20
|
+
|
|
21
|
+
- bd41130: fix(status-page): scope email subscriptions to published environments and author-selected systems
|
|
22
|
+
|
|
23
|
+
Two correctness fixes to status-page email subscriptions:
|
|
24
|
+
|
|
25
|
+
- **Health notifications now respect the page's published environments.** A
|
|
26
|
+
per-environment health transition carries the environment it happened in
|
|
27
|
+
(`originEnvironmentId`, threaded through `notifyForSubscription` ->
|
|
28
|
+
`NotificationAudienceEvent` -> the status-page fan-out). A page that publishes
|
|
29
|
+
a specific environment set is now skipped for a change in an environment it
|
|
30
|
+
does not publish - so a `development` failure never emails a prod-only page's
|
|
31
|
+
subscribers, even for a system that is also shown in prod. Pages publishing all
|
|
32
|
+
environments, and env-less sources (incident, maintenance, whole-system health
|
|
33
|
+
rollup), are unaffected.
|
|
34
|
+
- **Notifications are scoped per category to the widgets the author placed.** The
|
|
35
|
+
send-time fan-out now surfaces a notification only through widgets of its own
|
|
36
|
+
category: a health status change reaches a page only through a HEALTH widget
|
|
37
|
+
(`banner` / `systemHealth` / `groupStatus` / `uptime`, which now implement
|
|
38
|
+
`resolveScopedSystems` and declare `subscriptionCategory: "health"`), an
|
|
39
|
+
incident only through an incident widget, and so on. A page that lists a
|
|
40
|
+
system's incidents but never its health no longer emails health subscribers
|
|
41
|
+
about it, and a health-only page now correctly surfaces its systems for
|
|
42
|
+
subscription. Health widgets also participate in the public subscribe picker.
|
|
43
|
+
|
|
44
|
+
BREAKING CHANGE: on a page publishing a specific environment set, health
|
|
45
|
+
subscribers now only receive changes that occurred in a published environment
|
|
46
|
+
(previously any environment of a surfaced system triggered a notification), and a
|
|
47
|
+
notification is surfaced only by a widget of its own category (previously any
|
|
48
|
+
scoping widget on the page could surface any category). Legacy subscribers (NULL
|
|
49
|
+
categories) and all-environment pages are unchanged; no data migration is needed.
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- Updated dependencies [bd41130]
|
|
54
|
+
- Updated dependencies [bd41130]
|
|
55
|
+
- Updated dependencies [bd41130]
|
|
56
|
+
- Updated dependencies [bd41130]
|
|
57
|
+
- Updated dependencies [bd41130]
|
|
58
|
+
- Updated dependencies [bd41130]
|
|
59
|
+
- Updated dependencies [bd41130]
|
|
60
|
+
- @checkstack/backend-api@0.32.0
|
|
61
|
+
- @checkstack/auth-common@0.14.0
|
|
62
|
+
- @checkstack/cache-utils@0.3.0
|
|
63
|
+
- @checkstack/catalog-backend@1.8.0
|
|
64
|
+
- @checkstack/ai-backend@0.10.11
|
|
65
|
+
- @checkstack/notification-common@1.7.0
|
|
66
|
+
- @checkstack/status-page-backend@0.6.0
|
|
67
|
+
- @checkstack/automation-backend@0.11.2
|
|
68
|
+
- @checkstack/command-backend@0.2.23
|
|
69
|
+
- @checkstack/integration-backend@0.7.5
|
|
70
|
+
- @checkstack/catalog-common@2.7.1
|
|
71
|
+
- @checkstack/incident-common@1.10.1
|
|
72
|
+
- @checkstack/status-page-common@0.6.1
|
|
73
|
+
|
|
74
|
+
## 1.12.0
|
|
75
|
+
|
|
76
|
+
### Minor Changes
|
|
77
|
+
|
|
78
|
+
- 43e4484: Incidents and maintenance: richer, safer update timelines.
|
|
79
|
+
|
|
80
|
+
- **Markdown updates and descriptions.** Update messages and descriptions now
|
|
81
|
+
render sanitized Markdown (bold, links, lists) everywhere they appear -
|
|
82
|
+
detail pages, editors, the shared status-update timeline, and the public
|
|
83
|
+
status page (which stays sanitized via `rehype-sanitize`). An "Markdown
|
|
84
|
+
supported" hint is shown under the update composer.
|
|
85
|
+
- **Edit and delete published updates.** New `editUpdate` / `deleteUpdate`
|
|
86
|
+
procedures let a manager correct or remove an update in place; edited updates
|
|
87
|
+
are marked "edited". Editing the `statusChange` of the latest update
|
|
88
|
+
re-derives the incident/maintenance status. Deletion is irreversible and, on
|
|
89
|
+
the AI path, always routes through propose/apply. Both procedures are
|
|
90
|
+
object-scoped on the owning incident/maintenance (`idParam`), so team-scoped
|
|
91
|
+
managers can use them without a global rule.
|
|
92
|
+
- **Edit the published time of an update.** `editUpdate` now accepts an optional
|
|
93
|
+
`createdAt`, and the update editor exposes a date/time picker (the same
|
|
94
|
+
`DateTimePicker` used for maintenance windows) when editing an existing update.
|
|
95
|
+
Re-timing an update re-orders the timeline and re-derives the incident/
|
|
96
|
+
maintenance status (the header still follows the latest status-bearing
|
|
97
|
+
update), so moving an update never leaves the header and timeline diverged.
|
|
98
|
+
- **Per-update edit history (GitHub-style "history of edits").** Each in-place
|
|
99
|
+
edit now archives the prior version of the update into a new durable
|
|
100
|
+
`edit_history` `jsonb` column (a snapshot of message, status, visibility, and
|
|
101
|
+
the published time it carried, plus when it was superseded). The shared status
|
|
102
|
+
timeline turns the "edited" marker into an "edited (N)" disclosure that
|
|
103
|
+
expands to show those prior versions. History is **manager-facing only**: the
|
|
104
|
+
read path attaches `editHistory` solely for the manager audience and strips it
|
|
105
|
+
for public / logged-in readers, so a version that was `internal` before being
|
|
106
|
+
made `public` can never leak its prior internal content. A no-op edit
|
|
107
|
+
(nothing actually changed) neither archives a snapshot nor marks the update
|
|
108
|
+
"edited". Adds a forward-only, additive migration to each backend
|
|
109
|
+
(`edit_history jsonb NOT NULL DEFAULT '[]'`, backfilling existing rows).
|
|
110
|
+
We framed this as "either a delayed publish with undo OR a history of
|
|
111
|
+
edits"; edit history satisfies the ask, so undo-send / delayed-publish is
|
|
112
|
+
intentionally **deferred** (it would need a queue-delay + pending state and is
|
|
113
|
+
redundant with history).
|
|
114
|
+
- **Status updates are now editable from the editor dialog too, via one shared
|
|
115
|
+
implementation.** The status-updates surface (add / edit / delete an update,
|
|
116
|
+
including its published time and edit history) is extracted into a single
|
|
117
|
+
`IncidentUpdatesSection` / `MaintenanceUpdatesSection` used by BOTH the detail
|
|
118
|
+
page and the create/edit editor dialog, so the two surfaces can no longer
|
|
119
|
+
drift. Previously the editor dialog showed a read-only timeline with no way to
|
|
120
|
+
edit an existing update.
|
|
121
|
+
- **Editable hotlinks.** Added-links can now be edited in place (label, URL, and
|
|
122
|
+
visibility where applicable) instead of only added/removed. The shared
|
|
123
|
+
`LinksEditor` gains an inline edit affordance, backed by a new `updateLink`
|
|
124
|
+
procedure on incidents and maintenances and `updateSystemLink` on catalog
|
|
125
|
+
systems (so system links are editable too). Each is object-scoped on its
|
|
126
|
+
parent (`incidentId` / `maintenanceId` / `systemId`) with the same anti-spoof
|
|
127
|
+
WHERE-clause scoping as the remove path, so a link id cannot be paired with a
|
|
128
|
+
foreign parent the caller happens to manage. No migration is needed (the
|
|
129
|
+
columns already exist).
|
|
130
|
+
- **Per-update / per-link visibility.** A new shared visibility level
|
|
131
|
+
(`public` / `logged_in` / `internal`) can be set on both updates and hotlinks
|
|
132
|
+
via the same three-way visibility select in the editor (the update composer
|
|
133
|
+
previously exposed only a binary public/internal toggle, so `logged_in` was
|
|
134
|
+
unreachable for updates even though the backend already accepted and filtered
|
|
135
|
+
it). Filtering is enforced SERVER-SIDE on every read path: anonymous callers
|
|
136
|
+
and the public status-page projection see only `public`; authenticated
|
|
137
|
+
non-managers additionally see `logged_in`; managers see everything. Updates
|
|
138
|
+
still default to `public`, and `internal` updates never broadcast a
|
|
139
|
+
notification. Adds a forward-only migration to each backend (new visibility
|
|
140
|
+
enum + column, plus a nullable `edited_at` on updates).
|
|
141
|
+
- **"Keep Current" shows the current status**, e.g. "Keep Current
|
|
142
|
+
(Investigating)".
|
|
143
|
+
- **Status colors.** Adds a blue `--status-info` token and a shared
|
|
144
|
+
`StatusPillTone` / `pillToneStyles` in `@checkstack/ui`; incident "monitoring"
|
|
145
|
+
and maintenance "scheduled" now read as informational (blue) instead of grey.
|
|
146
|
+
The incident severity ramp is now blue(minor) -> amber(major) -> red(critical):
|
|
147
|
+
a minor incident uses the blue `info` hue instead of grey, with no minor/major
|
|
148
|
+
amber collision. This corrected ramp now also applies on the public status
|
|
149
|
+
page (active-incident cards, severity pills, and the incident detail page) and
|
|
150
|
+
in the system-detail active-incidents panel, which both previously still
|
|
151
|
+
rendered `minor` grey.
|
|
152
|
+
- **Logged-out overview.** Incidents and maintenance now expose a public,
|
|
153
|
+
read-gated overview page and sidebar entry (the manage-gated config page is
|
|
154
|
+
renamed "Manage ..."), so anonymous visitors who hold the default read rule
|
|
155
|
+
can browse them.
|
|
156
|
+
|
|
157
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
158
|
+
|
|
159
|
+
- 43e4484: Include the latest incident and maintenance update text in subscriber
|
|
160
|
+
notifications. The update message is now escaped, single-lined, truncated, and
|
|
161
|
+
appended to the notification body as a blockquote, so subscribers see WHAT
|
|
162
|
+
changed rather than a generic "has been updated"/"has been scheduled".
|
|
163
|
+
Message-only updates (no status change) now notify too, and an incident's
|
|
164
|
+
initial message is carried into its "reported" notification. Maintenance now has
|
|
165
|
+
full parity with incidents: its update text reaches subscribers, internal-only
|
|
166
|
+
operator notes never notify or leak text, and a completion note is carried into
|
|
167
|
+
the "completed" notification.
|
|
168
|
+
|
|
169
|
+
The escaping/truncation helper (`sanitizeUpdateMessage` /
|
|
170
|
+
`buildUpdateMessageSuffix`) now lives in `@checkstack/notification-common` so
|
|
171
|
+
both domain backends share one implementation.
|
|
172
|
+
|
|
173
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
174
|
+
|
|
175
|
+
- 43e4484: Count incident-forced downtime against SLOs. When an incident forces a system to
|
|
176
|
+
degraded/unhealthy via its health override, that downtime is now recorded as an
|
|
177
|
+
SLO downtime event for each of the system's objectives (consuming the error
|
|
178
|
+
budget and appearing in the downtime history) and is closed when the incident is
|
|
179
|
+
resolved, deleted, or its override is cleared - and only once the system's health
|
|
180
|
+
checks are also healthy. Downtime is never double-counted with a concurrent
|
|
181
|
+
health-check outage, and one cause never closes downtime the other is still
|
|
182
|
+
holding open (resolving an incident while checks still fail, or checks recovering
|
|
183
|
+
while an override is still active, both leave the outage open).
|
|
184
|
+
|
|
185
|
+
Adds a nullable `source` column (`healthcheck` | `incident`, NULL read as
|
|
186
|
+
`healthcheck`) to `slo_downtime_events` and a `DowntimeSource` schema in
|
|
187
|
+
slo-common, so the cause of each downtime event is recorded and the orphan
|
|
188
|
+
self-heal skips incident-owned events. incident-backend now emits an
|
|
189
|
+
`incident.lifecycle.changed` hook (contract in incident-common) on every incident
|
|
190
|
+
lifecycle change - including override-only edits that the reactive `incident`
|
|
191
|
+
entity change does not surface - which slo-backend subscribes to with
|
|
192
|
+
exactly-once delivery to reconcile downtime.
|
|
193
|
+
|
|
194
|
+
- 43e4484: Status pages can now publish only a subset of catalog environments. The page
|
|
195
|
+
builder gains a "Published environments" picker (empty = all environments, the
|
|
196
|
+
backward-compatible default). When a non-empty set is selected, the page omits
|
|
197
|
+
status, incidents, maintenances and uptime for systems that belong to none of
|
|
198
|
+
the selected environments.
|
|
199
|
+
|
|
200
|
+
- Status pages store an optional `publishedEnvironmentIds` set (new nullable
|
|
201
|
+
`published_environment_ids` column; NULL = all environments, so existing pages
|
|
202
|
+
are unchanged) exposed on `StatusPage`, `createStatusPage`, and
|
|
203
|
+
`updateStatusPage`.
|
|
204
|
+
- The scope is threaded onto `WidgetResolveContext.publishedEnvironmentIds` as
|
|
205
|
+
opaque strings and passed identically to `resolvePublic`,
|
|
206
|
+
`resolveScopedSystems`, and `resolveScopedSystemsDetailed` (and the email
|
|
207
|
+
subscribe clamp + fan-out), so what a page shows, offers for subscription, and
|
|
208
|
+
emails about all agree.
|
|
209
|
+
- Health widgets recompute per environment: they read the per-environment health
|
|
210
|
+
matrix and roll up only the selected environments. `getBulkRunStats` and
|
|
211
|
+
`getRunStats` gain an optional `environmentIds` filter so uptime counts only
|
|
212
|
+
runs recorded in the selected environments.
|
|
213
|
+
- Incident and maintenance widgets filter their feed and scope by intersecting
|
|
214
|
+
each item's affected systems with the environment-visible systems. Incidents
|
|
215
|
+
and maintenance windows carry no environment of their own, so a system in
|
|
216
|
+
several environments makes its items visible on a page publishing ANY of them
|
|
217
|
+
(the multi-environment caveat).
|
|
218
|
+
|
|
219
|
+
### Patch Changes
|
|
220
|
+
|
|
221
|
+
- 43e4484: Batch hot-path scoped-db reads/writes into single transactions to cut per-query round-trips.
|
|
222
|
+
|
|
223
|
+
The scoped-db proxy wraps every standalone query in its own `BEGIN → SET LOCAL search_path → query → COMMIT`, so a path issuing N sequential queries paid N round-trips and checked out a connection N times. These reads/writes now run under one `withScopedTransaction`, collapsing the batch to a single `SET LOCAL` on one connection. Behavior is unchanged:
|
|
224
|
+
|
|
225
|
+
- healthcheck: `getSystemHealthOverview`'s `1 + N·(2+E)` read fan-out.
|
|
226
|
+
- incident/maintenance: `getIncident`/`getMaintenance` (4 reads), `getManyEntityStates`, `listOpenIncidentsBySystem` / `getActiveMaintenancesBySystem`, `getMaintenanceWindowsForRange`; the `list*` / `*ForSystem` per-row `N+1` system lookups collapsed to a single set-based `inArray` read; maintenance `transitionStatus` update+insert made atomic; `addUpdate`/`editUpdate`/`addLink` use `.returning()` instead of a follow-up re-select.
|
|
227
|
+
- ai: `appendMessage`, memory `saveOrUpdate`.
|
|
228
|
+
- notification: `resolveInheritedGroups`.
|
|
229
|
+
- status-page: subscriber `verify` (4 reads) and `unsubscribe` (3 reads).
|
|
230
|
+
- announcement: `getActiveAnnouncements` / `dismissAnnouncement` / `createAnnouncement`.
|
|
231
|
+
- gitops: `upsertProvenance`.
|
|
232
|
+
|
|
233
|
+
- 43e4484: Eliminate N+1 RPC fan-outs in the public status-page widget resolvers.
|
|
234
|
+
|
|
235
|
+
Each of these widgets renders a PUBLIC page, so every per-item RPC was real
|
|
236
|
+
external DB load. Three bulk-by-id endpoints replace the per-item fetches:
|
|
237
|
+
|
|
238
|
+
- `healthcheck-common`: new `getBulkRunStats({ systemIds, startDate, endDate,
|
|
239
|
+
maxBuckets })` -> `{ stats: Record<systemId, RunStats> }`. The `systemHealth`
|
|
240
|
+
widget's uptime column now issues ONE request for all systems instead of one
|
|
241
|
+
`getRunStats` per system. Systems with no runs in the window are omitted, so
|
|
242
|
+
the resolver's output is unchanged.
|
|
243
|
+
- `incident-common`: new `getBulkIncidentUpdates({ incidentIds })` ->
|
|
244
|
+
`{ updates: Record<incidentId, IncidentUpdate[]> }`. The incidents widget now
|
|
245
|
+
fetches every selected incident's update timeline in ONE request instead of
|
|
246
|
+
one `getIncident` per incident.
|
|
247
|
+
- `maintenance-common`: new `getBulkMaintenanceUpdates({ maintenanceIds })` ->
|
|
248
|
+
`{ updates: Record<maintenanceId, MaintenanceUpdate[]> }` (symmetric with the
|
|
249
|
+
incident endpoint) for the maintenance widget.
|
|
250
|
+
|
|
251
|
+
The new update endpoints apply the same per-item audience filter as
|
|
252
|
+
`getIncident` / `getMaintenance`, so internal/logged-in updates and author
|
|
253
|
+
identity never leak to a non-manager caller. Each endpoint is keyed by the
|
|
254
|
+
resource id and gated with the record post-filter (`recordKey`) matching the
|
|
255
|
+
single endpoint's read scope, mirroring `getBulkSystemHealthStatus` /
|
|
256
|
+
`getBulkIncidentsForSystems`. Widget DTO output is unchanged - this is a pure
|
|
257
|
+
request-count optimization.
|
|
258
|
+
|
|
259
|
+
- 43e4484: Status page enhancements:
|
|
260
|
+
|
|
261
|
+
- Group-status widget can collapse its member rows while every member is
|
|
262
|
+
operational (auto-expanding on any issue or maintenance).
|
|
263
|
+
- New "Announcements" status-page widget, contributed fully externally by the
|
|
264
|
+
announcement plugin: it surfaces active `visibility: "all"` announcements
|
|
265
|
+
through a public-safe DTO (title/message/severity/timestamps only) and never
|
|
266
|
+
affects the page status rollup.
|
|
267
|
+
- Incident and maintenance widgets can scope by catalog GROUPS with per-system
|
|
268
|
+
exceptions. Scope is resolved at read time (`(systemIds ∪ members(groupIds)) −
|
|
269
|
+
excludedSystemIds`), so members added to a group later are reflected
|
|
270
|
+
automatically. The builder gets a nested group/system picker.
|
|
271
|
+
- Incident and maintenance items on a public page link to dedicated public
|
|
272
|
+
detail pages, gated server-side to items the page's published widgets actually
|
|
273
|
+
surface (no enumeration, no internal-field leak). The custom-domain public
|
|
274
|
+
bundle gains a minimal in-memory router for the two detail pages.
|
|
275
|
+
- Fix the custom-domain "Cannot connect to Checkstack backend" screen: a
|
|
276
|
+
configured-but-not-servable custom domain now serves the lean public
|
|
277
|
+
"not available" page instead of the admin shell; the public bundle skips the
|
|
278
|
+
cross-origin `/api/config` probe; CORS admits resolved custom domains; the
|
|
279
|
+
request origin is normalized for proxy scheme/port variance; and re-saving an
|
|
280
|
+
unchanged custom domain no longer clears its verification.
|
|
281
|
+
- Anonymous email subscriptions (double opt-in) for incident updates, opt-in per
|
|
282
|
+
status page (`emailSubscriptionsEnabled`, default off): a new
|
|
283
|
+
`status_page_subscribers` table, public subscribe/verify/unsubscribe
|
|
284
|
+
procedures with constant-time responses that fail closed when the page has not
|
|
285
|
+
enabled subscriptions, and team-scoped admin list/remove + an enable toggle in
|
|
286
|
+
the builder. Emails are delivered through a new `sendRawEmail` primitive in
|
|
287
|
+
notification-backend that sends to an arbitrary external address (no auth
|
|
288
|
+
account) via every enabled email strategy (SMTP), with a mandatory unsubscribe
|
|
289
|
+
link.
|
|
290
|
+
- Incident/maintenance update fan-out to subscribers via a new
|
|
291
|
+
`notificationAudienceExtensionPoint` in notification-backend. Every
|
|
292
|
+
notification funnelled through `notifyForSubscription` (incident, maintenance,
|
|
293
|
+
health - all unchanged) now also invokes each registered audience sink exactly
|
|
294
|
+
once, enriched with the affected systems and their catalog groups (resolved
|
|
295
|
+
from notification-backend's own resource-parent graph, never a domain import).
|
|
296
|
+
status-page-backend contributes a sink that, AT SEND TIME, matches each
|
|
297
|
+
notification's affected systems against the systems each published + public +
|
|
298
|
+
email-enabled page currently surfaces in its incident/maintenance widgets
|
|
299
|
+
(honoring group membership and per-system exclusions) and emails that page's
|
|
300
|
+
verified subscribers. Send-time scoping against the live layout is the privacy
|
|
301
|
+
boundary: a page only ever emails about systems its widgets surface right now.
|
|
302
|
+
Because `notifyForSubscription` is a single-pod point RPC, each notification
|
|
303
|
+
fans out exactly once cluster-wide.
|
|
304
|
+
- Subscriber reconcile on page deletion: the subscriber FK is `ON DELETE
|
|
305
|
+
CASCADE` and page deletion also explicitly purges subscribers (invalidating
|
|
306
|
+
pending verify/unsubscribe tokens) - no orphan rows, no post-deletion send.
|
|
307
|
+
Removing all systems from a page or disabling email is intentionally NOT a
|
|
308
|
+
prune: send-time scoping plus the email-enabled gate make those subscribers
|
|
309
|
+
dormant with no data loss, and re-enabling restores the audience without a
|
|
310
|
+
re-subscribe.
|
|
311
|
+
- Send-time scoping is single-source: the fan-out asks each event-feed widget for
|
|
312
|
+
its CURRENT effective system scope (the same live catalog group expansion the
|
|
313
|
+
widget renders from) instead of a parallel copy of group membership, so it can
|
|
314
|
+
never over- or under-deliver relative to what the page shows.
|
|
315
|
+
- `sendRawEmail` in notification-backend is now `userType: "service"` (was an
|
|
316
|
+
authenticated procedure gated on `notification.send`). Sending to an arbitrary
|
|
317
|
+
address is an open-relay / email-bomb primitive, so it is callable only by a
|
|
318
|
+
trusted backend-to-backend caller (the status-page subscriber mailer), never by
|
|
319
|
+
an end user.
|
|
320
|
+
- Incident/maintenance widgets gain an optional per-system PUBLIC label override
|
|
321
|
+
(`systemLabels`), the same override path the system-health widget uses, so the
|
|
322
|
+
public incident/maintenance detail pages present clean labels instead of raw
|
|
323
|
+
catalog names.
|
|
324
|
+
- The anonymous subscribe endpoint adds a coarse per-page quota (max new
|
|
325
|
+
subscribers per rolling hour, counted over durable rows so it holds across
|
|
326
|
+
pods) on top of the per-(page,email) cooldown, capping verification-email
|
|
327
|
+
amplification. The quota is CONFIGURABLE per status page (new nullable
|
|
328
|
+
`email_subscribers_hourly_quota` column; null uses the default of 50, so
|
|
329
|
+
existing pages are unchanged), validated as a positive integer up to 5000,
|
|
330
|
+
editable in the builder next to the email opt-in toggle and gated by the same
|
|
331
|
+
page-manage capability.
|
|
332
|
+
- Email verification is now per-page configurable and backed by a platform-global
|
|
333
|
+
once-per-address registry:
|
|
334
|
+
- New `email_verification_required` column (boolean, default true) on
|
|
335
|
+
`status_pages`, exposed on the admin StatusPage DTO + `updateStatusPage`
|
|
336
|
+
input (same page-manage gate) with a builder toggle. When OFF, a new
|
|
337
|
+
subscriber is created active immediately - no verification email, and the
|
|
338
|
+
address is NOT written to the global registry (the operator's trust choice
|
|
339
|
+
for e.g. an internal page).
|
|
340
|
+
- New `status_page_verified_emails` table: one row per normalized address that
|
|
341
|
+
has completed verification on ANY page. When a verification-required page is
|
|
342
|
+
subscribed by an already-globally-verified address, the row is created active
|
|
343
|
+
immediately and a COURTESY email (with one-click unsubscribe) is sent instead
|
|
344
|
+
of a verification email, so a malicious add is always caught. `verify` upserts
|
|
345
|
+
the address into this registry and activates every other pending row for the
|
|
346
|
+
same address in one update (confirm once, all pages).
|
|
347
|
+
- Fan-out is unchanged: it still gates on the per-row `verified` flag; the
|
|
348
|
+
registry only governs whether a NEW subscribe short-circuits to active.
|
|
349
|
+
|
|
350
|
+
BREAKING CHANGE: `sendRawEmail` is now service-only. Any (non-existent in-tree)
|
|
351
|
+
authenticated caller must invoke it through a trusted service client instead.
|
|
352
|
+
|
|
353
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
354
|
+
|
|
355
|
+
- Updated dependencies [43e4484]
|
|
356
|
+
- Updated dependencies [43e4484]
|
|
357
|
+
- Updated dependencies [43e4484]
|
|
358
|
+
- Updated dependencies [43e4484]
|
|
359
|
+
- Updated dependencies [43e4484]
|
|
360
|
+
- Updated dependencies [43e4484]
|
|
361
|
+
- Updated dependencies [43e4484]
|
|
362
|
+
- Updated dependencies [43e4484]
|
|
363
|
+
- Updated dependencies [43e4484]
|
|
364
|
+
- Updated dependencies [43e4484]
|
|
365
|
+
- Updated dependencies [43e4484]
|
|
366
|
+
- Updated dependencies [43e4484]
|
|
367
|
+
- Updated dependencies [43e4484]
|
|
368
|
+
- Updated dependencies [43e4484]
|
|
369
|
+
- Updated dependencies [43e4484]
|
|
370
|
+
- Updated dependencies [43e4484]
|
|
371
|
+
- Updated dependencies [43e4484]
|
|
372
|
+
- Updated dependencies [43e4484]
|
|
373
|
+
- Updated dependencies [43e4484]
|
|
374
|
+
- Updated dependencies [43e4484]
|
|
375
|
+
- Updated dependencies [43e4484]
|
|
376
|
+
- Updated dependencies [43e4484]
|
|
377
|
+
- Updated dependencies [43e4484]
|
|
378
|
+
- Updated dependencies [43e4484]
|
|
379
|
+
- @checkstack/ai-backend@0.10.10
|
|
380
|
+
- @checkstack/automation-backend@0.11.1
|
|
381
|
+
- @checkstack/catalog-common@2.7.0
|
|
382
|
+
- @checkstack/catalog-backend@1.7.0
|
|
383
|
+
- @checkstack/backend-api@0.31.1
|
|
384
|
+
- @checkstack/incident-common@1.10.0
|
|
385
|
+
- @checkstack/notification-common@1.6.0
|
|
386
|
+
- @checkstack/status-page-backend@0.5.0
|
|
387
|
+
- @checkstack/status-page-common@0.6.0
|
|
388
|
+
- @checkstack/command-backend@0.2.22
|
|
389
|
+
- @checkstack/integration-backend@0.7.4
|
|
390
|
+
|
|
3
391
|
## 1.11.0
|
|
4
392
|
|
|
5
393
|
### Minor Changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
CREATE TYPE "incident_content_visibility" AS ENUM('public', 'logged_in', 'internal');--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "incident_links" ADD COLUMN "visibility" "incident_content_visibility" DEFAULT 'public' NOT NULL;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "incident_updates" ADD COLUMN "visibility" "incident_content_visibility" DEFAULT 'public' NOT NULL;--> statement-breakpoint
|
|
4
|
+
ALTER TABLE "incident_updates" ADD COLUMN "edited_at" timestamp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "incident_updates" ADD COLUMN "edit_history" jsonb DEFAULT '[]'::jsonb NOT NULL;
|