From 041d58e7eba924ddfbcab8391ff113bf90d1986a Mon Sep 17 00:00:00 2001 From: Thibault Le Ouay Ducasse Date: Mon, 28 Sep 2026 16:13:05 +0200 Subject: [PATCH] db + services: incident_postmortem Co-Authored-By: Claude Opus 5.5 --- .../components/chat/tool-renderers/index.tsx | 11 + .../incidents/incident-properties.tsx | 6 +- apps/server/src/routes/mcp/handler.test.ts | 2 + apps/server/src/routes/mcp/tools/incident.ts | 4 + apps/server/src/routes/slack/system-prompt.ts | 1 + packages/analytics/src/events.ts | 8 + packages/api/src/router/incident.ts | 48 +- packages/db/drizzle/0090_condemned_photon.sql | 21 + packages/db/drizzle/meta/0090_snapshot.json | 6656 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + .../db/src/schema/audit_logs/validation.ts | 10 + packages/db/src/schema/incidents/constants.ts | 6 + packages/db/src/schema/incidents/index.ts | 1 + .../db/src/schema/incidents/postmortem.ts | 43 + .../db/src/schema/incidents/validation.ts | 4 + packages/db/src/test/factories.ts | 22 + .../agent-tools/__tests__/incident.test.ts | 3 +- packages/services/src/agent-tools/incident.ts | 83 + packages/services/src/agent-tools/index.ts | 6 + packages/services/src/agent-tools/prompt.ts | 1 + .../src/incident/__tests__/hooks.test.ts | 5 +- .../src/incident/__tests__/incident.test.ts | 14 +- .../src/incident/__tests__/postmortem.test.ts | 193 + packages/services/src/incident/close.ts | 111 +- packages/services/src/incident/index.ts | 10 +- packages/services/src/incident/postmortem.ts | 207 + packages/services/src/incident/schemas.ts | 21 + 27 files changed, 7453 insertions(+), 51 deletions(-) create mode 100644 packages/db/drizzle/0090_condemned_photon.sql create mode 100644 packages/db/drizzle/meta/0090_snapshot.json create mode 100644 packages/db/src/schema/incidents/postmortem.ts create mode 100644 packages/services/src/incident/__tests__/postmortem.test.ts create mode 100644 packages/services/src/incident/postmortem.ts diff --git a/apps/dashboard/src/components/chat/tool-renderers/index.tsx b/apps/dashboard/src/components/chat/tool-renderers/index.tsx index 2100153b..35902985 100644 --- a/apps/dashboard/src/components/chat/tool-renderers/index.tsx +++ b/apps/dashboard/src/components/chat/tool-renderers/index.tsx @@ -189,6 +189,17 @@ export const toolRenderers: ToolRendererRegistry = { ], summary: (o) => `${o.status} · ID ${o.id}`, }, + get_postmortem: { + summary: (o) => + o.exists ? `${o.status} · drafted by ${o.draftedBy}` : "no postmortem", + }, + approve_postmortem: { + renderDraft: (input) => [ + { field: "incidentId", after: input.id }, + { field: "close", after: input.close }, + ], + summary: (o) => `${o.status} · incident ${o.incidentId}`, + }, add_incident_note: { summary: (o) => `note added to incident ${o.incidentId}`, }, diff --git a/apps/dashboard/src/components/incidents/incident-properties.tsx b/apps/dashboard/src/components/incidents/incident-properties.tsx index 75d0d4ad..125f6bbe 100644 --- a/apps/dashboard/src/components/incidents/incident-properties.tsx +++ b/apps/dashboard/src/components/incidents/incident-properties.tsx @@ -206,9 +206,11 @@ export function IncidentProperties({ incident }: { incident: Incident }) { size="sm" variant="outline" disabled={close.isPending} - onClick={() => close.mutate({ id: incident.id })} + onClick={() => + close.mutate({ id: incident.id, skipPostmortem: true }) + } > - Close incident + Close without postmortem ) : null} {incident.deletable ? ( diff --git a/apps/server/src/routes/mcp/handler.test.ts b/apps/server/src/routes/mcp/handler.test.ts index 199196d8..2bb07619 100644 --- a/apps/server/src/routes/mcp/handler.test.ts +++ b/apps/server/src/routes/mcp/handler.test.ts @@ -121,6 +121,7 @@ describe("MCP transport", () => { expect(names).toEqual([ "add_incident_note", "add_status_report_update", + "approve_postmortem", "create_maintenance", "create_status_report", "declare_incident", @@ -131,6 +132,7 @@ describe("MCP transport", () => { "get_monitor", "get_monitor_status", "get_monitor_summary", + "get_postmortem", "get_response_log", "list_audit_logs", "list_incidents", diff --git a/apps/server/src/routes/mcp/tools/incident.ts b/apps/server/src/routes/mcp/tools/incident.ts index c69239ac..d783f473 100644 --- a/apps/server/src/routes/mcp/tools/incident.ts +++ b/apps/server/src/routes/mcp/tools/incident.ts @@ -5,8 +5,10 @@ import type { import type { ServiceContext } from "@openstatus/services"; import { addIncidentNoteTool, + approvePostmortemTool, declareIncidentTool, getIncidentTool, + getPostmortemTool, listIncidentsTool, resolveIncidentTool, setIncidentStatusTool, @@ -27,5 +29,7 @@ export function registerIncidentTools( resolveIncidentTool, setIncidentStatusTool, addIncidentNoteTool, + getPostmortemTool, + approvePostmortemTool, ]); } diff --git a/apps/server/src/routes/slack/system-prompt.ts b/apps/server/src/routes/slack/system-prompt.ts index aa0c0c7a..a003d3bb 100644 --- a/apps/server/src/routes/slack/system-prompt.ts +++ b/apps/server/src/routes/slack/system-prompt.ts @@ -10,6 +10,7 @@ Managed incidents (internal, never published): - "the incident is resolved" with a managed incident in play -> resolve_incident; if its linked status report is still open, draft resolve_status_report too. - "mitigated", "the bleeding stopped" -> set_incident_status mitigated; "false alarm", "declared by mistake" -> set_incident_status canceled (closes it for good). - In an incident channel (see the note below when there is one), notes and updates belong to that incident: use its id without asking. +- Postmortems: get_postmortem reads one; approve_postmortem signs it off (approval card) and by default closes the incident. - Before referencing a managed incident, call list_incidents. Severity: critical = major outage or data loss, major = significant degradation, minor = limited impact.`; export function buildSystemPrompt( diff --git a/packages/analytics/src/events.ts b/packages/analytics/src/events.ts index 24da4d7c..65aeafa0 100644 --- a/packages/analytics/src/events.ts +++ b/packages/analytics/src/events.ts @@ -188,6 +188,14 @@ export const Events = { name: "incident_closed", channel: "incident_management", }, + DraftManagedPostmortem: { + name: "postmortem_drafted", + channel: "incident_management", + }, + ApproveManagedPostmortem: { + name: "postmortem_approved", + channel: "incident_management", + }, DeleteManagedIncident: { name: "incident_removed", channel: "incident_management", diff --git a/packages/api/src/router/incident.ts b/packages/api/src/router/incident.ts index fc7b5063..7b6e8c5a 100644 --- a/packages/api/src/router/incident.ts +++ b/packages/api/src/router/incident.ts @@ -3,14 +3,20 @@ import { incidentStatus } from "@openstatus/db/src/schema/incidents/constants"; import { sendIncidentCommander } from "@openstatus/emails"; import { AddIncidentNoteInput, + ApprovePostmortemInput, BindIncidentSlackChannelInput, + CloseIncidentInput, DeclareIncidentInput, + DraftPostmortemInput, IncidentIdInput, LinkIncidentStatusReportInput, SetIncidentStatusInput, UpdateIncidentInput, addIncidentNote, allowedTransitions, + approvePostmortem, + draftPostmortem, + getPostmortem, announceIncidentChange, announceInChannel, bindIncidentSlackChannel, @@ -336,9 +342,49 @@ export const incidentRouter = createTRPCRouter({ } }), + getPostmortem: protectedProcedure + .input(IncidentIdInput) + .query(async ({ ctx, input }) => { + try { + return (await getPostmortem({ ctx: toServiceCtx(ctx), input })) ?? null; + } catch (err) { + toTRPCError(err); + } + }), + + draftPostmortem: protectedProcedure + .input(DraftPostmortemInput) + .mutation(async ({ ctx, input }) => { + try { + return await draftPostmortem({ ctx: toServiceCtx(ctx), input }); + } catch (err) { + toTRPCError(err); + } + }), + + approvePostmortem: protectedProcedure + .meta({ track: Events.ApproveManagedPostmortem }) + .input(ApprovePostmortemInput) + .mutation(async ({ ctx, input }) => { + try { + const row = await approvePostmortem({ ctx: toServiceCtx(ctx), input }); + if (input.close) { + announce( + ctx, + input.id, + `${actorName(ctx)} approved the postmortem and closed the incident.`, + true, + ); + } + return row; + } catch (err) { + toTRPCError(err); + } + }), + close: protectedProcedure .meta({ track: Events.CloseManagedIncident }) - .input(IncidentIdInput) + .input(CloseIncidentInput) .mutation(async ({ ctx, input }) => { try { const row = await closeIncident({ ctx: toServiceCtx(ctx), input }); diff --git a/packages/db/drizzle/0090_condemned_photon.sql b/packages/db/drizzle/0090_condemned_photon.sql new file mode 100644 index 00000000..e7469b35 --- /dev/null +++ b/packages/db/drizzle/0090_condemned_photon.sql @@ -0,0 +1,21 @@ +CREATE TABLE `incident_postmortem` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `incident_id` integer NOT NULL, + `status` text DEFAULT 'draft' NOT NULL, + `content` text NOT NULL, + `source_transcript` text, + `drafted_by` text NOT NULL, + `created_by` integer, + `updated_by` integer, + `approved_by` integer, + `approved_at` integer, + `published_at` integer, + `created_at` integer DEFAULT (strftime('%s', 'now')) NOT NULL, + `updated_at` integer DEFAULT (strftime('%s', 'now')) NOT NULL, + FOREIGN KEY (`incident_id`) REFERENCES `incident`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`created_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`updated_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`approved_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `incident_postmortem_incident_id_unique` ON `incident_postmortem` (`incident_id`); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0090_snapshot.json b/packages/db/drizzle/meta/0090_snapshot.json new file mode 100644 index 00000000..696bda75 --- /dev/null +++ b/packages/db/drizzle/meta/0090_snapshot.json @@ -0,0 +1,6656 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "962ab282-b5aa-4e15-8252-1f04bfdf39f0", + "prevId": "b7cb72b7-ac2c-4cd6-b0a7-7561221be34b", + "tables": { + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ends_at": { + "name": "ends_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_until": { + "name": "paid_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "limits": { + "name": "limits", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "workos_organization_id": { + "name": "workos_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_enabled": { + "name": "sso_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "dsn": { + "name": "dsn", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "workspace_stripe_id_unique": { + "name": "workspace_stripe_id_unique", + "columns": [ + "stripe_id" + ], + "isUnique": true + }, + "workspace_workos_organization_id_unique": { + "name": "workspace_workos_organization_id_unique", + "columns": [ + "workos_organization_id" + ], + "isUnique": true + }, + "workspace_id_dsn_unique": { + "name": "workspace_id_dsn_unique", + "columns": [ + "id", + "dsn" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sso_domain": { + "name": "workspace_sso_domain", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified_at": { + "name": "verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "workspace_sso_domain_domain_unique": { + "name": "workspace_sso_domain_domain_unique", + "columns": [ + "domain" + ], + "isUnique": true + }, + "workspace_sso_domain_workspace_id_idx": { + "name": "workspace_sso_domain_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sso_domain_workspace_id_workspace_id_fk": { + "name": "workspace_sso_domain_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sso_domain", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_provider_account_id_pk": { + "columns": [ + "provider", + "provider_account_id" + ], + "name": "account_provider_provider_account_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_tenant_id_unique": { + "name": "user_tenant_id_unique", + "columns": [ + "tenant_id" + ], + "isUnique": true + }, + "user_email_idx": { + "name": "user_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_to_workspaces": { + "name": "users_to_workspaces", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "users_to_workspaces_workspace_id_idx": { + "name": "users_to_workspaces_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_to_workspaces_user_id_user_id_fk": { + "name": "users_to_workspaces_user_id_user_id_fk", + "tableFrom": "users_to_workspaces", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_to_workspaces_workspace_id_workspace_id_fk": { + "name": "users_to_workspaces_workspace_id_workspace_id_fk", + "tableFrom": "users_to_workspaces", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "users_to_workspaces_user_id_workspace_id_pk": { + "columns": [ + "user_id", + "workspace_id" + ], + "name": "users_to_workspaces_user_id_workspace_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification_token": { + "name": "verification_token", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verification_token_identifier_token_pk": { + "columns": [ + "identifier", + "token" + ], + "name": "verification_token_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report": { + "name": "status_report", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_workspace_created_idx": { + "name": "status_report_workspace_created_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "status_report_page_id_idx": { + "name": "status_report_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_workspace_id_workspace_id_fk": { + "name": "status_report_workspace_id_workspace_id_fk", + "tableFrom": "status_report", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_report_page_id_page_id_fk": { + "name": "status_report_page_id_page_id_fk", + "tableFrom": "status_report", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report_update": { + "name": "status_report_update", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_update_status_report_id_idx": { + "name": "status_report_update_status_report_id_idx", + "columns": [ + "status_report_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_update_status_report_id_status_report_id_fk": { + "name": "status_report_update_status_report_id_status_report_id_fk", + "tableFrom": "status_report_update", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential": { + "name": "credential", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_workspace_id_idx": { + "name": "integration_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "integration_workspace_id_workspace_id_fk": { + "name": "integration_workspace_id_workspace_id_fk", + "tableFrom": "integration", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page": { + "name": "page", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "slug": { + "name": "slug", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published": { + "name": "published", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "force_theme": { + "name": "force_theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "custom_theme": { + "name": "custom_theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_protected": { + "name": "password_protected", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "access_type": { + "name": "access_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'public'" + }, + "auth_email_domains": { + "name": "auth_email_domains", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_ip_ranges": { + "name": "allowed_ip_ranges", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_url": { + "name": "contact_url", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en'" + }, + "locales": { + "name": "locales", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "legacy_page": { + "name": "legacy_page", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "configuration": { + "name": "configuration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_index": { + "name": "allow_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_monitor_values": { + "name": "show_monitor_values", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_slug_unique": { + "name": "page_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "page_lower_slug_idx": { + "name": "page_lower_slug_idx", + "columns": [ + "LOWER(\"slug\")" + ], + "isUnique": false + }, + "page_lower_custom_domain_idx": { + "name": "page_lower_custom_domain_idx", + "columns": [ + "LOWER(\"custom_domain\")" + ], + "isUnique": false + }, + "page_workspace_id_idx": { + "name": "page_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "page_workspace_id_workspace_id_fk": { + "name": "page_workspace_id_workspace_id_fk", + "tableFrom": "page", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor": { + "name": "monitor", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'http'" + }, + "periodicity": { + "name": "periodicity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'other'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "active": { + "name": "active", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "regions": { + "name": "regions", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "url": { + "name": "url", + "type": "text(2048)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "external_name": { + "name": "external_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'GET'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 45000 + }, + "degraded_after": { + "name": "degraded_after", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assertions": { + "name": "assertions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "otel_endpoint": { + "name": "otel_endpoint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "otel_headers": { + "name": "otel_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "retry": { + "name": "retry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3 + }, + "follow_redirects": { + "name": "follow_redirects", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "grpc_service": { + "name": "grpc_service", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grpc_tls": { + "name": "grpc_tls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'tls'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "monitor_workspace_id_active_idx": { + "name": "monitor_workspace_id_active_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false, + "where": "\"monitor\".\"deleted_at\" IS NULL" + } + }, + "foreignKeys": { + "monitor_workspace_id_workspace_id_fk": { + "name": "monitor_workspace_id_workspace_id_fk", + "tableFrom": "monitor", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_subscriber": { + "name": "page_subscriber", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_type": { + "name": "channel_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "webhook_url": { + "name": "webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel_config": { + "name": "channel_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'self_signup'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unsubscribed_at": { + "name": "unsubscribed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_subscriber_page_id_idx": { + "name": "page_subscriber_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "idx_page_subscriber_email_page_active": { + "name": "idx_page_subscriber_email_page_active", + "columns": [ + "LOWER(\"email\")", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'email'" + }, + "idx_page_subscriber_webhook_page_active": { + "name": "idx_page_subscriber_webhook_page_active", + "columns": [ + "LOWER(\"webhook_url\")", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'webhook'" + }, + "idx_page_subscriber_slack_channel_page_active": { + "name": "idx_page_subscriber_slack_channel_page_active", + "columns": [ + "slack_channel_id", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'slack'" + } + }, + "foreignKeys": { + "page_subscriber_page_id_page_id_fk": { + "name": "page_subscriber_page_id_page_id_fk", + "tableFrom": "page_subscriber", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "page_subscriber_channel_check": { + "name": "page_subscriber_channel_check", + "value": "(\"page_subscriber\".\"channel_type\" = 'email' AND \"page_subscriber\".\"email\" IS NOT NULL AND \"page_subscriber\".\"webhook_url\" IS NULL) OR (\"page_subscriber\".\"channel_type\" = 'webhook' AND \"page_subscriber\".\"webhook_url\" IS NOT NULL AND \"page_subscriber\".\"email\" IS NULL) OR (\"page_subscriber\".\"channel_type\" = 'slack' AND \"page_subscriber\".\"slack_channel_id\" IS NOT NULL AND \"page_subscriber\".\"email\" IS NULL AND \"page_subscriber\".\"webhook_url\" IS NULL)" + } + } + }, + "page_subscriber_to_page_component": { + "name": "page_subscriber_to_page_component", + "columns": { + "page_subscriber_id": { + "name": "page_subscriber_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": {}, + "foreignKeys": { + "page_subscriber_to_page_component_page_subscriber_id_page_subscriber_id_fk": { + "name": "page_subscriber_to_page_component_page_subscriber_id_page_subscriber_id_fk", + "tableFrom": "page_subscriber_to_page_component", + "tableTo": "page_subscriber", + "columnsFrom": [ + "page_subscriber_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_subscriber_to_page_component_page_component_id_page_component_id_fk": { + "name": "page_subscriber_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "page_subscriber_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "page_subscriber_to_page_component_page_subscriber_id_page_component_id_pk": { + "columns": [ + "page_subscriber_id", + "page_component_id" + ], + "name": "page_subscriber_to_page_component_page_subscriber_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification": { + "name": "notification", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{}'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "notification_workspace_id_idx": { + "name": "notification_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_workspace_id_workspace_id_fk": { + "name": "notification_workspace_id_workspace_id_fk", + "tableFrom": "notification", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_trigger": { + "name": "notification_trigger", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_id_monitor_id_crontimestampe": { + "name": "notification_id_monitor_id_crontimestampe", + "columns": [ + "notification_id", + "monitor_id", + "cron_timestamp" + ], + "isUnique": true + } + }, + "foreignKeys": { + "notification_trigger_monitor_id_monitor_id_fk": { + "name": "notification_trigger_monitor_id_monitor_id_fk", + "tableFrom": "notification_trigger", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_trigger_notification_id_notification_id_fk": { + "name": "notification_trigger_notification_id_notification_id_fk", + "tableFrom": "notification_trigger", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications_to_monitors": { + "name": "notifications_to_monitors", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "notifications_to_monitors_notification_id_idx": { + "name": "notifications_to_monitors_notification_id_idx", + "columns": [ + "notification_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_to_monitors_monitor_id_monitor_id_fk": { + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", + "tableFrom": "notifications_to_monitors", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_to_monitors_notification_id_notification_id_fk": { + "name": "notifications_to_monitors_notification_id_notification_id_fk", + "tableFrom": "notifications_to_monitors", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notifications_to_monitors_monitor_id_notification_id_pk": { + "columns": [ + "monitor_id", + "notification_id" + ], + "name": "notifications_to_monitors_monitor_id_notification_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_dead_letter": { + "name": "notification_dead_letter", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "outbox_id": { + "name": "outbox_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dedup_key": { + "name": "dedup_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "final_error": { + "name": "final_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "died_at": { + "name": "died_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_dead_letter_dedup_key_idx": { + "name": "notification_dead_letter_dedup_key_idx", + "columns": [ + "dedup_key" + ], + "isUnique": true + }, + "notification_dead_letter_workspace_id_died_at_idx": { + "name": "notification_dead_letter_workspace_id_died_at_idx", + "columns": [ + "workspace_id", + "died_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_dead_letter_monitor_id_monitor_id_fk": { + "name": "notification_dead_letter_monitor_id_monitor_id_fk", + "tableFrom": "notification_dead_letter", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_dead_letter_workspace_id_workspace_id_fk": { + "name": "notification_dead_letter_workspace_id_workspace_id_fk", + "tableFrom": "notification_dead_letter", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_outbox": { + "name": "notification_outbox", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "dedup_key": { + "name": "dedup_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivery_status": { + "name": "delivery_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deadline_at": { + "name": "deadline_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locked_by": { + "name": "locked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locked_until": { + "name": "locked_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_outbox_dedup_key_idx": { + "name": "notification_outbox_dedup_key_idx", + "columns": [ + "dedup_key" + ], + "isUnique": true + }, + "notification_outbox_claim_idx": { + "name": "notification_outbox_claim_idx", + "columns": [ + "next_attempt_at" + ], + "isUnique": false, + "where": "\"notification_outbox\".\"delivery_status\" = 'pending'" + }, + "notification_outbox_notification_id_cron_timestamp_idx": { + "name": "notification_outbox_notification_id_cron_timestamp_idx", + "columns": [ + "notification_id", + "cron_timestamp" + ], + "isUnique": false + }, + "notification_outbox_channel_idx": { + "name": "notification_outbox_channel_idx", + "columns": [ + "monitor_id", + "notification_id" + ], + "isUnique": false, + "where": "\"notification_outbox\".\"delivery_status\" = 'pending'" + } + }, + "foreignKeys": { + "notification_outbox_monitor_id_monitor_id_fk": { + "name": "notification_outbox_monitor_id_monitor_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_outbox_workspace_id_workspace_id_fk": { + "name": "notification_outbox_workspace_id_workspace_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notification_outbox_notification_id_notification_id_fk": { + "name": "notification_outbox_notification_id_notification_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_outbox_incident_id_monitor_incident_id_fk": { + "name": "notification_outbox_incident_id_monitor_incident_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "monitor_incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_status": { + "name": "monitor_status", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_status_idx": { + "name": "monitor_status_idx", + "columns": [ + "monitor_id", + "region" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_status_monitor_id_monitor_id_fk": { + "name": "monitor_status_monitor_id_monitor_id_fk", + "tableFrom": "monitor_status", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "monitor_status_monitor_id_region_pk": { + "columns": [ + "monitor_id", + "region" + ], + "name": "monitor_status_monitor_id_region_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invitation_workspace_id_idx": { + "name": "invitation_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_incident": { + "name": "monitor_incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'triage'" + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "incident_screenshot_url": { + "name": "incident_screenshot_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recovery_screenshot_url": { + "name": "recovery_screenshot_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auto_resolved": { + "name": "auto_resolved", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_incident_workspace_id_started_at_idx": { + "name": "monitor_incident_workspace_id_started_at_idx", + "columns": [ + "workspace_id", + "started_at" + ], + "isUnique": false + }, + "monitor_incident_open_idx": { + "name": "monitor_incident_open_idx", + "columns": [ + "monitor_id" + ], + "isUnique": true, + "where": "\"monitor_incident\".\"resolved_at\" IS NULL" + }, + "monitor_incident_monitor_id_started_at_unique": { + "name": "monitor_incident_monitor_id_started_at_unique", + "columns": [ + "monitor_id", + "started_at" + ], + "isUnique": true + } + }, + "foreignKeys": { + "monitor_incident_monitor_id_monitor_id_fk": { + "name": "monitor_incident_monitor_id_monitor_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set default", + "onUpdate": "no action" + }, + "monitor_incident_workspace_id_workspace_id_fk": { + "name": "monitor_incident_workspace_id_workspace_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_incident_acknowledged_by_user_id_fk": { + "name": "monitor_incident_acknowledged_by_user_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "user", + "columnsFrom": [ + "acknowledged_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_incident_resolved_by_user_id_fk": { + "name": "monitor_incident_resolved_by_user_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "user", + "columnsFrom": [ + "resolved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_tag": { + "name": "monitor_tag", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_tag_workspace_id_idx": { + "name": "monitor_tag_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_tag_workspace_id_workspace_id_fk": { + "name": "monitor_tag_workspace_id_workspace_id_fk", + "tableFrom": "monitor_tag", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_tag_to_monitor": { + "name": "monitor_tag_to_monitor", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_tag_id": { + "name": "monitor_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_tag_to_monitor_monitor_tag_id_idx": { + "name": "monitor_tag_to_monitor_monitor_tag_id_idx", + "columns": [ + "monitor_tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_tag_to_monitor_monitor_id_monitor_id_fk": { + "name": "monitor_tag_to_monitor_monitor_id_monitor_id_fk", + "tableFrom": "monitor_tag_to_monitor", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk": { + "name": "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk", + "tableFrom": "monitor_tag_to_monitor", + "tableTo": "monitor_tag", + "columnsFrom": [ + "monitor_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk": { + "columns": [ + "monitor_id", + "monitor_tag_id" + ], + "name": "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "application": { + "name": "application", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dsn": { + "name": "dsn", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "application_dsn_unique": { + "name": "application_dsn_unique", + "columns": [ + "dsn" + ], + "isUnique": true + }, + "application_workspace_id_idx": { + "name": "application_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "application_workspace_id_workspace_id_fk": { + "name": "application_workspace_id_workspace_id_fk", + "tableFrom": "application", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "maintenance": { + "name": "maintenance", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from": { + "name": "from", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to": { + "name": "to", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "maintenance_page_id_idx": { + "name": "maintenance_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "maintenance_workspace_id_idx": { + "name": "maintenance_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "maintenance_workspace_id_workspace_id_fk": { + "name": "maintenance_workspace_id_workspace_id_fk", + "tableFrom": "maintenance", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "maintenance_page_id_page_id_fk": { + "name": "maintenance_page_id_page_id_fk", + "tableFrom": "maintenance", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "check": { + "name": "check", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "regions": { + "name": "regions", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "url": { + "name": "url", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'GET'" + }, + "count_requests": { + "name": "count_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "check_workspace_id_idx": { + "name": "check_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "check_workspace_id_workspace_id_fk": { + "name": "check_workspace_id_workspace_id_fk", + "tableFrom": "check", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_run": { + "name": "monitor_run", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "runned_at": { + "name": "runned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_run_workspace_id_created_at_idx": { + "name": "monitor_run_workspace_id_created_at_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "monitor_run_monitor_id_idx": { + "name": "monitor_run_monitor_id_idx", + "columns": [ + "monitor_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_run_workspace_id_workspace_id_fk": { + "name": "monitor_run_workspace_id_workspace_id_fk", + "tableFrom": "monitor_run", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_run_monitor_id_monitor_id_fk": { + "name": "monitor_run_monitor_id_monitor_id_fk", + "tableFrom": "monitor_run", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location_monitor_status": { + "name": "private_location_monitor_status", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_location_id": { + "name": "private_location_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "private_location_monitor_status_pl_id_idx": { + "name": "private_location_monitor_status_pl_id_idx", + "columns": [ + "private_location_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_monitor_status_monitor_id_monitor_id_fk": { + "name": "private_location_monitor_status_monitor_id_monitor_id_fk", + "tableFrom": "private_location_monitor_status", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "private_location_monitor_status_private_location_id_private_location_id_fk": { + "name": "private_location_monitor_status_private_location_id_private_location_id_fk", + "tableFrom": "private_location_monitor_status", + "tableTo": "private_location", + "columnsFrom": [ + "private_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "private_location_monitor_status_monitor_id_private_location_id_pk": { + "columns": [ + "monitor_id", + "private_location_id" + ], + "name": "private_location_monitor_status_monitor_id_private_location_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location": { + "name": "private_location", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'error'" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "private_location_workspace_id_idx": { + "name": "private_location_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_workspace_id_workspace_id_fk": { + "name": "private_location_workspace_id_workspace_id_fk", + "tableFrom": "private_location", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location_to_monitor": { + "name": "private_location_to_monitor", + "columns": { + "private_location_id": { + "name": "private_location_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "private_location_to_monitor_private_location_id_idx": { + "name": "private_location_to_monitor_private_location_id_idx", + "columns": [ + "private_location_id" + ], + "isUnique": false + }, + "private_location_to_monitor_monitor_id_idx": { + "name": "private_location_to_monitor_monitor_id_idx", + "columns": [ + "monitor_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_to_monitor_private_location_id_private_location_id_fk": { + "name": "private_location_to_monitor_private_location_id_private_location_id_fk", + "tableFrom": "private_location_to_monitor", + "tableTo": "private_location", + "columnsFrom": [ + "private_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "private_location_to_monitor_monitor_id_monitor_id_fk": { + "name": "private_location_to_monitor_monitor_id_monitor_id_fk", + "tableFrom": "private_location_to_monitor", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_group": { + "name": "monitor_group", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_group_workspace_id_idx": { + "name": "monitor_group_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "monitor_group_page_id_idx": { + "name": "monitor_group_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_group_workspace_id_workspace_id_fk": { + "name": "monitor_group_workspace_id_workspace_id_fk", + "tableFrom": "monitor_group", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "monitor_group_page_id_page_id_fk": { + "name": "monitor_group_page_id_page_id_fk", + "tableFrom": "monitor_group", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer": { + "name": "viewer", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "viewer_email_unique": { + "name": "viewer_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer_accounts": { + "name": "viewer_accounts", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "viewer_accounts_user_id_viewer_id_fk": { + "name": "viewer_accounts_user_id_viewer_id_fk", + "tableFrom": "viewer_accounts", + "tableTo": "viewer", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "viewer_accounts_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "viewer_accounts_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer_session": { + "name": "viewer_session", + "columns": { + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "viewer_session_user_id_viewer_id_fk": { + "name": "viewer_session_user_id_viewer_id_fk", + "tableFrom": "viewer_session", + "tableTo": "viewer", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_key": { + "name": "api_key", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hashed_token": { + "name": "hashed_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"write\"]'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "api_key_prefix_unique": { + "name": "api_key_prefix_unique", + "columns": [ + "prefix" + ], + "isUnique": true + }, + "api_key_hashed_token_unique": { + "name": "api_key_hashed_token_unique", + "columns": [ + "hashed_token" + ], + "isUnique": true + }, + "api_key_prefix_idx": { + "name": "api_key_prefix_idx", + "columns": [ + "prefix" + ], + "isUnique": false + }, + "api_key_workspace_id_idx": { + "name": "api_key_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_id_user_id_fk": { + "name": "api_key_created_by_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "maintenance_to_page_component": { + "name": "maintenance_to_page_component", + "columns": { + "maintenance_id": { + "name": "maintenance_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "maintenance_to_page_component_page_component_id_idx": { + "name": "maintenance_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "maintenance_to_page_component_maintenance_id_maintenance_id_fk": { + "name": "maintenance_to_page_component_maintenance_id_maintenance_id_fk", + "tableFrom": "maintenance_to_page_component", + "tableTo": "maintenance", + "columnsFrom": [ + "maintenance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "maintenance_to_page_component_page_component_id_page_component_id_fk": { + "name": "maintenance_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "maintenance_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "maintenance_to_page_component_maintenance_id_page_component_id_pk": { + "columns": [ + "maintenance_id", + "page_component_id" + ], + "name": "maintenance_to_page_component_maintenance_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_component": { + "name": "page_component", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'monitor'" + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_order": { + "name": "group_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_component_workspace_id_idx": { + "name": "page_component_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "page_component_page_id_monitor_id_unique": { + "name": "page_component_page_id_monitor_id_unique", + "columns": [ + "page_id", + "monitor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "page_component_workspace_id_workspace_id_fk": { + "name": "page_component_workspace_id_workspace_id_fk", + "tableFrom": "page_component", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_page_id_page_id_fk": { + "name": "page_component_page_id_page_id_fk", + "tableFrom": "page_component", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_monitor_id_monitor_id_fk": { + "name": "page_component_monitor_id_monitor_id_fk", + "tableFrom": "page_component", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_group_id_page_component_groups_id_fk": { + "name": "page_component_group_id_page_component_groups_id_fk", + "tableFrom": "page_component", + "tableTo": "page_component_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "page_component_type_check": { + "name": "page_component_type_check", + "value": "\"page_component\".\"type\" = 'monitor' AND \"page_component\".\"monitor_id\" IS NOT NULL OR \"page_component\".\"type\" = 'static' AND \"page_component\".\"monitor_id\" IS NULL" + } + } + }, + "status_report_update_to_page_component": { + "name": "status_report_update_to_page_component", + "columns": { + "status_report_update_id": { + "name": "status_report_update_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impact": { + "name": "impact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_update_to_page_component_page_component_id_idx": { + "name": "status_report_update_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_update_to_page_component_status_report_update_id_status_report_update_id_fk": { + "name": "status_report_update_to_page_component_status_report_update_id_status_report_update_id_fk", + "tableFrom": "status_report_update_to_page_component", + "tableTo": "status_report_update", + "columnsFrom": [ + "status_report_update_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_update_to_page_component_page_component_id_page_component_id_fk": { + "name": "status_report_update_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "status_report_update_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "status_report_update_to_page_component_status_report_update_id_page_component_id_pk": { + "columns": [ + "status_report_update_id", + "page_component_id" + ], + "name": "status_report_update_to_page_component_status_report_update_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report_to_page_component": { + "name": "status_report_to_page_component", + "columns": { + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_to_page_component_page_component_id_idx": { + "name": "status_report_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_to_page_component_status_report_id_status_report_id_fk": { + "name": "status_report_to_page_component_status_report_id_status_report_id_fk", + "tableFrom": "status_report_to_page_component", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_to_page_component_page_component_id_page_component_id_fk": { + "name": "status_report_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "status_report_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "status_report_to_page_component_status_report_id_page_component_id_pk": { + "columns": [ + "status_report_id", + "page_component_id" + ], + "name": "status_report_to_page_component_status_report_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_component_groups": { + "name": "page_component_groups", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_open": { + "name": "default_open", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_component_groups_page_id_idx": { + "name": "page_component_groups_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "page_component_groups_workspace_id_idx": { + "name": "page_component_groups_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "page_component_groups_workspace_id_workspace_id_fk": { + "name": "page_component_groups_workspace_id_workspace_id_fk", + "tableFrom": "page_component_groups", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_groups_page_id_page_id_fk": { + "name": "page_component_groups_page_id_page_id_fk", + "tableFrom": "page_component_groups", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocker": { + "name": "blocker", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "feedback_workspace_id_idx": { + "name": "feedback_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "feedback_workspace_id_workspace_id_fk": { + "name": "feedback_workspace_id_workspace_id_fk", + "tableFrom": "feedback", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_user_id_user_id_fk": { + "name": "feedback_user_id_user_id_fk", + "tableFrom": "feedback", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_log": { + "name": "audit_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "before": { + "name": "before", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "after": { + "name": "after", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_fields": { + "name": "changed_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "audit_log_entity_idx": { + "name": "audit_log_entity_idx", + "columns": [ + "workspace_id", + "entity_type", + "entity_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service": { + "name": "external_service", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(json_array())" + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_page_url": { + "name": "status_page_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "industry": { + "name": "industry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_config": { + "name": "api_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_slug_unique": { + "name": "external_service_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "external_service_deleted_at_idx": { + "name": "external_service_deleted_at_idx", + "columns": [ + "deleted_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_component": { + "name": "external_service_component", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "upstream_component_id": { + "name": "upstream_component_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(json_array())" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "indicator": { + "name": "indicator", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_component_unique_idx": { + "name": "external_service_component_unique_idx", + "columns": [ + "external_service_id", + "upstream_component_id" + ], + "isUnique": true + }, + "external_service_component_slug_unique_idx": { + "name": "external_service_component_slug_unique_idx", + "columns": [ + "external_service_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "external_service_component_external_service_id_external_service_id_fk": { + "name": "external_service_component_external_service_id_external_service_id_fk", + "tableFrom": "external_service_component", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_incident": { + "name": "external_service_incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_incident_id": { + "name": "provider_incident_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impact": { + "name": "impact", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shortlink": { + "name": "shortlink", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affected_component_ids": { + "name": "affected_component_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_payload_purged_at": { + "name": "raw_payload_purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_incident_unique_idx": { + "name": "external_service_incident_unique_idx", + "columns": [ + "external_service_id", + "provider_incident_id" + ], + "isUnique": true + }, + "external_service_incident_started_at_idx": { + "name": "external_service_incident_started_at_idx", + "columns": [ + "external_service_id", + "started_at" + ], + "isUnique": false + }, + "external_service_incident_resolved_at_idx": { + "name": "external_service_incident_resolved_at_idx", + "columns": [ + "resolved_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "external_service_incident_external_service_id_external_service_id_fk": { + "name": "external_service_incident_external_service_id_external_service_id_fk", + "tableFrom": "external_service_incident", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_report": { + "name": "external_service_report", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_service_component_id": { + "name": "external_service_component_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporter_hash": { + "name": "reporter_hash", + "type": "text(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text(2)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_report_service_idx": { + "name": "external_service_report_service_idx", + "columns": [ + "external_service_id", + "created_at" + ], + "isUnique": false + }, + "external_service_report_component_idx": { + "name": "external_service_report_component_idx", + "columns": [ + "external_service_component_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "external_service_report_external_service_id_external_service_id_fk": { + "name": "external_service_report_external_service_id_external_service_id_fk", + "tableFrom": "external_service_report", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_service_report_external_service_component_id_external_service_component_id_fk": { + "name": "external_service_report_external_service_component_id_external_service_component_id_fk", + "tableFrom": "external_service_report", + "tableTo": "external_service_component", + "columnsFrom": [ + "external_service_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_session": { + "name": "chat_session", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "messages": { + "name": "messages", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "chat_session_workspace_user_updated_idx": { + "name": "chat_session_workspace_user_updated_idx", + "columns": [ + "workspace_id", + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chat_session_workspace_id_workspace_id_fk": { + "name": "chat_session_workspace_id_workspace_id_fk", + "tableFrom": "chat_session", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_session_user_id_user_id_fk": { + "name": "chat_session_user_id_user_id_fk", + "tableFrom": "chat_session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "frozen_monitor_uptime": { + "name": "frozen_monitor_uptime", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "month": { + "name": "month", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "days": { + "name": "days", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "frozen_monitor_uptime_workspace_id_idx": { + "name": "frozen_monitor_uptime_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "frozen_monitor_uptime_monitor_id_month_unique": { + "name": "frozen_monitor_uptime_monitor_id_month_unique", + "columns": [ + "monitor_id", + "month" + ], + "isUnique": true + } + }, + "foreignKeys": { + "frozen_monitor_uptime_workspace_id_workspace_id_fk": { + "name": "frozen_monitor_uptime_workspace_id_workspace_id_fk", + "tableFrom": "frozen_monitor_uptime", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "frozen_monitor_uptime_monitor_id_monitor_id_fk": { + "name": "frozen_monitor_uptime_monitor_id_monitor_id_fk", + "tableFrom": "frozen_monitor_uptime", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_transition": { + "name": "monitor_transition", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quorum_count": { + "name": "quorum_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region_count": { + "name": "region_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "transitioned": { + "name": "transitioned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outbox_rows": { + "name": "outbox_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "monitor_transition_monitor_id_cron_timestamp_idx": { + "name": "monitor_transition_monitor_id_cron_timestamp_idx", + "columns": [ + "monitor_id", + "cron_timestamp" + ], + "isUnique": false + }, + "monitor_transition_created_at_idx": { + "name": "monitor_transition_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_transition_monitor_id_monitor_id_fk": { + "name": "monitor_transition_monitor_id_monitor_id_fk", + "tableFrom": "monitor_transition", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_client": { + "name": "oauth_client", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'MCP Client'" + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_session": { + "name": "oauth_session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'S256'" + }, + "decided_at": { + "name": "decided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_session_client_id_idx": { + "name": "oauth_session_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_session_expires_at_idx": { + "name": "oauth_session_expires_at_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_session_client_id_oauth_client_client_id_fk": { + "name": "oauth_session_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_session", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_authorization_code": { + "name": "oauth_authorization_code", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_id": { + "name": "grant_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "oauth_authorization_code_hash_unique": { + "name": "oauth_authorization_code_hash_unique", + "columns": [ + "hash" + ], + "isUnique": true + }, + "oauth_authorization_code_expires_at_idx": { + "name": "oauth_authorization_code_expires_at_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "oauth_authorization_code_client_id_idx": { + "name": "oauth_authorization_code_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_authorization_code_client_id_oauth_client_client_id_fk": { + "name": "oauth_authorization_code_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_user_id_user_id_fk": { + "name": "oauth_authorization_code_user_id_user_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_workspace_id_workspace_id_fk": { + "name": "oauth_authorization_code_workspace_id_workspace_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_grant_id_oauth_grant_id_fk": { + "name": "oauth_authorization_code_grant_id_oauth_grant_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "oauth_grant", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_grant": { + "name": "oauth_grant", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_hash": { + "name": "access_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_hash": { + "name": "refresh_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "previous_refresh_token_hash": { + "name": "previous_refresh_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "oauth_grant_access_token_hash_unique": { + "name": "oauth_grant_access_token_hash_unique", + "columns": [ + "access_token_hash" + ], + "isUnique": true + }, + "oauth_grant_refresh_token_hash_unique": { + "name": "oauth_grant_refresh_token_hash_unique", + "columns": [ + "refresh_token_hash" + ], + "isUnique": true + }, + "oauth_grant_workspace_id_idx": { + "name": "oauth_grant_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "oauth_grant_client_id_user_id_idx": { + "name": "oauth_grant_client_id_user_id_idx", + "columns": [ + "client_id", + "user_id" + ], + "isUnique": false + }, + "oauth_grant_user_id_idx": { + "name": "oauth_grant_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauth_grant_previous_refresh_token_hash_idx": { + "name": "oauth_grant_previous_refresh_token_hash_idx", + "columns": [ + "previous_refresh_token_hash" + ], + "isUnique": false + }, + "oauth_grant_refresh_token_expires_at_idx": { + "name": "oauth_grant_refresh_token_expires_at_idx", + "columns": [ + "refresh_token_expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_grant_client_id_oauth_client_client_id_fk": { + "name": "oauth_grant_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_grant_user_id_user_id_fk": { + "name": "oauth_grant_user_id_user_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_grant_workspace_id_workspace_id_fk": { + "name": "oauth_grant_workspace_id_workspace_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident": { + "name": "incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commander_id": { + "name": "commander_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "declared_by": { + "name": "declared_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "declared_at": { + "name": "declared_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mitigated_at": { + "name": "mitigated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closed_at": { + "name": "closed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "incident_workspace_id_status_idx": { + "name": "incident_workspace_id_status_idx", + "columns": [ + "workspace_id", + "status" + ], + "isUnique": false + }, + "incident_workspace_id_declared_at_idx": { + "name": "incident_workspace_id_declared_at_idx", + "columns": [ + "workspace_id", + "declared_at" + ], + "isUnique": false + }, + "incident_slack_channel_idx": { + "name": "incident_slack_channel_idx", + "columns": [ + "slack_team_id", + "slack_channel_id" + ], + "isUnique": true, + "where": "\"incident\".\"slack_channel_id\" IS NOT NULL" + }, + "incident_status_report_id_idx": { + "name": "incident_status_report_id_idx", + "columns": [ + "status_report_id" + ], + "isUnique": true, + "where": "\"incident\".\"status_report_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "incident_workspace_id_workspace_id_fk": { + "name": "incident_workspace_id_workspace_id_fk", + "tableFrom": "incident", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_commander_id_user_id_fk": { + "name": "incident_commander_id_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "commander_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_declared_by_user_id_fk": { + "name": "incident_declared_by_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "declared_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_resolved_by_user_id_fk": { + "name": "incident_resolved_by_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "resolved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_status_report_id_status_report_id_fk": { + "name": "incident_status_report_id_status_report_id_fk", + "tableFrom": "incident", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident_event": { + "name": "incident_event", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "incident_event_incident_id_created_at_idx": { + "name": "incident_event_incident_id_created_at_idx", + "columns": [ + "incident_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "incident_event_incident_id_incident_id_fk": { + "name": "incident_event_incident_id_incident_id_fk", + "tableFrom": "incident_event", + "tableTo": "incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "incident_event_created_by_user_id_fk": { + "name": "incident_event_created_by_user_id_fk", + "tableFrom": "incident_event", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident_postmortem": { + "name": "incident_postmortem", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_transcript": { + "name": "source_transcript", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "drafted_by": { + "name": "drafted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "approved_by": { + "name": "approved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "approved_at": { + "name": "approved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "incident_postmortem_incident_id_unique": { + "name": "incident_postmortem_incident_id_unique", + "columns": [ + "incident_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "incident_postmortem_incident_id_incident_id_fk": { + "name": "incident_postmortem_incident_id_incident_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "incident_postmortem_created_by_user_id_fk": { + "name": "incident_postmortem_created_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_postmortem_updated_by_user_id_fk": { + "name": "incident_postmortem_updated_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_postmortem_approved_by_user_id_fk": { + "name": "incident_postmortem_approved_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "approved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slack_user": { + "name": "slack_user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "slack_user_user_id_idx": { + "name": "slack_user_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "slack_user_workspace_id_slack_team_id_slack_user_id_unique": { + "name": "slack_user_workspace_id_slack_team_id_slack_user_id_unique", + "columns": [ + "workspace_id", + "slack_team_id", + "slack_user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "slack_user_workspace_id_workspace_id_fk": { + "name": "slack_user_workspace_id_workspace_id_fk", + "tableFrom": "slack_user", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_user_user_id_user_id_fk": { + "name": "slack_user_user_id_user_id_fk", + "tableFrom": "slack_user", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "page_lower_slug_idx": { + "columns": { + "LOWER(\"slug\")": { + "isExpression": true + } + } + }, + "page_lower_custom_domain_idx": { + "columns": { + "LOWER(\"custom_domain\")": { + "isExpression": true + } + } + }, + "idx_page_subscriber_email_page_active": { + "columns": { + "LOWER(\"email\")": { + "isExpression": true + } + } + }, + "idx_page_subscriber_webhook_page_active": { + "columns": { + "LOWER(\"webhook_url\")": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 7b5ea0fb..9616a1a3 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -631,6 +631,13 @@ "when": 1790599837204, "tag": "0089_hesitant_dragon_man", "breakpoints": true + }, + { + "idx": 90, + "version": "6", + "when": 1790604809096, + "tag": "0090_condemned_photon", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/audit_logs/validation.ts b/packages/db/src/schema/audit_logs/validation.ts index 5b63ff00..3b9ccf93 100644 --- a/packages/db/src/schema/audit_logs/validation.ts +++ b/packages/db/src/schema/audit_logs/validation.ts @@ -139,6 +139,15 @@ const incidentEventActions = [ }), ] as const; +const incidentPostmortemActions = [ + action("incident_postmortem.create", "incident_postmortem", intId, { + optionalMetadata: true, + }), + action("incident_postmortem.update", "incident_postmortem", intId, { + optionalMetadata: true, + }), +] as const; + const slackUserActions = [ action("slack_user.create", "slack_user", intId, { optionalMetadata: true }), action("slack_user.delete", "slack_user", intId, { optionalMetadata: true }), @@ -243,6 +252,7 @@ export const auditActionSchema = z.discriminatedUnion("action", [ ...monitorIncidentActions, ...incidentActions, ...incidentEventActions, + ...incidentPostmortemActions, ...slackUserActions, ...statusReportActions, ...statusReportUpdateActions, diff --git a/packages/db/src/schema/incidents/constants.ts b/packages/db/src/schema/incidents/constants.ts index 86dce86a..f8ebd618 100644 --- a/packages/db/src/schema/incidents/constants.ts +++ b/packages/db/src/schema/incidents/constants.ts @@ -30,3 +30,9 @@ export const incidentEventType = [ export type IncidentSeverity = (typeof incidentSeverity)[number]; export type IncidentStatus = (typeof incidentStatus)[number]; export type IncidentEventType = (typeof incidentEventType)[number]; + +export const postmortemStatus = ["draft", "approved"] as const; +export const postmortemAuthor = ["agent", "user"] as const; + +export type PostmortemStatus = (typeof postmortemStatus)[number]; +export type PostmortemAuthor = (typeof postmortemAuthor)[number]; diff --git a/packages/db/src/schema/incidents/index.ts b/packages/db/src/schema/incidents/index.ts index fca4940b..f1be2eb8 100644 --- a/packages/db/src/schema/incidents/index.ts +++ b/packages/db/src/schema/incidents/index.ts @@ -1,3 +1,4 @@ export * from "./constants"; export * from "./incident"; +export * from "./postmortem"; export * from "./validation"; diff --git a/packages/db/src/schema/incidents/postmortem.ts b/packages/db/src/schema/incidents/postmortem.ts new file mode 100644 index 00000000..0655c3a7 --- /dev/null +++ b/packages/db/src/schema/incidents/postmortem.ts @@ -0,0 +1,43 @@ +import { relations, sql } from "drizzle-orm"; +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +import { user } from "../users"; +import { postmortemAuthor, postmortemStatus } from "./constants"; +import { incident } from "./incident"; + +export const incidentPostmortem = sqliteTable("incident_postmortem", { + id: integer("id").primaryKey({ autoIncrement: true }), + incidentId: integer("incident_id") + .notNull() + .unique() + .references(() => incident.id, { onDelete: "cascade" }), + status: text("status", { enum: postmortemStatus }).notNull().default("draft"), + content: text("content").notNull(), + sourceTranscript: text("source_transcript"), + draftedBy: text("drafted_by", { enum: postmortemAuthor }).notNull(), + createdBy: integer("created_by").references(() => user.id), + updatedBy: integer("updated_by").references(() => user.id), + approvedBy: integer("approved_by").references(() => user.id), + approvedAt: integer("approved_at", { mode: "timestamp" }), + publishedAt: integer("published_at", { mode: "timestamp" }), + createdAt: integer("created_at", { mode: "timestamp" }) + .notNull() + .default(sql`(strftime('%s', 'now'))`), + updatedAt: integer("updated_at", { mode: "timestamp" }) + .notNull() + .default(sql`(strftime('%s', 'now'))`), +}); + +export const incidentPostmortemRelations = relations( + incidentPostmortem, + ({ one }) => ({ + incident: one(incident, { + fields: [incidentPostmortem.incidentId], + references: [incident.id], + }), + approvedByUser: one(user, { + fields: [incidentPostmortem.approvedBy], + references: [user.id], + }), + }), +); diff --git a/packages/db/src/schema/incidents/validation.ts b/packages/db/src/schema/incidents/validation.ts index f5b6f5c1..c8d62d33 100644 --- a/packages/db/src/schema/incidents/validation.ts +++ b/packages/db/src/schema/incidents/validation.ts @@ -2,9 +2,13 @@ import { createSelectSchema } from "drizzle-zod"; import type { z } from "zod"; import { incident, incidentEvent } from "./incident"; +import { incidentPostmortem } from "./postmortem"; export const selectIncidentSchema = createSelectSchema(incident); export const selectIncidentEventSchema = createSelectSchema(incidentEvent); +export const selectIncidentPostmortemSchema = + createSelectSchema(incidentPostmortem); export type Incident = z.infer; export type IncidentEvent = z.infer; +export type IncidentPostmortem = z.infer; diff --git a/packages/db/src/test/factories.ts b/packages/db/src/test/factories.ts index af4f6481..66e01e56 100644 --- a/packages/db/src/test/factories.ts +++ b/packages/db/src/test/factories.ts @@ -9,6 +9,7 @@ import { db as defaultDb } from "../db"; import { incident, incidentEvent, + incidentPostmortem, monitor, notification, notificationsToMonitors, @@ -33,6 +34,7 @@ type NotificationInsert = typeof notification.$inferInsert; type IncidentInsert = typeof incident.$inferInsert; type IncidentEventInsert = typeof incidentEvent.$inferInsert; type SlackUserInsert = typeof slackUser.$inferInsert; +type IncidentPostmortemInsert = typeof incidentPostmortem.$inferInsert; const unique = () => crypto.randomUUID().slice(0, 8); @@ -282,6 +284,26 @@ export async function createIncidentEvent( return row; } +export async function createIncidentPostmortem( + incidentId: number, + overrides: Partial = {}, + db: Db = defaultDb, +) { + const [row] = await withBusyRetry(() => + db + .insert(incidentPostmortem) + .values({ + incidentId, + content: "## Summary\n\nTest postmortem.", + draftedBy: "user", + ...overrides, + }) + .returning(), + ); + if (!row) throw new Error("factory insert returned no row"); + return row; +} + export async function createSlackUser( workspaceId: number, userId: number, diff --git a/packages/services/src/agent-tools/__tests__/incident.test.ts b/packages/services/src/agent-tools/__tests__/incident.test.ts index b6d3dbcd..95db28d0 100644 --- a/packages/services/src/agent-tools/__tests__/incident.test.ts +++ b/packages/services/src/agent-tools/__tests__/incident.test.ts @@ -76,7 +76,8 @@ describe("incident agent tools", () => { test("every incident tool is feature-gated, the rest are not", () => { for (const tool of Object.values(agentTools)) { - const isIncident = tool.name.includes("incident"); + const isIncident = + tool.name.includes("incident") || tool.name.includes("postmortem"); expect(tool.feature === "incident-management").toBe(isIncident); } expect(isAgentToolAvailable(listIncidentsTool, { id: 1 })).toBe(true); diff --git a/packages/services/src/agent-tools/incident.ts b/packages/services/src/agent-tools/incident.ts index ec271830..a4a72812 100644 --- a/packages/services/src/agent-tools/incident.ts +++ b/packages/services/src/agent-tools/incident.ts @@ -8,9 +8,11 @@ import { tryGetActorUserId } from "../context"; import { NotFoundError } from "../errors"; import { addIncidentNote, + approvePostmortem, declareIncident, displayName, getIncident, + getPostmortem, listIncidentEvents, listIncidents, setIncidentStatus, @@ -429,6 +431,87 @@ export const addIncidentNoteTool: AgentTool< }, }; +const PostmortemInput = z.object({ + id: z.number().int().describe("Incident id, from list_incidents."), +}); + +const GetPostmortemOutput = z.object({ + incidentId: z.number().int(), + exists: z.boolean(), + status: z.enum(["draft", "approved"]).nullable(), + content: z.string().nullable(), + draftedBy: z.enum(["agent", "user"]).nullable(), +}); + +export const getPostmortemTool: AgentTool< + z.infer, + z.infer +> = { + name: "get_postmortem", + description: + "Read a managed incident's postmortem (markdown), if one was drafted.", + scope: "read", + destructive: false, + feature: FEATURE, + inputSchema: PostmortemInput, + outputSchema: GetPostmortemOutput, + async run({ ctx, input }) { + const row = await getPostmortem({ ctx, input }); + return { + incidentId: input.id, + exists: row !== undefined, + status: row?.status ?? null, + content: row?.content ?? null, + draftedBy: row?.draftedBy ?? null, + }; + }, +}; + +const ApprovePostmortemToolInput = PostmortemInput.extend({ + close: z + .boolean() + .default(true) + .describe("Also close the incident (the usual last step)."), +}); + +const ApprovePostmortemOutput = z.object({ + incidentId: z.number().int(), + status: z.enum(["draft", "approved"]), +}); + +export const approvePostmortemTool: AgentTool< + z.infer, + z.infer +> = { + name: "approve_postmortem", + description: + "Approve a managed incident's postmortem draft and, by default, close the incident. Only an admin, owner or the incident's commander can approve.", + scope: "write", + destructive: true, + feature: FEATURE, + inputSchema: ApprovePostmortemToolInput, + outputSchema: ApprovePostmortemOutput, + approval: { + summarize: (input) => ({ + title: `Approve the postmortem of incident #${input.id}`, + lines: [ + { + label: "Then", + value: input.close === false ? "keep it open" : "close the incident", + }, + ], + }), + verb: "approved", + }, + async run({ ctx, input }) { + const row = await approvePostmortem({ + ctx, + input: { id: input.id, close: input.close }, + }); + return { incidentId: input.id, status: row.status }; + }, +}; + function optionalLines( entries: Array<[string, string | undefined]>, ): SummaryLine[] { diff --git a/packages/services/src/agent-tools/index.ts b/packages/services/src/agent-tools/index.ts index e2fef14a..ba733382 100644 --- a/packages/services/src/agent-tools/index.ts +++ b/packages/services/src/agent-tools/index.ts @@ -7,8 +7,10 @@ import { getContentPageTool, searchContentTool } from "./content"; import { getDocPageTool, searchDocsTool } from "./docs"; import { addIncidentNoteTool, + approvePostmortemTool, declareIncidentTool, getIncidentTool, + getPostmortemTool, listIncidentsTool, resolveIncidentTool, setIncidentStatusTool, @@ -41,8 +43,10 @@ export { getContentPageTool, searchContentTool } from "./content"; export { getDocPageTool, searchDocsTool } from "./docs"; export { addIncidentNoteTool, + approvePostmortemTool, declareIncidentTool, getIncidentTool, + getPostmortemTool, listIncidentsTool, resolveIncidentTool, setIncidentStatusTool, @@ -109,6 +113,8 @@ export const agentTools = { resolve_incident: resolveIncidentTool, set_incident_status: setIncidentStatusTool, add_incident_note: addIncidentNoteTool, + get_postmortem: getPostmortemTool, + approve_postmortem: approvePostmortemTool, list_maintenances: listMaintenancesTool, create_maintenance: createMaintenanceTool, list_monitors: listMonitorsTool, diff --git a/packages/services/src/agent-tools/prompt.ts b/packages/services/src/agent-tools/prompt.ts index 9b2b0a51..df2e0cf0 100644 --- a/packages/services/src/agent-tools/prompt.ts +++ b/packages/services/src/agent-tools/prompt.ts @@ -51,6 +51,7 @@ Managed incidents (internal): - "note that…", "add to the timeline", "log that…" → add_incident_note (internal, runs without confirmation). - "the incident is fixed/resolved" with a managed incident in play → resolve_incident; if its linked status report is still open, ask whether to resolve that too (resolve_status_report). - "mitigated", "the bleeding stopped" → set_incident_status mitigated; "false alarm", "declared by mistake" → set_incident_status canceled (this closes it). +- Postmortems: get_postmortem reads it; approve_postmortem signs it off and by default closes the incident. - severity: critical = major outage or data loss, major = significant degradation, minor = limited impact. Ask when unclear.` : ""; diff --git a/packages/services/src/incident/__tests__/hooks.test.ts b/packages/services/src/incident/__tests__/hooks.test.ts index d494ca81..0f149c6d 100644 --- a/packages/services/src/incident/__tests__/hooks.test.ts +++ b/packages/services/src/incident/__tests__/hooks.test.ts @@ -66,7 +66,10 @@ describe("member removal", () => { ctx, input: { id: closed.id, status: "resolved" }, }); - await closeIncident({ ctx, input: { id: closed.id } }); + await closeIncident({ + ctx, + input: { id: closed.id, skipPostmortem: true }, + }); const link = await createSlackUser(workspace.id, leaver.id, {}, tx); await removeMemberInWorkspace({ tx, ctx, userId: leaver.id }); diff --git a/packages/services/src/incident/__tests__/incident.test.ts b/packages/services/src/incident/__tests__/incident.test.ts index 85fb6690..640c411b 100644 --- a/packages/services/src/incident/__tests__/incident.test.ts +++ b/packages/services/src/incident/__tests__/incident.test.ts @@ -577,11 +577,14 @@ describe("closeIncident", () => { input: { id: row.id, status: "resolved" }, }); await expect( - closeIncident({ ctx: as(memberId, tx), input: { id: row.id } }), + closeIncident({ + ctx: as(memberId, tx), + input: { id: row.id, skipPostmortem: true }, + }), ).rejects.toThrow(ForbiddenError); const closed = await closeIncident({ ctx: as(adminId, tx), - input: { id: row.id }, + input: { id: row.id, skipPostmortem: true }, }); expect(closed.closedAt).not.toBeNull(); await expectInvariant(tx, row.id); @@ -597,7 +600,7 @@ describe("closeIncident", () => { }); const closed = await closeIncident({ ctx: as(memberId, tx), - input: { id: row.id }, + input: { id: row.id, skipPostmortem: true }, }); expect(closed.closedAt).not.toBeNull(); }); @@ -607,7 +610,10 @@ describe("closeIncident", () => { await withTestTransaction(async (tx) => { const row = await declare(tx); await expect( - closeIncident({ ctx: as(ownerId, tx), input: { id: row.id } }), + closeIncident({ + ctx: as(ownerId, tx), + input: { id: row.id, skipPostmortem: true }, + }), ).rejects.toThrow(ConflictError); }); }); diff --git a/packages/services/src/incident/__tests__/postmortem.test.ts b/packages/services/src/incident/__tests__/postmortem.test.ts new file mode 100644 index 00000000..d67613b6 --- /dev/null +++ b/packages/services/src/incident/__tests__/postmortem.test.ts @@ -0,0 +1,193 @@ +import { and, eq, sql } from "@openstatus/db"; +import { auditLog, incidentEvent } from "@openstatus/db/src/schema"; +import { + addUserToWorkspace, + createUser, +} from "@openstatus/db/src/test/factories"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + expectAuditRow, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { DB, ServiceContext } from "../../context"; +import { ConflictError, ForbiddenError } from "../../errors"; +import type { Workspace } from "../../types"; +import { + approvePostmortem, + closeIncident, + declareIncident, + draftPostmortem, + getIncident, + getPostmortem, + setIncidentStatus, +} from "../index"; + +let workspace: Workspace; +let ownerId: number; +let memberId: number; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + workspace = fixture.workspace; + ownerId = fixture.userId; + memberId = (await createUser()).id; + await addUserToWorkspace(memberId, workspace.id, "member"); +}); + +const as = (userId: number, tx: DB): ServiceContext => ({ + ...makeUserCtx(workspace, { userId }), + db: tx, +}); + +async function resolved(tx: DB) { + const row = await declareIncident({ + ctx: as(memberId, tx), + input: { title: "API down", severity: "major", commanderId: ownerId }, + }); + await setIncidentStatus({ + ctx: as(memberId, tx), + input: { id: row.id, status: "resolved" }, + }); + return row; +} + +async function eventTypes(tx: DB, incidentId: number) { + const rows = await tx + .select({ type: incidentEvent.type }) + .from(incidentEvent) + .where(eq(incidentEvent.incidentId, incidentId)) + .all(); + return rows.map((r) => r.type); +} + +async function eventAudits(tx: DB, incidentId: number) { + const rows = await tx + .select({ id: auditLog.id }) + .from(auditLog) + .where( + and( + eq(auditLog.action, "incident_event.create"), + sql`json_extract(${auditLog.metadata}, '$.incidentId') = ${incidentId}`, + ), + ) + .all(); + return rows.length; +} + +describe("postmortem", () => { + test("draft, redraft, approve and close; every event audited", async () => { + await withTestTransaction(async (tx) => { + const row = await resolved(tx); + const draft = await draftPostmortem({ + ctx: as(memberId, tx), + input: { + id: row.id, + content: "## Summary\n\nFirst", + draftedBy: "agent", + sourceTranscript: "U1: it's down", + }, + }); + expect(draft.status).toBe("draft"); + expect(draft.createdBy).toBe(memberId); + await expectAuditRow({ + workspaceId: workspace.id, + action: "incident_postmortem.create", + entityType: "incident_postmortem", + entityId: draft.id, + db: tx, + }); + + const redraft = await draftPostmortem({ + ctx: as(ownerId, tx), + input: { id: row.id, content: "## Summary\n\nSecond" }, + }); + expect(redraft.id).toBe(draft.id); + expect(redraft.sourceTranscript).toBe("U1: it's down"); + expect(redraft.updatedBy).toBe(ownerId); + + await expect( + closeIncident({ ctx: as(ownerId, tx), input: { id: row.id } }), + ).rejects.toThrow(ConflictError); + + const approved = await approvePostmortem({ + ctx: as(ownerId, tx), + input: { id: row.id, close: true }, + }); + expect(approved.status).toBe("approved"); + expect(approved.approvedBy).toBe(ownerId); + expect( + (await getIncident({ ctx: as(ownerId, tx), input: { id: row.id } })) + ?.closedAt, + ).not.toBeNull(); + + const types = await eventTypes(tx, row.id); + expect(types).toContain("postmortem_drafted"); + expect(types).toContain("postmortem_approved"); + expect(types).toContain("closed"); + expect(await eventAudits(tx, row.id)).toBe(types.length); + }); + }); + + test("editing an approved postmortem keeps it approved; the agent can't redraft it", async () => { + await withTestTransaction(async (tx) => { + const row = await resolved(tx); + await draftPostmortem({ + ctx: as(memberId, tx), + input: { id: row.id, content: "v1" }, + }); + await approvePostmortem({ + ctx: as(ownerId, tx), + input: { id: row.id }, + }); + const edited = await draftPostmortem({ + ctx: as(memberId, tx), + input: { id: row.id, content: "v2" }, + }); + expect(edited.status).toBe("approved"); + expect(edited.content).toBe("v2"); + expect(await eventTypes(tx, row.id)).toContain("postmortem_updated"); + await expect( + draftPostmortem({ + ctx: as(memberId, tx), + input: { id: row.id, content: "v3", draftedBy: "agent" }, + }), + ).rejects.toThrow(ConflictError); + expect( + (await getPostmortem({ ctx: as(memberId, tx), input: { id: row.id } })) + ?.content, + ).toBe("v2"); + }); + }); + + test("only admin, owner or the commander approves", async () => { + await withTestTransaction(async (tx) => { + const row = await resolved(tx); + await draftPostmortem({ + ctx: as(memberId, tx), + input: { id: row.id, content: "v1" }, + }); + await expect( + approvePostmortem({ ctx: as(memberId, tx), input: { id: row.id } }), + ).rejects.toThrow(ForbiddenError); + }); + }); + + test("an unresolved incident has no postmortem yet", async () => { + await withTestTransaction(async (tx) => { + const row = await declareIncident({ + ctx: as(memberId, tx), + input: { title: "x", severity: "minor" }, + }); + await expect( + draftPostmortem({ + ctx: as(memberId, tx), + input: { id: row.id, content: "too early" }, + }), + ).rejects.toThrow(ConflictError); + }); + }); +}); diff --git a/packages/services/src/incident/close.ts b/packages/services/src/incident/close.ts index bf5c2588..c943302e 100644 --- a/packages/services/src/incident/close.ts +++ b/packages/services/src/incident/close.ts @@ -1,10 +1,14 @@ import { and, eq, isNull } from "@openstatus/db"; -import { type Incident, incident } from "@openstatus/db/src/schema"; +import { + type Incident, + incident, + incidentPostmortem, +} from "@openstatus/db/src/schema"; import { emitAudit } from "../audit"; import { requireScope } from "../auth"; import { requireRole } from "../auth/require-role"; -import { type ServiceContext, withTransaction } from "../context"; +import { type DB, type ServiceContext, withTransaction } from "../context"; import { ConflictError } from "../errors"; import { appendIncidentEvent, @@ -12,17 +16,65 @@ import { getIncidentInWorkspace, requireIncidentFeature, } from "./internal"; -import { IncidentIdInput } from "./schemas"; +import { CloseIncidentInput } from "./schemas"; + +/** The close itself, inside a caller's transaction; checks are the caller's. */ +export async function closeIncidentInTx( + tx: DB, + ctx: ServiceContext, + existing: Incident, +): Promise { + if (existing.status !== "resolved") { + throw new ConflictError( + `Incident #${existing.id} must be resolved before it is closed`, + ); + } + const now = new Date(); + const updated = await tx + .update(incident) + .set({ closedAt: now, updatedAt: now }) + .where( + and( + eq(incident.id, existing.id), + eq(incident.status, "resolved"), + isNull(incident.closedAt), + ), + ) + .returning() + .get(); + if (!updated) { + throw new ConflictError(`Incident #${existing.id} changed concurrently`); + } + await emitAudit(tx, ctx, { + action: "incident.update", + entityType: "incident", + entityId: updated.id, + before: existing, + after: updated, + }); + await appendIncidentEvent(tx, ctx, { + incidentId: updated.id, + type: "closed", + message: "Incident closed", + createdAt: now, + }); + return updated; +} -/** Ends the incident's life: only a resolved one, by an admin, owner or its commander. */ +/** + * Ends the incident's life: only a resolved one, by an admin, owner or its + * commander, and only with an approved postmortem unless explicitly skipped. + */ +// The update and its `incident.update` audit row live in `closeIncidentInTx`. +// oxlint-disable-next-line openstatus/services-mutation-guards export async function closeIncident(args: { ctx: ServiceContext; - input: IncidentIdInput; + input: CloseIncidentInput; }): Promise { const { ctx } = args; requireScope(ctx, "write"); requireIncidentFeature(ctx); - const input = IncidentIdInput.parse(args.input); + const input = CloseIncidentInput.parse(args.input); return withTransaction(ctx, async (tx) => { const existing = await getIncidentInWorkspace( @@ -34,41 +86,18 @@ export async function closeIncident(args: { await requireRole(tx, ctx, ["owner", "admin"], { orUserId: existing.commanderId, }); - if (existing.status !== "resolved") { - throw new ConflictError( - `Incident #${existing.id} must be resolved before it is closed`, - ); - } - - const now = new Date(); - const updated = await tx - .update(incident) - .set({ closedAt: now, updatedAt: now }) - .where( - and( - eq(incident.id, existing.id), - eq(incident.status, "resolved"), - isNull(incident.closedAt), - ), - ) - .returning() - .get(); - if (!updated) { - throw new ConflictError(`Incident #${existing.id} changed concurrently`); + if (!input.skipPostmortem) { + const postmortem = await tx + .select({ status: incidentPostmortem.status }) + .from(incidentPostmortem) + .where(eq(incidentPostmortem.incidentId, existing.id)) + .get(); + if (postmortem?.status !== "approved") { + throw new ConflictError( + "Approve the postmortem first, or close without one", + ); + } } - await emitAudit(tx, ctx, { - action: "incident.update", - entityType: "incident", - entityId: updated.id, - before: existing, - after: updated, - }); - await appendIncidentEvent(tx, ctx, { - incidentId: updated.id, - type: "closed", - message: "Incident closed", - createdAt: now, - }); - return updated; + return closeIncidentInTx(tx, ctx, existing); }); } diff --git a/packages/services/src/incident/index.ts b/packages/services/src/incident/index.ts index b5ccf26a..c3c2df84 100644 --- a/packages/services/src/incident/index.ts +++ b/packages/services/src/incident/index.ts @@ -1,5 +1,5 @@ export { addIncidentNote } from "./add-note"; -export { closeIncident } from "./close"; +export { closeIncident, closeIncidentInTx } from "./close"; export { declareIncident } from "./declare"; export { deleteIncident, isDeletable } from "./delete"; export { allowedTransitions, displayName } from "./internal"; @@ -23,8 +23,16 @@ export { getIncidentForStatusReport, listIncidents, } from "./list"; +export { + approvePostmortem, + draftPostmortem, + getPostmortem, +} from "./postmortem"; export { AddIncidentNoteInput, + ApprovePostmortemInput, + CloseIncidentInput, + DraftPostmortemInput, BindIncidentSlackChannelInput, DeclareIncidentInput, IncidentIdInput, diff --git a/packages/services/src/incident/postmortem.ts b/packages/services/src/incident/postmortem.ts new file mode 100644 index 00000000..3bc84ead --- /dev/null +++ b/packages/services/src/incident/postmortem.ts @@ -0,0 +1,207 @@ +import { eq } from "@openstatus/db"; +import { + type IncidentPostmortem, + incidentPostmortem, +} from "@openstatus/db/src/schema"; + +import { emitAudit } from "../audit"; +import { requireScope } from "../auth"; +import { requireRole } from "../auth/require-role"; +import { + type DB, + type ServiceContext, + getReadDb, + tryGetActorUserId, + withTransaction, +} from "../context"; +import { ConflictError, NotFoundError } from "../errors"; +import { closeIncidentInTx } from "./close"; +import { + appendIncidentEvent, + getIncidentInWorkspace, + requireIncidentFeature, +} from "./internal"; +import { + ApprovePostmortemInput, + DraftPostmortemInput, + IncidentIdInput, +} from "./schemas"; + +async function findPostmortem( + db: DB, + incidentId: number, +): Promise { + return db + .select() + .from(incidentPostmortem) + .where(eq(incidentPostmortem.incidentId, incidentId)) + .get(); +} + +/** + * Writes the postmortem: creates the draft, or overwrites the body. An + * approved postmortem stays approved when a person edits it; the agent may + * only redraft while it is still a draft. + */ +export async function draftPostmortem(args: { + ctx: ServiceContext; + input: DraftPostmortemInput; +}): Promise { + const { ctx } = args; + requireScope(ctx, "write"); + requireIncidentFeature(ctx); + const input = DraftPostmortemInput.parse(args.input); + + return withTransaction(ctx, async (tx) => { + const row = await getIncidentInWorkspace(tx, ctx.workspace.id, input.id); + if (row.status !== "resolved") { + throw new ConflictError( + `A postmortem needs a resolved incident; #${row.id} is ${row.status}`, + ); + } + const userId = tryGetActorUserId(ctx.actor); + const existing = await findPostmortem(tx, row.id); + + if (!existing) { + const created = await tx + .insert(incidentPostmortem) + .values({ + incidentId: row.id, + content: input.content, + draftedBy: input.draftedBy, + sourceTranscript: input.sourceTranscript ?? null, + createdBy: userId, + updatedBy: userId, + }) + .returning() + .get(); + await emitAudit(tx, ctx, { + action: "incident_postmortem.create", + entityType: "incident_postmortem", + entityId: created.id, + after: created, + metadata: { incidentId: row.id }, + }); + await appendIncidentEvent(tx, ctx, { + incidentId: row.id, + type: "postmortem_drafted", + message: + input.draftedBy === "agent" + ? "Postmortem drafted by the agent" + : "Postmortem drafted", + }); + return created; + } + + if (existing.status === "approved" && input.draftedBy === "agent") { + throw new ConflictError( + "The postmortem is approved; edit it instead of redrafting", + ); + } + const updated = await tx + .update(incidentPostmortem) + .set({ + content: input.content, + draftedBy: + existing.status === "approved" ? existing.draftedBy : input.draftedBy, + ...(input.sourceTranscript !== undefined + ? { sourceTranscript: input.sourceTranscript } + : {}), + updatedBy: userId, + updatedAt: new Date(), + }) + .where(eq(incidentPostmortem.id, existing.id)) + .returning() + .get(); + await emitAudit(tx, ctx, { + action: "incident_postmortem.update", + entityType: "incident_postmortem", + entityId: updated.id, + before: existing, + after: updated, + metadata: { incidentId: row.id }, + }); + await appendIncidentEvent(tx, ctx, { + incidentId: row.id, + type: + existing.status === "approved" + ? "postmortem_updated" + : "postmortem_drafted", + message: + existing.status === "approved" + ? "Postmortem edited" + : input.draftedBy === "agent" + ? "Postmortem redrafted by the agent" + : "Postmortem draft updated", + }); + return updated; + }); +} + +/** Admin, owner or the commander signs the postmortem off; optionally closes. */ +export async function approvePostmortem(args: { + ctx: ServiceContext; + input: ApprovePostmortemInput; +}): Promise { + const { ctx } = args; + requireScope(ctx, "write"); + requireIncidentFeature(ctx); + const input = ApprovePostmortemInput.parse(args.input); + + return withTransaction(ctx, async (tx) => { + const row = await getIncidentInWorkspace(tx, ctx.workspace.id, input.id); + await requireRole(tx, ctx, ["owner", "admin"], { + orUserId: row.commanderId, + }); + const existing = await findPostmortem(tx, row.id); + if (!existing) throw new NotFoundError("incident_postmortem", row.id); + if (existing.status === "approved") { + throw new ConflictError("The postmortem is already approved"); + } + + const userId = tryGetActorUserId(ctx.actor); + const now = new Date(); + const approved = await tx + .update(incidentPostmortem) + .set({ + status: "approved", + approvedBy: userId, + approvedAt: now, + updatedBy: userId, + updatedAt: now, + }) + .where(eq(incidentPostmortem.id, existing.id)) + .returning() + .get(); + await emitAudit(tx, ctx, { + action: "incident_postmortem.update", + entityType: "incident_postmortem", + entityId: approved.id, + before: existing, + after: approved, + metadata: { incidentId: row.id }, + }); + await appendIncidentEvent(tx, ctx, { + incidentId: row.id, + type: "postmortem_approved", + message: "Postmortem approved", + createdAt: now, + }); + if (input.close && !row.closedAt) { + await closeIncidentInTx(tx, ctx, row); + } + return approved; + }); +} + +export async function getPostmortem(args: { + ctx: ServiceContext; + input: IncidentIdInput; +}): Promise { + const { ctx } = args; + requireIncidentFeature(ctx); + const input = IncidentIdInput.parse(args.input); + const db = getReadDb(ctx); + const row = await getIncidentInWorkspace(db, ctx.workspace.id, input.id); + return findPostmortem(db, row.id); +} diff --git a/packages/services/src/incident/schemas.ts b/packages/services/src/incident/schemas.ts index 2398735c..125857b3 100644 --- a/packages/services/src/incident/schemas.ts +++ b/packages/services/src/incident/schemas.ts @@ -76,3 +76,24 @@ export const ListIncidentsInput = z.object({ offset: z.number().int().min(0).default(0), }); export type ListIncidentsInput = z.input; + +export const CloseIncidentInput = z.object({ + id, + /** Close without an approved postmortem. */ + skipPostmortem: z.boolean().optional(), +}); +export type CloseIncidentInput = z.infer; + +export const DraftPostmortemInput = z.object({ + id, + content: z.string().trim().min(1).max(100_000), + draftedBy: z.enum(["agent", "user"]).default("user"), + sourceTranscript: z.string().max(2_000_000).nullish(), +}); +export type DraftPostmortemInput = z.input; + +export const ApprovePostmortemInput = z.object({ + id, + close: z.boolean().optional(), +}); +export type ApprovePostmortemInput = z.infer; -- 2.51.2