@oneuptime/common 11.3.28 → 11.4.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.
Files changed (69) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/Project.ts +60 -0
  3. package/Server/API/AIInvestigationAPI.ts +148 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  8. package/Server/Services/AlertService.ts +25 -0
  9. package/Server/Services/IncidentService.ts +25 -0
  10. package/Server/Services/IncidentStateTimelineService.ts +19 -0
  11. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
  12. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
  13. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
  14. package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
  15. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
  16. package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
  17. package/Server/Utils/AI/Toolbox/Index.ts +11 -0
  18. package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
  19. package/Tests/Server/API/BaseAPI.test.ts +1 -0
  20. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
  21. package/Types/Date.ts +46 -0
  22. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
  23. package/UI/Components/Calendar/Calendar.tsx +8 -2
  24. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  25. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  26. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  27. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  28. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  29. package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
  30. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
  31. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
  32. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
  33. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
  35. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
  36. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
  37. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  38. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  39. package/build/dist/Server/Services/AlertService.js +24 -0
  40. package/build/dist/Server/Services/AlertService.js.map +1 -1
  41. package/build/dist/Server/Services/IncidentService.js +24 -0
  42. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  43. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  44. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  45. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
  46. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  47. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
  48. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
  49. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
  50. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
  51. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
  52. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
  53. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
  54. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
  55. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
  56. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
  57. package/build/dist/Server/Utils/AI/Toolbox/Index.js +6 -0
  58. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  59. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
  60. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
  61. package/build/dist/Types/Date.js +32 -0
  62. package/build/dist/Types/Date.js.map +1 -1
  63. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
  64. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
  65. package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
  66. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  67. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  68. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  69. package/package.json +1 -1
@@ -284,6 +284,56 @@ export default class AIRun extends BaseModel {
284
284
  })
285
285
  public conversationId?: ObjectID = undefined;
286
286
 
287
+ @ColumnAccessControl({
288
+ create: [],
289
+ read: [
290
+ Permission.ProjectOwner,
291
+ Permission.ProjectAdmin,
292
+ Permission.ProjectMember,
293
+ ],
294
+ update: [],
295
+ })
296
+ @Index()
297
+ @TableColumn({
298
+ type: TableColumnType.ObjectID,
299
+ required: false,
300
+ canReadOnRelationQuery: true,
301
+ title: "Triggered By Incident ID",
302
+ description:
303
+ "The incident that triggered this run (for autonomous investigations).",
304
+ })
305
+ @Column({
306
+ type: ColumnType.ObjectID,
307
+ nullable: true,
308
+ transformer: ObjectID.getDatabaseTransformer(),
309
+ })
310
+ public triggeredByIncidentId?: ObjectID = undefined;
311
+
312
+ @ColumnAccessControl({
313
+ create: [],
314
+ read: [
315
+ Permission.ProjectOwner,
316
+ Permission.ProjectAdmin,
317
+ Permission.ProjectMember,
318
+ ],
319
+ update: [],
320
+ })
321
+ @Index()
322
+ @TableColumn({
323
+ type: TableColumnType.ObjectID,
324
+ required: false,
325
+ canReadOnRelationQuery: true,
326
+ title: "Triggered By Alert ID",
327
+ description:
328
+ "The alert that triggered this run (for autonomous investigations).",
329
+ })
330
+ @Column({
331
+ type: ColumnType.ObjectID,
332
+ nullable: true,
333
+ transformer: ObjectID.getDatabaseTransformer(),
334
+ })
335
+ public triggeredByAlertId?: ObjectID = undefined;
336
+
287
337
  @ColumnAccessControl({
288
338
  create: [],
289
339
  read: [
@@ -1422,6 +1422,66 @@ export default class Project extends TenantModel {
1422
1422
  })
1423
1423
  public enableAi?: boolean = undefined;
1424
1424
 
1425
+ @ColumnAccessControl({
1426
+ create: [],
1427
+ read: [
1428
+ Permission.ProjectOwner,
1429
+ Permission.ProjectAdmin,
1430
+ Permission.ProjectMember,
1431
+ Permission.Viewer,
1432
+ Permission.ReadProject,
1433
+ Permission.UnAuthorizedSsoUser,
1434
+ Permission.ProjectUser,
1435
+ ],
1436
+ update: [Permission.ProjectOwner, Permission.ProjectAdmin],
1437
+ })
1438
+ @TableColumn({
1439
+ required: true,
1440
+ isDefaultValueColumn: true,
1441
+ type: TableColumnType.Boolean,
1442
+ title: "Enable Automatic Incident Investigation",
1443
+ description:
1444
+ "When enabled, OneUptime's AI SRE (Sentinel) automatically investigates every new incident and posts a cited root cause analysis to the incident timeline. Requires AI to be enabled and an LLM provider to be configured.",
1445
+ defaultValue: false,
1446
+ example: true,
1447
+ })
1448
+ @Column({
1449
+ nullable: false,
1450
+ default: false,
1451
+ type: ColumnType.Boolean,
1452
+ })
1453
+ public enableAutomaticIncidentInvestigation?: boolean = undefined;
1454
+
1455
+ @ColumnAccessControl({
1456
+ create: [],
1457
+ read: [
1458
+ Permission.ProjectOwner,
1459
+ Permission.ProjectAdmin,
1460
+ Permission.ProjectMember,
1461
+ Permission.Viewer,
1462
+ Permission.ReadProject,
1463
+ Permission.UnAuthorizedSsoUser,
1464
+ Permission.ProjectUser,
1465
+ ],
1466
+ update: [Permission.ProjectOwner, Permission.ProjectAdmin],
1467
+ })
1468
+ @TableColumn({
1469
+ required: true,
1470
+ isDefaultValueColumn: true,
1471
+ type: TableColumnType.Boolean,
1472
+ title: "Enable Automatic Alert Investigation",
1473
+ description:
1474
+ "When enabled, OneUptime's AI SRE (Sentinel) automatically investigates every new alert and posts a cited root cause analysis to the alert timeline. Requires AI to be enabled and an LLM provider to be configured.",
1475
+ defaultValue: false,
1476
+ example: true,
1477
+ })
1478
+ @Column({
1479
+ nullable: false,
1480
+ default: false,
1481
+ type: ColumnType.Boolean,
1482
+ })
1483
+ public enableAutomaticAlertInvestigation?: boolean = undefined;
1484
+
1425
1485
  @ColumnAccessControl({
1426
1486
  create: [],
1427
1487
  read: [
@@ -0,0 +1,148 @@
1
+ import UserMiddleware from "../Middleware/UserAuthorization";
2
+ import CommonAPI from "./CommonAPI";
3
+ import Express, {
4
+ ExpressRequest,
5
+ ExpressResponse,
6
+ ExpressRouter,
7
+ NextFunction,
8
+ } from "../Utils/Express";
9
+ import Response from "../Utils/Response";
10
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
11
+ import ObjectID from "../../Types/ObjectID";
12
+ import BadDataException from "../../Types/Exception/BadDataException";
13
+ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
14
+ import SortOrder from "../../Types/BaseDatabase/SortOrder";
15
+ import { JSONArray, JSONObject } from "../../Types/JSON";
16
+ import AIRunType from "../../Types/AI/AIRunType";
17
+ import AIRun from "../../Models/DatabaseModels/AIRun";
18
+ import AIRunEvent from "../../Models/DatabaseModels/AIRunEvent";
19
+ import Incident from "../../Models/DatabaseModels/Incident";
20
+ import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
21
+ import IncidentService from "../Services/IncidentService";
22
+ import AIRunService from "../Services/AIRunService";
23
+ import AIRunEventService from "../Services/AIRunEventService";
24
+
25
+ const router: ExpressRouter = Express.getRouter();
26
+
27
+ const MAX_EVENTS: number = 500;
28
+
29
+ /*
30
+ * Returns the latest Sentinel investigation (the AIRun + its ordered
31
+ * AIRunEvents) for an incident, so the dashboard can render a live "watch it
32
+ * think" panel.
33
+ *
34
+ * Investigation runs are system-authored (userId = null) and are therefore
35
+ * hidden by the per-user privacy pin on the generic AIRun / AIRunEvent CRUD. So
36
+ * we gate access explicitly here: first confirm the caller can read the incident
37
+ * under THEIR permissions, then read the run + events as root.
38
+ */
39
+ router.post(
40
+ "/ai-investigation/incident",
41
+ UserMiddleware.getUserMiddleware,
42
+ async (
43
+ req: ExpressRequest,
44
+ res: ExpressResponse,
45
+ next: NextFunction,
46
+ ): Promise<void> => {
47
+ try {
48
+ const props: DatabaseCommonInteractionProps =
49
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
50
+
51
+ if (!props.userId) {
52
+ throw new NotAuthorizedException(
53
+ "A logged-in user session is required.",
54
+ );
55
+ }
56
+
57
+ const incidentIdString: string | undefined = req.body["incidentId"] as
58
+ | string
59
+ | undefined;
60
+
61
+ if (!incidentIdString) {
62
+ throw new BadDataException("incidentId is required.");
63
+ }
64
+
65
+ const incidentId: ObjectID = new ObjectID(incidentIdString);
66
+
67
+ // Access check under the USER's permissions (null when not allowed).
68
+ const incident: Incident | null = await IncidentService.findOneById({
69
+ id: incidentId,
70
+ select: { _id: true },
71
+ props,
72
+ });
73
+
74
+ if (!incident) {
75
+ throw new BadDataException(
76
+ "Incident not found (or you do not have access to it).",
77
+ );
78
+ }
79
+
80
+ // Read the latest investigation run as root (bypasses the per-user pin).
81
+ const runs: Array<AIRun> = await AIRunService.findBy({
82
+ query: {
83
+ triggeredByIncidentId: incidentId,
84
+ runType: AIRunType.Investigation,
85
+ },
86
+ select: {
87
+ _id: true,
88
+ status: true,
89
+ startedAt: true,
90
+ completedAt: true,
91
+ errorMessage: true,
92
+ llmCallCount: true,
93
+ toolCallCount: true,
94
+ totalTokens: true,
95
+ createdAt: true,
96
+ },
97
+ sort: { createdAt: SortOrder.Descending },
98
+ limit: 1,
99
+ skip: 0,
100
+ props: { isRoot: true },
101
+ });
102
+
103
+ const run: AIRun | undefined = runs[0];
104
+
105
+ if (!run) {
106
+ Response.sendJsonObjectResponse(req, res, {
107
+ run: null,
108
+ events: [],
109
+ });
110
+ return;
111
+ }
112
+
113
+ const events: Array<AIRunEvent> = await AIRunEventService.findBy({
114
+ query: { aiRunId: run.id! },
115
+ select: {
116
+ _id: true,
117
+ sequence: true,
118
+ eventType: true,
119
+ toolName: true,
120
+ resultSummary: true,
121
+ createdAt: true,
122
+ },
123
+ sort: { sequence: SortOrder.Ascending },
124
+ limit: MAX_EVENTS,
125
+ skip: 0,
126
+ props: { isRoot: true },
127
+ });
128
+
129
+ const runJson: JSONObject | undefined = BaseModel.toJSONArray(
130
+ [run],
131
+ AIRun,
132
+ )[0];
133
+
134
+ const eventsJson: JSONArray = BaseModel.toJSONArray(events, AIRunEvent);
135
+
136
+ Response.sendJsonObjectResponse(req, res, {
137
+ run: runJson || null,
138
+ events: eventsJson,
139
+ });
140
+ return;
141
+ } catch (err) {
142
+ next(err);
143
+ return;
144
+ }
145
+ },
146
+ );
147
+
148
+ export default router;
@@ -0,0 +1,19 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddEnableAutomaticIncidentInvestigationToProject1783598145111
4
+ implements MigrationInterface
5
+ {
6
+ public name = "AddEnableAutomaticIncidentInvestigationToProject1783598145111";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "Project" ADD "enableAutomaticIncidentInvestigation" boolean NOT NULL DEFAULT false`,
11
+ );
12
+ }
13
+
14
+ public async down(queryRunner: QueryRunner): Promise<void> {
15
+ await queryRunner.query(
16
+ `ALTER TABLE "Project" DROP COLUMN "enableAutomaticIncidentInvestigation"`,
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,33 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddInvestigationSubjectToAIRun1783615884106
4
+ implements MigrationInterface
5
+ {
6
+ public name = "AddInvestigationSubjectToAIRun1783615884106";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "AIRun" ADD "triggeredByIncidentId" uuid`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "AIRun" ADD "triggeredByAlertId" uuid`,
14
+ );
15
+ await queryRunner.query(
16
+ `CREATE INDEX "IDX_AIRun_triggeredByIncidentId" ON "AIRun" ("triggeredByIncidentId")`,
17
+ );
18
+ await queryRunner.query(
19
+ `CREATE INDEX "IDX_AIRun_triggeredByAlertId" ON "AIRun" ("triggeredByAlertId")`,
20
+ );
21
+ }
22
+
23
+ public async down(queryRunner: QueryRunner): Promise<void> {
24
+ await queryRunner.query(`DROP INDEX "IDX_AIRun_triggeredByAlertId"`);
25
+ await queryRunner.query(`DROP INDEX "IDX_AIRun_triggeredByIncidentId"`);
26
+ await queryRunner.query(
27
+ `ALTER TABLE "AIRun" DROP COLUMN "triggeredByAlertId"`,
28
+ );
29
+ await queryRunner.query(
30
+ `ALTER TABLE "AIRun" DROP COLUMN "triggeredByIncidentId"`,
31
+ );
32
+ }
33
+ }
@@ -0,0 +1,19 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddEnableAutomaticAlertInvestigationToProject1783632447975
4
+ implements MigrationInterface
5
+ {
6
+ public name = "AddEnableAutomaticAlertInvestigationToProject1783632447975";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "Project" ADD "enableAutomaticAlertInvestigation" boolean NOT NULL DEFAULT false`,
11
+ );
12
+ }
13
+
14
+ public async down(queryRunner: QueryRunner): Promise<void> {
15
+ await queryRunner.query(
16
+ `ALTER TABLE "Project" DROP COLUMN "enableAutomaticAlertInvestigation"`,
17
+ );
18
+ }
19
+ }
@@ -417,6 +417,9 @@ import { AddIncomingCallPolicyRoutingPhoneNumberUnique1783470000000 } from "./17
417
417
  import { FixNotNullForeignKeysOnDelete1783510935686 } from "./1783510935686-FixNotNullForeignKeysOnDelete";
418
418
  import { MigrationName1783515836148 } from "./1783515836148-MigrationName";
419
419
  import { AddMigrationFailureTable1783523076215 } from "./1783523076215-AddMigrationFailureTable";
420
+ import { AddEnableAutomaticIncidentInvestigationToProject1783598145111 } from "./1783598145111-AddEnableAutomaticIncidentInvestigationToProject";
421
+ import { AddInvestigationSubjectToAIRun1783615884106 } from "./1783615884106-AddInvestigationSubjectToAIRun";
422
+ import { AddEnableAutomaticAlertInvestigationToProject1783632447975 } from "./1783632447975-AddEnableAutomaticAlertInvestigationToProject";
420
423
 
421
424
  export default [
422
425
  InitialMigration,
@@ -838,4 +841,7 @@ export default [
838
841
  FixNotNullForeignKeysOnDelete1783510935686,
839
842
  MigrationName1783515836148,
840
843
  AddMigrationFailureTable1783523076215,
844
+ AddEnableAutomaticIncidentInvestigationToProject1783598145111,
845
+ AddInvestigationSubjectToAIRun1783615884106,
846
+ AddEnableAutomaticAlertInvestigationToProject1783632447975,
841
847
  ];
@@ -67,6 +67,7 @@ import AlertLabelRuleEngineService from "./AlertLabelRuleEngineService";
67
67
  import AlertOnCallRuleEngineService from "./AlertOnCallRuleEngineService";
68
68
  import AlertOwnerRuleEngineService from "./AlertOwnerRuleEngineService";
69
69
  import RunbookRuleEngineService from "./RunbookRuleEngineService";
70
+ import SentinelAlertInvestigationRunner from "../Utils/AI/Sentinel/AlertInvestigationRunner";
70
71
  import AlertPrivacyRuleEngineService from "./AlertPrivacyRuleEngineService";
71
72
  import ProjectService from "./ProjectService";
72
73
 
@@ -558,6 +559,30 @@ export class Service extends DatabaseService<Model> {
558
559
  );
559
560
  }
560
561
  })
562
+ .then(async () => {
563
+ /*
564
+ * Sentinel (AI SRE): automatically investigate the new alert and post a
565
+ * cited root cause analysis to the alert timeline + Slack/Teams. Runs
566
+ * last, is gated per project (opt-in) + requires an LLM provider, and is
567
+ * read-only.
568
+ */
569
+ try {
570
+ if (createdItem.projectId && createdItem.id) {
571
+ await SentinelAlertInvestigationRunner.investigateNewAlert({
572
+ alertId: createdItem.id,
573
+ projectId: createdItem.projectId,
574
+ });
575
+ }
576
+ } catch (error) {
577
+ logger.error(
578
+ `Sentinel alert investigation failed in AlertService.onCreateSuccess: ${error}`,
579
+ {
580
+ projectId: createdItem.projectId?.toString(),
581
+ alertId: createdItem.id?.toString(),
582
+ } as LogAttributes,
583
+ );
584
+ }
585
+ })
561
586
  .catch((error: Error) => {
562
587
  logger.error(
563
588
  `Critical error in AlertService sequential operations: ${error}`,
@@ -89,6 +89,7 @@ import LLMService, {
89
89
  } from "../Utils/LLM/LLMService";
90
90
  import LlmProviderService from "./LlmProviderService";
91
91
  import LlmProvider from "../../Models/DatabaseModels/LlmProvider";
92
+ import SentinelIncidentInvestigationRunner from "../Utils/AI/Sentinel/IncidentInvestigationRunner";
92
93
  import IncidentAIContextBuilder, {
93
94
  AIGenerationContext,
94
95
  IncidentContextData,
@@ -1241,6 +1242,30 @@ export class Service extends DatabaseService<Model> {
1241
1242
  );
1242
1243
  }
1243
1244
  })
1245
+ .then(async () => {
1246
+ /*
1247
+ * Sentinel (AI SRE): automatically investigate the new incident and post
1248
+ * a cited root cause analysis to the incident timeline + Slack/Teams.
1249
+ * Runs last so the workspace channels already exist, and is gated per
1250
+ * project (opt-in) + requires a configured LLM provider. Read-only.
1251
+ */
1252
+ try {
1253
+ if (createdItem.projectId && createdItem.id) {
1254
+ await SentinelIncidentInvestigationRunner.investigateNewIncident({
1255
+ incidentId: createdItem.id,
1256
+ projectId: createdItem.projectId,
1257
+ });
1258
+ }
1259
+ } catch (error) {
1260
+ logger.error(
1261
+ `Sentinel incident investigation failed in IncidentService.onCreateSuccess: ${error}`,
1262
+ {
1263
+ projectId: createdItem.projectId?.toString(),
1264
+ incidentId: createdItem.id?.toString(),
1265
+ } as LogAttributes,
1266
+ );
1267
+ }
1268
+ })
1244
1269
  .catch((error: Error) => {
1245
1270
  logger.error(
1246
1271
  `Critical error in IncidentService sequential operations: ${error}`,
@@ -27,6 +27,7 @@ import IncidentRole from "../../Models/DatabaseModels/IncidentRole";
27
27
  import { IsBillingEnabled } from "../EnvironmentConfig";
28
28
  import logger, { LogAttributes } from "../Utils/Logger";
29
29
  import IncidentFeedService from "./IncidentFeedService";
30
+ import SentinelIncidentPostmortemRunner from "../Utils/AI/Sentinel/IncidentPostmortemRunner";
30
31
  import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
31
32
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
32
33
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
@@ -555,6 +556,24 @@ ${createdItem.rootCause}`,
555
556
  createdItem.startsAt || undefined,
556
557
  );
557
558
  }
559
+
560
+ /*
561
+ * Sentinel: auto-draft a postmortem now that the incident is resolved
562
+ * (gated per project; never overwrites an existing postmortem).
563
+ */
564
+ SentinelIncidentPostmortemRunner.draftPostmortemOnResolve({
565
+ incidentId: createdItem.incidentId!,
566
+ projectId: createdItem.projectId!,
567
+ }).catch((error: Error) => {
568
+ logger.error(`Sentinel auto-postmortem failed on resolve:`, {
569
+ projectId: createdItem.projectId?.toString(),
570
+ incidentId: createdItem.incidentId?.toString(),
571
+ } as LogAttributes);
572
+ logger.error(error, {
573
+ projectId: createdItem.projectId?.toString(),
574
+ incidentId: createdItem.incidentId?.toString(),
575
+ } as LogAttributes);
576
+ });
558
577
  }
559
578
 
560
579
  if (onCreate.carryForward.publicNote) {