@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.
@@ -0,0 +1,391 @@
1
+ {
2
+ "id": "a430b9d2-36ed-4c87-a41a-6f9e2a7517c7",
3
+ "prevId": "ff7f73e0-3d6b-47a4-b759-000739af4ee6",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.incident_links": {
8
+ "name": "incident_links",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "incident_id": {
18
+ "name": "incident_id",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "label": {
24
+ "name": "label",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": false
28
+ },
29
+ "url": {
30
+ "name": "url",
31
+ "type": "text",
32
+ "primaryKey": false,
33
+ "notNull": true
34
+ },
35
+ "visibility": {
36
+ "name": "visibility",
37
+ "type": "incident_content_visibility",
38
+ "typeSchema": "public",
39
+ "primaryKey": false,
40
+ "notNull": true,
41
+ "default": "'public'"
42
+ },
43
+ "created_at": {
44
+ "name": "created_at",
45
+ "type": "timestamp",
46
+ "primaryKey": false,
47
+ "notNull": true,
48
+ "default": "now()"
49
+ }
50
+ },
51
+ "indexes": {
52
+ "incident_links_incident_url_unique": {
53
+ "name": "incident_links_incident_url_unique",
54
+ "columns": [
55
+ {
56
+ "expression": "incident_id",
57
+ "isExpression": false,
58
+ "asc": true,
59
+ "nulls": "last"
60
+ },
61
+ {
62
+ "expression": "url",
63
+ "isExpression": false,
64
+ "asc": true,
65
+ "nulls": "last"
66
+ }
67
+ ],
68
+ "isUnique": true,
69
+ "concurrently": false,
70
+ "method": "btree",
71
+ "with": {}
72
+ }
73
+ },
74
+ "foreignKeys": {
75
+ "incident_links_incident_id_incidents_id_fk": {
76
+ "name": "incident_links_incident_id_incidents_id_fk",
77
+ "tableFrom": "incident_links",
78
+ "tableTo": "incidents",
79
+ "columnsFrom": [
80
+ "incident_id"
81
+ ],
82
+ "columnsTo": [
83
+ "id"
84
+ ],
85
+ "onDelete": "cascade",
86
+ "onUpdate": "no action"
87
+ }
88
+ },
89
+ "compositePrimaryKeys": {},
90
+ "uniqueConstraints": {},
91
+ "policies": {},
92
+ "checkConstraints": {},
93
+ "isRLSEnabled": false
94
+ },
95
+ "public.incident_systems": {
96
+ "name": "incident_systems",
97
+ "schema": "",
98
+ "columns": {
99
+ "incident_id": {
100
+ "name": "incident_id",
101
+ "type": "text",
102
+ "primaryKey": false,
103
+ "notNull": true
104
+ },
105
+ "system_id": {
106
+ "name": "system_id",
107
+ "type": "text",
108
+ "primaryKey": false,
109
+ "notNull": true
110
+ }
111
+ },
112
+ "indexes": {
113
+ "incident_systems_system_idx": {
114
+ "name": "incident_systems_system_idx",
115
+ "columns": [
116
+ {
117
+ "expression": "system_id",
118
+ "isExpression": false,
119
+ "asc": true,
120
+ "nulls": "last"
121
+ }
122
+ ],
123
+ "isUnique": false,
124
+ "concurrently": false,
125
+ "method": "btree",
126
+ "with": {}
127
+ }
128
+ },
129
+ "foreignKeys": {
130
+ "incident_systems_incident_id_incidents_id_fk": {
131
+ "name": "incident_systems_incident_id_incidents_id_fk",
132
+ "tableFrom": "incident_systems",
133
+ "tableTo": "incidents",
134
+ "columnsFrom": [
135
+ "incident_id"
136
+ ],
137
+ "columnsTo": [
138
+ "id"
139
+ ],
140
+ "onDelete": "cascade",
141
+ "onUpdate": "no action"
142
+ }
143
+ },
144
+ "compositePrimaryKeys": {
145
+ "incident_systems_incident_id_system_id_pk": {
146
+ "name": "incident_systems_incident_id_system_id_pk",
147
+ "columns": [
148
+ "incident_id",
149
+ "system_id"
150
+ ]
151
+ }
152
+ },
153
+ "uniqueConstraints": {},
154
+ "policies": {},
155
+ "checkConstraints": {},
156
+ "isRLSEnabled": false
157
+ },
158
+ "public.incident_updates": {
159
+ "name": "incident_updates",
160
+ "schema": "",
161
+ "columns": {
162
+ "id": {
163
+ "name": "id",
164
+ "type": "text",
165
+ "primaryKey": true,
166
+ "notNull": true
167
+ },
168
+ "incident_id": {
169
+ "name": "incident_id",
170
+ "type": "text",
171
+ "primaryKey": false,
172
+ "notNull": true
173
+ },
174
+ "message": {
175
+ "name": "message",
176
+ "type": "text",
177
+ "primaryKey": false,
178
+ "notNull": true
179
+ },
180
+ "status_change": {
181
+ "name": "status_change",
182
+ "type": "incident_status",
183
+ "typeSchema": "public",
184
+ "primaryKey": false,
185
+ "notNull": false
186
+ },
187
+ "visibility": {
188
+ "name": "visibility",
189
+ "type": "incident_content_visibility",
190
+ "typeSchema": "public",
191
+ "primaryKey": false,
192
+ "notNull": true,
193
+ "default": "'public'"
194
+ },
195
+ "created_at": {
196
+ "name": "created_at",
197
+ "type": "timestamp",
198
+ "primaryKey": false,
199
+ "notNull": true,
200
+ "default": "now()"
201
+ },
202
+ "edited_at": {
203
+ "name": "edited_at",
204
+ "type": "timestamp",
205
+ "primaryKey": false,
206
+ "notNull": false
207
+ },
208
+ "edit_history": {
209
+ "name": "edit_history",
210
+ "type": "jsonb",
211
+ "primaryKey": false,
212
+ "notNull": true,
213
+ "default": "'[]'::jsonb"
214
+ },
215
+ "created_by": {
216
+ "name": "created_by",
217
+ "type": "text",
218
+ "primaryKey": false,
219
+ "notNull": false
220
+ }
221
+ },
222
+ "indexes": {
223
+ "incident_updates_incident_created_idx": {
224
+ "name": "incident_updates_incident_created_idx",
225
+ "columns": [
226
+ {
227
+ "expression": "incident_id",
228
+ "isExpression": false,
229
+ "asc": true,
230
+ "nulls": "last"
231
+ },
232
+ {
233
+ "expression": "created_at",
234
+ "isExpression": false,
235
+ "asc": true,
236
+ "nulls": "last"
237
+ }
238
+ ],
239
+ "isUnique": false,
240
+ "concurrently": false,
241
+ "method": "btree",
242
+ "with": {}
243
+ }
244
+ },
245
+ "foreignKeys": {
246
+ "incident_updates_incident_id_incidents_id_fk": {
247
+ "name": "incident_updates_incident_id_incidents_id_fk",
248
+ "tableFrom": "incident_updates",
249
+ "tableTo": "incidents",
250
+ "columnsFrom": [
251
+ "incident_id"
252
+ ],
253
+ "columnsTo": [
254
+ "id"
255
+ ],
256
+ "onDelete": "cascade",
257
+ "onUpdate": "no action"
258
+ }
259
+ },
260
+ "compositePrimaryKeys": {},
261
+ "uniqueConstraints": {},
262
+ "policies": {},
263
+ "checkConstraints": {},
264
+ "isRLSEnabled": false
265
+ },
266
+ "public.incidents": {
267
+ "name": "incidents",
268
+ "schema": "",
269
+ "columns": {
270
+ "id": {
271
+ "name": "id",
272
+ "type": "text",
273
+ "primaryKey": true,
274
+ "notNull": true
275
+ },
276
+ "title": {
277
+ "name": "title",
278
+ "type": "text",
279
+ "primaryKey": false,
280
+ "notNull": true
281
+ },
282
+ "description": {
283
+ "name": "description",
284
+ "type": "text",
285
+ "primaryKey": false,
286
+ "notNull": false
287
+ },
288
+ "status": {
289
+ "name": "status",
290
+ "type": "incident_status",
291
+ "typeSchema": "public",
292
+ "primaryKey": false,
293
+ "notNull": true,
294
+ "default": "'investigating'"
295
+ },
296
+ "severity": {
297
+ "name": "severity",
298
+ "type": "incident_severity",
299
+ "typeSchema": "public",
300
+ "primaryKey": false,
301
+ "notNull": true,
302
+ "default": "'major'"
303
+ },
304
+ "suppress_notifications": {
305
+ "name": "suppress_notifications",
306
+ "type": "boolean",
307
+ "primaryKey": false,
308
+ "notNull": true,
309
+ "default": false
310
+ },
311
+ "health_override": {
312
+ "name": "health_override",
313
+ "type": "incident_health_override",
314
+ "typeSchema": "public",
315
+ "primaryKey": false,
316
+ "notNull": false
317
+ },
318
+ "created_at": {
319
+ "name": "created_at",
320
+ "type": "timestamp",
321
+ "primaryKey": false,
322
+ "notNull": true,
323
+ "default": "now()"
324
+ },
325
+ "updated_at": {
326
+ "name": "updated_at",
327
+ "type": "timestamp",
328
+ "primaryKey": false,
329
+ "notNull": true,
330
+ "default": "now()"
331
+ }
332
+ },
333
+ "indexes": {},
334
+ "foreignKeys": {},
335
+ "compositePrimaryKeys": {},
336
+ "uniqueConstraints": {},
337
+ "policies": {},
338
+ "checkConstraints": {},
339
+ "isRLSEnabled": false
340
+ }
341
+ },
342
+ "enums": {
343
+ "public.incident_health_override": {
344
+ "name": "incident_health_override",
345
+ "schema": "public",
346
+ "values": [
347
+ "degraded",
348
+ "unhealthy"
349
+ ]
350
+ },
351
+ "public.incident_severity": {
352
+ "name": "incident_severity",
353
+ "schema": "public",
354
+ "values": [
355
+ "minor",
356
+ "major",
357
+ "critical"
358
+ ]
359
+ },
360
+ "public.incident_status": {
361
+ "name": "incident_status",
362
+ "schema": "public",
363
+ "values": [
364
+ "investigating",
365
+ "identified",
366
+ "fixing",
367
+ "monitoring",
368
+ "resolved"
369
+ ]
370
+ },
371
+ "public.incident_content_visibility": {
372
+ "name": "incident_content_visibility",
373
+ "schema": "public",
374
+ "values": [
375
+ "public",
376
+ "logged_in",
377
+ "internal"
378
+ ]
379
+ }
380
+ },
381
+ "schemas": {},
382
+ "sequences": {},
383
+ "roles": {},
384
+ "policies": {},
385
+ "views": {},
386
+ "_meta": {
387
+ "columns": {},
388
+ "schemas": {},
389
+ "tables": {}
390
+ }
391
+ }
@@ -36,6 +36,27 @@
36
36
  "when": 1783287429703,
37
37
  "tag": "0004_flaky_malcolm_colcord",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1783498751737,
44
+ "tag": "0005_real_leper_queen",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1783535507970,
51
+ "tag": "0006_last_princess_powerful",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "7",
57
+ "when": 1783607735789,
58
+ "tag": "0007_colorful_true_believers",
59
+ "breakpoints": true
39
60
  }
40
61
  ]
41
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/incident-backend",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -14,23 +14,23 @@
14
14
  "lint:code": "eslint . --max-warnings 0"
15
15
  },
16
16
  "dependencies": {
17
- "@checkstack/ai-backend": "0.10.9",
17
+ "@checkstack/ai-backend": "0.10.11",
18
18
  "@checkstack/ai-common": "0.6.6",
19
- "@checkstack/backend-api": "0.31.0",
19
+ "@checkstack/backend-api": "0.32.0",
20
20
  "@checkstack/cache-api": "0.3.19",
21
- "@checkstack/cache-utils": "0.2.24",
22
- "@checkstack/incident-common": "1.9.0",
23
- "@checkstack/catalog-common": "2.6.3",
24
- "@checkstack/catalog-backend": "1.6.9",
25
- "@checkstack/notification-common": "1.5.3",
26
- "@checkstack/auth-common": "0.13.0",
27
- "@checkstack/command-backend": "0.2.21",
21
+ "@checkstack/cache-utils": "0.3.0",
22
+ "@checkstack/incident-common": "1.10.1",
23
+ "@checkstack/catalog-common": "2.7.1",
24
+ "@checkstack/catalog-backend": "1.8.0",
25
+ "@checkstack/notification-common": "1.7.0",
26
+ "@checkstack/auth-common": "0.14.0",
27
+ "@checkstack/command-backend": "0.2.23",
28
28
  "@checkstack/signal-common": "0.2.17",
29
- "@checkstack/status-page-backend": "0.4.8",
30
- "@checkstack/status-page-common": "0.5.3",
31
- "@checkstack/integration-backend": "0.7.3",
29
+ "@checkstack/status-page-backend": "0.6.0",
30
+ "@checkstack/status-page-common": "0.6.1",
31
+ "@checkstack/integration-backend": "0.7.5",
32
32
  "@checkstack/integration-common": "0.9.8",
33
- "@checkstack/automation-backend": "0.11.0",
33
+ "@checkstack/automation-backend": "0.11.2",
34
34
  "@checkstack/automation-common": "0.10.0",
35
35
  "@checkstack/common": "0.22.0",
36
36
  "drizzle-orm": "^0.45.0",
@@ -41,7 +41,7 @@
41
41
  "devDependencies": {
42
42
  "@checkstack/drizzle-helper": "0.0.6",
43
43
  "@checkstack/scripts": "0.7.3",
44
- "@checkstack/test-utils-backend": "0.1.55",
44
+ "@checkstack/test-utils-backend": "0.1.57",
45
45
  "@checkstack/tsconfig": "0.0.7",
46
46
  "@types/bun": "^1.0.0",
47
47
  "@types/pg": "^8.20.0",
@@ -50,6 +50,7 @@ describe("incident.addLink tool", () => {
50
50
  incidentId: input.incidentId,
51
51
  label: input.label ?? null,
52
52
  url: input.url,
53
+ visibility: "public" as const,
53
54
  createdAt: new Date(),
54
55
  };
55
56
  const addLink = mock(() => Promise.resolve(created));
@@ -45,7 +45,12 @@ describe("incident.addUpdate tool", () => {
45
45
  });
46
46
 
47
47
  test("execute (apply) posts via addUpdate", async () => {
48
- const created = { id: "upd1", ...input, createdAt: new Date() };
48
+ const created = {
49
+ id: "upd1",
50
+ ...input,
51
+ visibility: "public" as const,
52
+ createdAt: new Date(),
53
+ };
49
54
  const addUpdate = mock(() => Promise.resolve(created));
50
55
  const rpcClient = fakeRpcClient({ addUpdate });
51
56
  const tool = createIncidentAddUpdateTool();
@@ -0,0 +1,59 @@
1
+ import { describe, expect, test, mock } from "bun:test";
2
+ import type { AuthUser, RpcClient } from "@checkstack/backend-api";
3
+ import { createIncidentDeleteUpdateTool } from "./incident-delete-update";
4
+
5
+ const principal: AuthUser = {
6
+ type: "user",
7
+ id: "u1",
8
+ accessRules: ["incident.incident.manage"],
9
+ };
10
+
11
+ function fakeRpcClient({
12
+ deleteUpdate,
13
+ }: {
14
+ deleteUpdate: ReturnType<typeof mock>;
15
+ }): RpcClient {
16
+ return {
17
+ forPlugin: () => ({ deleteUpdate }),
18
+ } as unknown as RpcClient;
19
+ }
20
+
21
+ describe("incident.deleteUpdate tool", () => {
22
+ test("declares destructive effect + the manage rule", () => {
23
+ const tool = createIncidentDeleteUpdateTool();
24
+ expect(tool.name).toBe("incident.deleteUpdate");
25
+ expect(tool.effect).toBe("destructive");
26
+ expect(tool.requiredAccessRules).toEqual(["incident.incident.manage"]);
27
+ expect(typeof tool.dryRun).toBe("function");
28
+ });
29
+
30
+ test("dryRun returns a payload and NEVER deletes the update", async () => {
31
+ const deleteUpdate = mock(() => Promise.resolve({ success: true }));
32
+ const rpcClient = fakeRpcClient({ deleteUpdate });
33
+ const tool = createIncidentDeleteUpdateTool();
34
+ const preview = await tool.dryRun!({
35
+ input: { id: "upd1", incidentId: "inc1" },
36
+ principal,
37
+ rpcClient,
38
+ });
39
+ expect(deleteUpdate).not.toHaveBeenCalled();
40
+ expect(preview.summary).toContain("permanent");
41
+ expect(preview.payload).toEqual({ id: "upd1", incidentId: "inc1" });
42
+ });
43
+
44
+ test("execute (apply) deletes via deleteUpdate with {id, incidentId}", async () => {
45
+ const deleteUpdate = mock(() => Promise.resolve({ success: true }));
46
+ const rpcClient = fakeRpcClient({ deleteUpdate });
47
+ const tool = createIncidentDeleteUpdateTool();
48
+ const result = await tool.execute({
49
+ input: { id: "upd1", incidentId: "inc1" },
50
+ principal,
51
+ rpcClient,
52
+ });
53
+ expect(deleteUpdate).toHaveBeenCalledWith({
54
+ id: "upd1",
55
+ incidentId: "inc1",
56
+ });
57
+ expect(result).toEqual({ id: "upd1", removed: true });
58
+ });
59
+ });
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ import { qualifyAccessRuleId } from "@checkstack/common";
3
+ import type { RpcClient, AuthUser } from "@checkstack/backend-api";
4
+ import {
5
+ IncidentApi,
6
+ incidentAccess,
7
+ pluginMetadata,
8
+ } from "@checkstack/incident-common";
9
+ import type { AiProposalPreview } from "@checkstack/ai-common";
10
+ import type { RegisteredAiTool } from "@checkstack/ai-backend";
11
+
12
+ /** Input for `incident.deleteUpdate`: the update id + its owning incident. */
13
+ export const IncidentDeleteUpdateInputSchema = z.object({
14
+ id: z.string(),
15
+ incidentId: z.string(),
16
+ });
17
+ export type IncidentDeleteUpdateInput = z.infer<
18
+ typeof IncidentDeleteUpdateInputSchema
19
+ >;
20
+
21
+ /** Output returned once a human applies the update removal. */
22
+ export interface IncidentDeleteUpdateApplyResult {
23
+ id: string;
24
+ removed: true;
25
+ }
26
+
27
+ /**
28
+ * `incident.deleteUpdate` - delete a published status update by id.
29
+ *
30
+ * `effect: "destructive"` - deleting an update is irreversible, so it ALWAYS
31
+ * routes through the propose/apply confirm card in BOTH permission modes (it can
32
+ * never auto-apply). `dryRun` returns the captured payload for human review
33
+ * WITHOUT mutating; `execute` (reached only via `apply`) deletes the update. The
34
+ * underlying RPC uses the USER-SCOPED client passed at call time, so
35
+ * handler-side authorization is enforced exactly as a direct UI/RPC call.
36
+ */
37
+ export function createIncidentDeleteUpdateTool(): RegisteredAiTool<
38
+ IncidentDeleteUpdateInput,
39
+ IncidentDeleteUpdateApplyResult
40
+ > {
41
+ const dryRun = async ({
42
+ input,
43
+ }: {
44
+ input: IncidentDeleteUpdateInput;
45
+ principal: AuthUser;
46
+ rpcClient: RpcClient;
47
+ }): Promise<AiProposalPreview<IncidentDeleteUpdateInput>> => {
48
+ return {
49
+ summary: `Delete update ${input.id} from incident ${input.incidentId}. This is permanent.`,
50
+ payload: { id: input.id, incidentId: input.incidentId },
51
+ };
52
+ };
53
+
54
+ return {
55
+ name: "incident.deleteUpdate",
56
+ description:
57
+ "Delete a published status update from an incident by update id and its owning incidentId. DESTRUCTIVE and irreversible. Never deletes directly; a person must approve the confirmation. Find the update id and incidentId by reading the incident's details first.",
58
+ effect: "destructive",
59
+ input: IncidentDeleteUpdateInputSchema,
60
+ requiredAccessRules: [
61
+ qualifyAccessRuleId(pluginMetadata, incidentAccess.incident.manage),
62
+ ],
63
+ dryRun,
64
+ async execute({ input, rpcClient }) {
65
+ const incidentClient = rpcClient.forPlugin(IncidentApi);
66
+ await incidentClient.deleteUpdate({
67
+ id: input.id,
68
+ incidentId: input.incidentId,
69
+ });
70
+ return { id: input.id, removed: true };
71
+ },
72
+ };
73
+ }
@@ -6,6 +6,7 @@ import { createIncidentAddUpdateTool } from "./incident-add-update";
6
6
  import { createIncidentResolveTool } from "./incident-resolve";
7
7
  import { createIncidentAddLinkTool } from "./incident-add-link";
8
8
  import { createIncidentRemoveLinkTool } from "./incident-remove-link";
9
+ import { createIncidentDeleteUpdateTool } from "./incident-delete-update";
9
10
 
10
11
  /**
11
12
  * The incident plugin's AI tools, registered into the AI registry via
@@ -29,5 +30,6 @@ export function buildIncidentAiTools(): RegisteredAiTool[] {
29
30
  createIncidentResolveTool(),
30
31
  createIncidentAddLinkTool(),
31
32
  createIncidentRemoveLinkTool(),
33
+ createIncidentDeleteUpdateTool(),
32
34
  ];
33
35
  }