From 4272aa6400065d3496465119ee4abff7a82ecd1c Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 19:05:27 +0100 Subject: [PATCH 01/28] feat(alerts): anomalies page through alert rules The anomaly detector opened anomaly incidents that nobody could route, ack, silence or escalate. A new `anomaly` alert signal type reads the detector's open anomalies the way uptime and SLO rules read their sources, so every anomaly becomes an alert incident with the normal lifecycle. - One undeletable, unrouted default anomaly rule per org (backfilled on the anomaly tick) plus optional rules scoped by service, environment and signal. - An anomaly goes to the most specific rule that covers it and has a tier at or below its severity; it hands over when a narrower rule qualifies. - Incidents are keyed by the anomaly incident id, link `anomaly_incident_id`, reuse the anomaly's issue and triage (no second issue or triage), and store a `group_label` such as "checkout (production)" for every surface. - Notifications show the anomaly sentence instead of the severity code. - v2 API and MCP expose `anomaly_signals`, `anomaly_incident_id` and `group_label`. --- apps/ai/src/mcp/lib/alert-rule-evaluation.ts | 5 + apps/ai/src/mcp/lib/alert-rules.ts | 19 + apps/ai/src/mcp/tool-descriptors.generated.ts | 14 +- .../mcp/tools/__tests__/alert-tools.test.ts | 36 + apps/ai/src/mcp/tools/create-alert-rule.ts | 12 +- apps/ai/src/mcp/tools/delete-alert-rule.ts | 2 + apps/ai/src/mcp/tools/get-alert-rule.ts | 2 + .../ai/src/mcp/tools/get-incident-timeline.ts | 3 +- apps/ai/src/mcp/tools/list-alert-incidents.ts | 6 +- apps/ai/src/mcp/tools/preview-alert-rule.ts | 8 + apps/ai/src/mcp/tools/update-alert-rule.ts | 14 +- apps/alerting/src/scheduled.ts | 15 +- .../v2/alchemy-provider.integration.test.ts | 2 + .../api/src/routes/v2/alert-incidents.http.ts | 2 + apps/api/src/routes/v2/alert-rules.http.ts | 19 +- apps/api/src/routes/v2/alerts.http.test.ts | 45 + .../Maple/Components/AlertFormatting.swift | 2 + apps/ios/Maple/Features/Home/HomeView.swift | 2 +- .../MapleAPI/Sources/MapleAPI/openapi.json | 37 +- apps/web/src/page/alerts/hub/view-inbox.ts | 6 +- .../web/src/page/alerts/incident/view-rail.ts | 9 +- apps/web/src/page/alerts/rule/view-history.ts | 4 +- .../services/alerts/AlertChatCardService.ts | 1 + .../alerts/AlertDeliveryDispatch.test.ts | 56 +- .../services/alerts/AlertDeliveryDispatch.ts | 20 +- .../alerts/AlertDestinationDelivery.ts | 1 + .../services/alerts/AlertReadModelsService.ts | 2 + .../src/services/alerts/AlertRuleModel.ts | 111 +- .../src/services/alerts/AlertRulesService.ts | 92 +- .../alerts/AlertsService.anomaly.test.ts | 514 + .../src/services/alerts/AlertsService.ts | 99 +- .../src/services/alerts/alert-email.ts | 4 +- .../src/services/alerts/alert-formatting.ts | 68 +- .../services/alerts/alert-signal-display.ts | 1 + .../alerts/alerts-service-test-support.ts | 2 + .../alerts/anomaly/AnomalyAlertSource.test.ts | 422 + .../alerts/anomaly/AnomalyAlertSource.ts | 362 + .../alerts/anomaly/anomaly-rule.test.ts | 179 + .../services/alerts/anomaly/anomaly-rule.ts | 210 + .../alerts/anomaly/anomaly-rules.test.ts | 311 + .../services/alerts/delivery/chat-digest.ts | 7 +- .../src/services/alerts/delivery/context.ts | 2 + .../alerts/delivery/stored-payload.ts | 1 + .../alerts/delivery/transports/chat.ts | 4 +- .../alerts/delivery/transports/discord.ts | 4 +- .../alerts/delivery/transports/google-chat.ts | 4 +- .../alerts/delivery/transports/incident-io.ts | 7 +- .../alerts/delivery/transports/linear.ts | 4 +- .../delivery/transports/microsoft-teams.ts | 4 +- .../alerts/delivery/transports/pagerduty.ts | 8 +- .../services/push/MobilePushService.test.ts | 20 + .../src/services/push/MobilePushService.ts | 26 +- .../migration.sql | 8 + .../snapshot.json | 19183 ++++++++++++++++ packages/db/src/tables/alerts.ts | 13 + packages/domain/src/http/alerts.ts | 12 + .../domain/src/http/v2/alert-incidents.ts | 13 + packages/domain/src/http/v2/alert-rules.ts | 18 +- packages/domain/src/mcp-outputs/alerts.ts | 8 + packages/web-data/src/alerts/form-utils.ts | 4 + packages/web-data/src/alerts/headroom.ts | 19 + .../src/alerts/overview-derive.test.ts | 19 + .../web-data/src/alerts/overview-derive.ts | 11 +- packages/web-data/src/collections/alerts.ts | 24 + 64 files changed, 22016 insertions(+), 126 deletions(-) create mode 100644 packages/backend/src/services/alerts/AlertsService.anomaly.test.ts create mode 100644 packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.test.ts create mode 100644 packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts create mode 100644 packages/backend/src/services/alerts/anomaly/anomaly-rule.test.ts create mode 100644 packages/backend/src/services/alerts/anomaly/anomaly-rule.ts create mode 100644 packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts create mode 100644 packages/db/drizzle/20261106175508_alerts_anomaly_rules/migration.sql create mode 100644 packages/db/drizzle/20261106175508_alerts_anomaly_rules/snapshot.json diff --git a/apps/ai/src/mcp/lib/alert-rule-evaluation.ts b/apps/ai/src/mcp/lib/alert-rule-evaluation.ts index b1e4ae19e0..3c9001f0cd 100644 --- a/apps/ai/src/mcp/lib/alert-rule-evaluation.ts +++ b/apps/ai/src/mcp/lib/alert-rule-evaluation.ts @@ -69,6 +69,11 @@ export const firstEvaluation = Effect.fn("McpTool.alertFirstEvaluation")(functio DateTime.makeUnsafe(Math.ceil((now + 1) / SCHEDULER_TICK_MS) * SCHEDULER_TICK_MS), ) : null + // An anomaly rule reads the detector's open incidents, not a query, so there is nothing to preview. + if (rule.signalType === "anomaly") { + const evaluation: typeof AlertRuleFirstEvaluation.Type = { nextEvaluationAt, current: [] } + return { evaluation, warnings: noWarnings } + } const lookbackMs = Math.max(rule.windowMinutes * 6, 60) * 60_000 const preview = yield* decodePreviewRequest({ diff --git a/apps/ai/src/mcp/lib/alert-rules.ts b/apps/ai/src/mcp/lib/alert-rules.ts index 4943da9d11..46ce882294 100644 --- a/apps/ai/src/mcp/lib/alert-rules.ts +++ b/apps/ai/src/mcp/lib/alert-rules.ts @@ -9,6 +9,7 @@ import { AlertRuleId, AlertSeverity, AlertSignalType, + AnomalySignalType, type AlertIncidentDocument, type AlertRuleDocument, type AlertRuleDestinationNotFoundError, @@ -31,7 +32,12 @@ export const ALERT_SIGNAL_TYPES = AlertSignalType.pick([ "throughput", "builder_query", "raw_query", + "anomaly", ]).literals +export const ANOMALY_SIGNAL_TYPES = AnomalySignalType.literals +/** Shared wording for the `anomaly_signals` parameter of the rule tools. */ +export const ANOMALY_SIGNALS_DESCRIPTION = + "signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does." /** The range comparators need `thresholdUpper`, which the tools do not take. */ export const ALERT_COMPARATORS = AlertComparator.pick(["gt", "gte", "lt", "lte", "eq", "neq"]).literals export const ALERT_REDUCERS = QueryEngineAlertReducer.literals @@ -67,6 +73,17 @@ export const formatCondition = (rule: { ? `${rule.comparator} ${rule.threshold} and ${rule.thresholdUpper}` : `${comparatorLabel(rule.comparator)} ${rule.threshold}` +/** An anomaly rule's signal scope for display; undefined on other rules. */ +export const formatAnomalySignals = (rule: { + readonly signalType: string + readonly anomalySignals?: ReadonlyArray | undefined +}): string | undefined => + rule.signalType !== "anomaly" + ? undefined + : rule.anomalySignals === undefined || rule.anomalySignals.length === 0 + ? "All signals" + : Arr.join(rule.anomalySignals, ", ") + export const toAlertRuleRow = (rule: AlertRuleDocument): typeof AlertRuleRow.Type => ({ id: rule.id, name: rule.name, @@ -74,6 +91,7 @@ export const toAlertRuleRow = (rule: AlertRuleDocument): typeof AlertRuleRow.Typ severity: rule.severity, serviceNames: [...rule.serviceNames], environments: [...rule.environments], + ...(rule.anomalySignals === undefined ? undefined : { anomalySignals: [...rule.anomalySignals] }), signalType: rule.signalType, comparator: rule.comparator, threshold: rule.threshold, @@ -270,6 +288,7 @@ export const renderRuleWrite = ( "Environments", rule.environments.length > 0 ? Arr.join(rule.environments, ", ") : undefined, ], + ["Anomaly signals", formatAnomalySignals(rule)], ]), ], next: [ diff --git a/apps/ai/src/mcp/tool-descriptors.generated.ts b/apps/ai/src/mcp/tool-descriptors.generated.ts index 1da2b6d3b6..094659b483 100644 --- a/apps/ai/src/mcp/tool-descriptors.generated.ts +++ b/apps/ai/src/mcp/tool-descriptors.generated.ts @@ -27,15 +27,15 @@ export const MCP_TOOL_DESCRIPTORS: ReadonlyArray = [ {"name":"query_data","title":"Query Data","description":"Aggregate traces, logs, metrics or product events into a timeseries or a top-N breakdown. Start here for trends, comparisons and top-N; for errors prefer find_errors and error_detail; for a shape this tool cannot express, run_sql. Applied defaults are listed in the result. A traces breakdown by span_name or attribute reads raw spans: set `service` and keep the window short (a window too large is narrowed, with a note). For endpoint volume over days use route_usage.","inputSchema":{"type":"object","properties":{"source":{"type":"string","enum":["traces","logs","metrics","product_events"],"description":"'traces' for request/span analysis (latency, errors, throughput); 'logs' for log volume; 'metrics' for a custom metric (needs metric_name and metric_type from list_metrics); 'product_events' for track() events, page views and server events (names from list_product_events)."},"kind":{"type":"string","enum":["timeseries","breakdown"],"description":"'timeseries' for how a value changed over time; 'breakdown' for top-N or distribution. Pick the right one first rather than calling twice."},"metric":{"type":"string","description":"Traces: count, avg_duration, p50_duration, p95_duration, p99_duration, error_rate (0-1 ratio), apdex (needs apdex_threshold_ms). Logs: count. Product events: count, sessions, persons, users, visitors. Metrics: avg, sum, min, max, count, rate, increase. For counters (sum metrics, Prometheus *_total gauges) use increase (total over the window) or rate (per second); both are per-series and reset-aware. sum adds raw samples. Default: count, or avg for metrics."},"group_by":{"type":"string","description":"Traces: service, span_name, status_code, http_method, attribute, resource_attribute. Logs: service, severity. Metrics: service, attribute, resource_attribute. Product events: event_name, kind, source, host, page_path, service, group, attribute. 'attribute' and 'resource_attribute' need attribute_key (resource_attribute reads resource keys such as k8s.pod.name or deployment.environment). Timeseries also take 'none' (the default); breakdown defaults to service."},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 6 hours before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."},"service":{"type":"string","description":"Only this service (exact `service.name`)"},"span_name":{"type":"string","description":"Filter by span name (traces only). Exact match first; when nothing matches exactly it is matched as a case-insensitive substring"},"root_spans_only":{"type":"boolean","description":"Only include root spans (traces only)"},"environments":{"type":"array","items":{"type":"string"},"description":"Only these deployment environments (traces, logs, metrics; explore_attributes source=services lists them)"},"commit_shas":{"type":"array","items":{"type":"string"},"description":"Commit SHAs to filter (traces only)"},"apdex_threshold_ms":{"type":"number","description":"Apdex threshold in ms (traces only; needed for metric=apdex)"},"severity":{"type":"string","enum":["TRACE","DEBUG","INFO","WARN","ERROR","FATAL"],"description":"Only this log severity (logs only)"},"event_name":{"type":"array","items":{"type":"string"},"description":"Only these event names (product_events only)"},"event_kind":{"type":"array","items":{"type":"string","enum":["navigation","custom","screen"]},"description":"Only these event kinds (product_events only)"},"host":{"type":"string","description":"Only events fired on this site host (product_events only)"},"page_path":{"type":"string","description":"Only events fired on this page path (product_events only)"},"metric_name":{"type":"string","description":"The metric, from list_metrics (source=metrics only)"},"metric_type":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"],"description":"Type of `metric_name`, as list_metrics reports it"},"attribute_key":{"type":"string","description":"Attribute to filter on, or to group by with group_by=attribute. Span (or log/metric label) and resource attributes both work; the scope is resolved automatically"},"attribute_value":{"type":"string","description":"Exact value for attribute_key. Also applies when grouping by the same key"},"attribute_scope":{"type":"string","enum":["span","resource"],"description":"Where attribute_key lives: 'span' (span, log or metric-datapoint attributes) or 'resource' (deployment.environment, k8s.pod.name...). Resolved from the known keys when omitted"},"bucket_seconds":{"type":"number","description":"Bucket width in seconds (timeseries only; auto-computed when omitted)"},"limit":{"type":"number","description":"Max rows of a breakdown (default 10, max 100)"}},"required":["source","kind"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"kind":{"type":"string"},"metric":{"type":"string"},"groupBy":{"type":"string"},"decisions":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}},"queryContext":{"type":"object","properties":{"source":{"type":"string","enum":["traces","logs","metrics","product_events"]},"serviceName":{"type":"string"},"eventName":{"type":"string"},"eventKind":{"type":"string"},"host":{"type":"string"},"pagePath":{"type":"string"},"spanName":{"type":"string"},"rootSpansOnly":{"type":"boolean"},"environments":{"type":"array","items":{"type":"string"}},"commitShas":{"type":"array","items":{"type":"string"}},"severity":{"type":"string"},"metricName":{"type":"string"},"metricType":{"type":"string"},"attributeFilters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"},"mode":{"type":"string"}},"required":["key","mode"],"additionalProperties":true}},"apdexThresholdMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"bucketSeconds":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["source"],"additionalProperties":true},"unit":{"type":"string","enum":["duration_ms","duration_us","duration_s","duration_ns","percent","number","bytes","requests_per_sec"]},"result":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["timeseries"]},"data":{"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string"},"series":{"type":"object","additionalProperties":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}}},"required":["bucket","series"],"additionalProperties":true}}},"required":["kind","data"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","enum":["breakdown"]},"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["name","value"],"additionalProperties":true}}},"required":["kind","data"],"additionalProperties":true}]},"emptyHints":{"type":"array","items":{"type":"string"}}},"required":["timeRange","kind","metric","queryContext","unit","result"],"additionalProperties":true},"annotations":{"title":"Query Data","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"run_sql","title":"Run SQL","description":"Run one read-only ClickHouse SELECT against the org's warehouse and return its rows: at most 1000 are fetched and the first 100 rendered, with the count reported. Use it for what query_data cannot express, to spot-check data, or to test SQL for a raw_sql widget. For trends and top-N prefer query_data. The SQL must AND in `$__orgFilter`; macros are listed on `sql`. Spans are in `traces` (no otel_spans/spans table), log lines in `logs`; also metrics_sum/metrics_gauge/metrics_histogram, error_events, session_replays, product_events and *_hourly/*_minutely rollups. Select 64-bit IDs and hashes as toString(col). Attribute columns are Map(String, String): SpanAttributes['http.route'], never JSON* functions. In traces the name is SpanName, StatusCode is 'Ok'/'Error' and Duration is nanoseconds. Compare timestamps to 'YYYY-MM-DD HH:MM:SS', not ISO T/Z strings; bucket with toStartOfInterval. Call describe_warehouse_tables for a table's columns, units and enum casing before guessing names.","inputSchema":{"type":"object","properties":{"sql":{"type":"string","description":"The SELECT. It MUST contain `$__orgFilter` (expands to `(OrgId = '')`) as a top-level AND condition; a query without it, or with it OR-ed against another condition, is rejected. In joins use `$__orgFilter(alias)` (`(alias.OrgId = '')`) once per joined table. Other macros: `$__timeFilter(Column)` (`Column >= AND Column <= `), `$__startTime`, `$__endTime`, `$__interval_s` (bucket width for toStartOfInterval). Only one statement; DDL/DML, SETTINGS and system tables are rejected."},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 6 hours before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."},"granularity_seconds":{"type":"number","description":"Value of `$__interval_s`. Auto-computed from the window when omitted."},"max_cell_chars":{"type":"number","description":"Clip each rendered cell to this many characters (default 120, max 2000). Raise it to read long values such as log bodies or stack traces."},"show_sql":{"type":"boolean","description":"Also return the macro-expanded SQL that ran (default false)."},"reason":{"type":"string","description":"Why you are running this query, in a sentence. Optional; does not change the query."}},"required":["sql"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rowCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"columns":{"type":"array","items":{"type":"string"}},"rows":{"type":"array","items":{"type":"object"}},"truncated":{"type":"boolean"},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"maxCellChars":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowApplied":{"type":"boolean"},"expandedSql":{"type":"string"},"atRowCap":{"type":"boolean"},"rewrites":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"additionalProperties":true}}},"required":["rowCount","columns","rows","truncated","timeRange"],"additionalProperties":true},"annotations":{"title":"Run SQL","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"service_map","title":"Service Map","description":"Show service-to-service dependencies with call counts, error rates, and latency per edge. Use to understand system architecture and identify problematic inter-service calls.","inputSchema":{"type":"object","properties":{"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 6 hours before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."},"service":{"type":"string","description":"Only edges involving this service, as source or target (exact `service.name`)"},"environment":{"type":"string","description":"Only this deployment environment (e.g. production, staging)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"edges":{"type":"array","items":{"type":"object","properties":{"sourceService":{"type":"string"},"targetService":{"type":"string"},"callCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"errorCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"avgDurationMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"maxDurationMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["sourceService","targetService","callCount","errorCount","avgDurationMs","maxDurationMs"],"additionalProperties":true}},"serviceCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"service":{"type":"string"},"environment":{"type":"string"}},"required":["timeRange","edges","serviceCount"],"additionalProperties":true},"annotations":{"title":"Service Map","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"list_alert_rules","title":"List Alert Rules","description":"List configured alert rules with severity, signal type and condition. Use list_alert_incidents for what has fired.","inputSchema":{"type":"object","properties":{"services":{"type":"array","items":{"type":"string"},"description":"Only rules covering any of these services: scoped to them, scoped to every service, or raw_query rules whose SQL or name mentions them"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query"],"description":"Only rules on this signal type"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only rules with this severity"},"enabled_only":{"type":"boolean","description":"Only enabled rules"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"services":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"severity":{"type":"string"},"enabledOnly":{"type":"boolean"}},"required":["rules","total"],"additionalProperties":true},"annotations":{"title":"List Alert Rules","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"list_alert_rules","title":"List Alert Rules","description":"List configured alert rules with severity, signal type and condition. Use list_alert_incidents for what has fired.","inputSchema":{"type":"object","properties":{"services":{"type":"array","items":{"type":"string"},"description":"Only rules covering any of these services: scoped to them, scoped to every service, or raw_query rules whose SQL or name mentions them"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"Only rules on this signal type"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only rules with this severity"},"enabled_only":{"type":"boolean","description":"Only enabled rules"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"services":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"severity":{"type":"string"},"enabledOnly":{"type":"boolean"}},"required":["rules","total"],"additionalProperties":true},"annotations":{"title":"List Alert Rules","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"list_alert_destinations","title":"List Alert Destinations","description":"List the notification destinations a rule can deliver to, with id, type and delivery health. Pass the ids as destination_ids to create_alert_rule / update_alert_rule.","inputSchema":{"type":"object","properties":{"type":{"type":"string","enum":["pagerduty","webhook","discord","telegram","google-chat","microsoft-teams","incident-io","email","chat","linear"],"description":"Only destinations of this type"},"enabled_only":{"type":"boolean","description":"Only enabled destinations"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"publicId":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"enabled":{"type":"boolean"},"summary":{"type":"string"},"channelLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastTestedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastTestError":{"anyOf":[{"type":"string"},{"type":"null"}]},"disabledReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","publicId","name","type","enabled","summary","channelLabel","lastTestedAt","lastTestError","disabledReason","createdAt","updatedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"type":{"type":"string"},"enabledOnly":{"type":"boolean"}},"required":["destinations","total"],"additionalProperties":true},"annotations":{"title":"List Alert Destinations","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"get_alert_rule","title":"Get Alert Rule","description":"Get full configuration details of a specific alert rule including thresholds, service filters, evaluation settings, and notification destinations. Use list_alert_rules to find rule IDs.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"}},"required":["rule_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"excludeServiceNames":{"type":"array","items":{"type":"string"}},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noDataBehavior":{"type":"string"},"consecutiveBreachesRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"consecutiveHealthyRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"renotifyIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"apdexThresholdMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"queryBuilderDraft":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"anyOf":[{"type":"string","enum":["default","meter"]},{"type":"null"}]},"metricName":{"anyOf":[{"type":"string"},{"type":"null"}]},"metricType":{"anyOf":[{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},{"type":"null"}]},"isMonotonic":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"spaceAggregation":{"anyOf":[{"type":"string","enum":["sum","avg","min","max","count"]},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"null"}]},"rawQuerySql":{"anyOf":[{"type":"string"},{"type":"null"}]},"rawQueryReducer":{"anyOf":[{"type":"string"},{"type":"null"}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"notificationTitle":{"anyOf":[{"type":"string"},{"type":"null"}]},"notificationBody":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationError":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationErrorKind":{"anyOf":[{"type":"string"},{"type":"null"}]},"slowedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluatedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"type":"string"},{"type":"null"}]},"enabled":{"type":"boolean"}},"required":["id","name","type","enabled"],"additionalProperties":true}}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt","excludeServiceNames","groupBy","minimumSampleCount","noDataBehavior","consecutiveBreachesRequired","consecutiveHealthyRequired","renotifyIntervalMinutes","apdexThresholdMs","queryBuilderDraft","rawQuerySql","rawQueryReducer"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Get Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"list_alert_incidents","title":"List Alert Incidents","description":"Alert incidents: alert-rule breaches, open and resolved, with severity, group, condition and last observed value (flare-ups of an error issue are list_error_incidents). For one rule's trigger, notify and resolve timestamps use get_incident_timeline.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"acknowledged":{"type":"boolean","description":"true: open incidents someone acknowledged; false: open incidents nobody has. Implies status=open"},"limit":{"type":"number","description":"Max incidents to return (default 50, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"holdReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"heldSince":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"firstTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","holdReason","heldSince","threshold","comparator","firstTriggeredAt","resolvedAt","lastObservedValue"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"recentlyResolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"List Alert Incidents","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"get_alert_rule","title":"Get Alert Rule","description":"Get full configuration details of a specific alert rule including thresholds, service filters, evaluation settings, and notification destinations. Use list_alert_rules to find rule IDs.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"}},"required":["rule_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"excludeServiceNames":{"type":"array","items":{"type":"string"}},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noDataBehavior":{"type":"string"},"consecutiveBreachesRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"consecutiveHealthyRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"renotifyIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"apdexThresholdMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"queryBuilderDraft":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"anyOf":[{"type":"string","enum":["default","meter"]},{"type":"null"}]},"metricName":{"anyOf":[{"type":"string"},{"type":"null"}]},"metricType":{"anyOf":[{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},{"type":"null"}]},"isMonotonic":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"spaceAggregation":{"anyOf":[{"type":"string","enum":["sum","avg","min","max","count"]},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"null"}]},"rawQuerySql":{"anyOf":[{"type":"string"},{"type":"null"}]},"rawQueryReducer":{"anyOf":[{"type":"string"},{"type":"null"}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"notificationTitle":{"anyOf":[{"type":"string"},{"type":"null"}]},"notificationBody":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationError":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationErrorKind":{"anyOf":[{"type":"string"},{"type":"null"}]},"slowedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluatedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"type":"string"},{"type":"null"}]},"enabled":{"type":"boolean"}},"required":["id","name","type","enabled"],"additionalProperties":true}}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt","excludeServiceNames","groupBy","minimumSampleCount","noDataBehavior","consecutiveBreachesRequired","consecutiveHealthyRequired","renotifyIntervalMinutes","apdexThresholdMs","queryBuilderDraft","rawQuerySql","rawQueryReducer"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Get Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"list_alert_incidents","title":"List Alert Incidents","description":"Alert incidents: alert-rule breaches, open and resolved, with severity, group, condition and last observed value (flare-ups of an error issue are list_error_incidents). For one rule's trigger, notify and resolve timestamps use get_incident_timeline.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"acknowledged":{"type":"boolean","description":"true: open incidents someone acknowledged; false: open incidents nobody has. Implies status=open"},"limit":{"type":"number","description":"Max incidents to return (default 50, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"holdReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"heldSince":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"firstTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"anomalyIncidentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","holdReason","heldSince","threshold","comparator","firstTriggeredAt","resolvedAt","lastObservedValue"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"recentlyResolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"List Alert Incidents","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"list_alert_checks","title":"List Alert Checks","description":"List a rule's recent checks (one row per evaluation) with observed value, threshold, sample count and incident transition. Use it to tune thresholds, diagnose flapping, or see the near-misses before a breach.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"},"group_key":{"type":"string","description":"Only checks for this exact group key"},"status":{"type":"string","enum":["breached","healthy","skipped","error"],"description":"Only checks with this status"},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 7 days before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."},"limit":{"type":"number","description":"Max checks to return (default 100, max 2000)"}},"required":["rule_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ruleId":{"type":"string"},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"breached":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"healthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"skipped":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noData":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"belowMinSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"errored":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"transitions":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"checks":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string"},"groupKey":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"observedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowStart":{"type":"string"},"windowEnd":{"type":"string"},"consecutiveBreaches":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"consecutiveHealthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"incidentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"incidentTransition":{"type":"string"},"evaluationDurationMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorCategory":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["timestamp","groupKey","status","skipReason","observedValue","threshold","comparator","sampleCount","windowStart","windowEnd","consecutiveBreaches","consecutiveHealthy","incidentId","incidentTransition","evaluationDurationMs","errorMessage","errorCategory"],"additionalProperties":true}},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"groupKey":{"type":"string"},"status":{"type":"string"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["ruleId","total","breached","healthy","skipped","noData","belowMinSamples","errored","transitions","checks"],"additionalProperties":true},"annotations":{"title":"List Alert Checks","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"preview_alert_rule","title":"Preview Alert Rule","description":"Replay an alert rule over past data without saving it: per evaluation window, the value, sample count and verdict the scheduler would produce (breached, healthy, or skipped with the reason), plus when it would have fired. Pass rule_id to preview a saved rule, optionally with overrides, or a create_alert_rule definition to dry-run a new one. Run it before saving a raw_query rule.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Existing rule to preview (list_alert_rules). Definition params you also pass override its saved config, so a change can be tried before update_alert_rule. `template` is ignored with rule_id.: its UUID or its REST id (alrt_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql. All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 1 day before end_time. Covers at most 7 days; a wider window is narrowed to the 7 days before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ruleId":{"type":"string"},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"truncatedToStart":{"anyOf":[{"type":"string"},{"type":"null"}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"groups":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"windows":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"breached":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"healthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noData":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"belowMinSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"minValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"maxValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"totalSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","windows","breached","healthy","noData","belowMinSamples","noValue","minValue","maxValue","totalSamples"],"additionalProperties":true}},"points":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"bucket":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"provisional":{"type":"boolean"}},"required":["groupKey","bucket","status","skipReason","value","sampleCount","provisional"],"additionalProperties":true}},"wouldFire":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"}},"required":["groupKey","start","end"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"seriesFailure":{"type":"string","enum":["query_too_slow","query_too_much_memory","not_time_grouped"]},"queryTiming":{"type":"object","properties":{"elapsedMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"failure":{"anyOf":[{"type":"string"},{"type":"null"}]},"probeLimitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"intervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"limitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"fitsIntervalMinutes":{"type":"array","items":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}}},"required":["elapsedMs","failure","probeLimitMs","intervalMinutes","limitMs","fitsIntervalMinutes"],"additionalProperties":true},"performanceHints":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"line":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["code","message","line"],"additionalProperties":true}}},"required":["timeRange","truncatedToStart","windowMinutes","comparator","threshold","thresholdUpper","minimumSampleCount","groups","points","wouldFire","warnings"],"additionalProperties":true},"annotations":{"title":"Preview Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"get_incident_timeline","title":"Incident Timeline","description":"Alert-rule incidents (not error-issue incidents) for one rule or all, with first and last trigger, last notification and resolution timestamps: the per-incident detail behind list_alert_incidents. Pass rule_id to follow one rule over time.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Only incidents of this rule (ids from list_alert_rules): its UUID or its REST id (alrt_\u2026)"},"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"limit":{"type":"number","description":"Max incidents to return (default 20, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"firstTriggeredAt":{"type":"string"},"lastTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastNotifiedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","comparator","threshold","lastObservedValue","firstTriggeredAt","lastTriggeredAt","resolvedAt","lastNotifiedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"ruleId":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"Incident Timeline","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"create_alert_rule","title":"Create Alert Rule","description":"Create an alert rule. Pick a `template` for the common cases; otherwise pass signal_type, comparator and threshold yourself. destination_ids is required (at least one, from list_alert_destinations).","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Rule name"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Required, at least one: destination IDs to notify, from list_alert_destinations. Each is its UUID or its REST id (dest_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity (default: warning)"},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"enabled":{"type":"boolean","description":"Whether the rule evaluates (default true)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql. All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"notification_title":{"type":"string","description":"Custom notification title template. Supports {{ variable }} substitution, e.g. \"{{ event.emoji }} {{ rule.name }}: {{ event.label }}\". Omit for the built-in format. Variables: rule.name, severity, signal.label, comparator.label, threshold, value, observed.summary, group, window, links.app, links.chat."},"notification_body":{"type":"string","description":"Custom notification body template (Markdown). Supports {{ variable }} substitution and {{#if key}}\u2026{{/if}} blocks, e.g. \"*Severity:* {{ severity }}\\n*Observed:* {{ observed.summary }}\". Omit for the built-in format."}},"required":["name","destination_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Create Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}}, - {"name":"update_alert_rule","title":"Update Alert Rule","description":"Update an alert rule. Pass only the fields to change; the rest keep their current value (get_alert_rule shows it). rule_ids applies one change to many rules (e.g. add_destination_ids on every rule with rule_ids=[\"*\"]). Ids from list_alert_rules and list_alert_destinations.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID to update (use list_alert_rules to find IDs): its UUID or its REST id (alrt_\u2026)"},"rule_ids":{"type":"array","items":{"type":"string"},"description":"Apply the same change to several rules in one call instead of rule_id; [\"*\"] means every rule. name cannot be set in bulk. Each is its UUID or its REST id (alrt_\u2026)"},"name":{"type":"string","description":"New rule name"},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity"},"threshold":{"type":"number","description":"Threshold value. E.g. 0.05 for 5% error rate, 1000 for 1s latency"},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit to keep the current one; pass \"auto\" to derive it from the window (a fifth, 1 to 5). Any edit also clears a slowdown the scheduler applied."},"services":{"type":"array","items":{"type":"string"},"description":"Service scope (replaces the current one; an empty list means all services)"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (replaces the current scope; pass an empty list for all environments). Ignored for builder_query / raw_query."},"enabled":{"type":"boolean","description":"Whether the rule is enabled"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to notify, from list_alert_destinations; replaces the current ones. A rule needs at least one. Each is its UUID or its REST id (dest_\u2026)"},"add_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to add, keeping the current ones. Each is its UUID or its REST id (dest_\u2026)"},"remove_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to remove, keeping the rest. Each is its UUID or its REST id (dest_\u2026)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold"},"group_by":{"type":"array","items":{"type":"string"},"description":"Dimensions to evaluate the alert per-group (replaces the current grouping; an empty list removes it). Built-in tokens: service.name, span.name, status.code, http.method, severity. Attribute keys: attr.."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window. For raw_query it sums the `samples` column; without one each returned row counts as 1."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach instead of skipping it. Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes"},"apdex_threshold_ms":{"type":"number","description":"Apdex threshold in milliseconds (for signal_type=apdex)"},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"Query-builder draft, same shape as create_alert_rule (for signal_type=builder_query)"},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column (for signal_type=raw_query), at most 100 groups. Must reference $__orgFilter and $__timeFilter(col)."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How to collapse raw_query result rows into one value."},"notification_title":{"type":"string","description":"Notification title template with {{ variable }} substitution; variables as in create_alert_rule"},"notification_body":{"type":"string","description":"Notification body template (Markdown) with {{ variable }} and {{#if key}}...{{/if}}"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Update Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"preview_alert_rule","title":"Preview Alert Rule","description":"Replay an alert rule over past data without saving it: per evaluation window, the value, sample count and verdict the scheduler would produce (breached, healthy, or skipped with the reason), plus when it would have fired. Pass rule_id to preview a saved rule, optionally with overrides, or a create_alert_rule definition to dry-run a new one. Run it before saving a raw_query rule.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Existing rule to preview (list_alert_rules). Definition params you also pass override its saved config, so a change can be tried before update_alert_rule. `template` is ignored with rule_id.: its UUID or its REST id (alrt_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents (see anomaly_signals). All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 1 day before end_time. Covers at most 7 days; a wider window is narrowed to the 7 days before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ruleId":{"type":"string"},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"truncatedToStart":{"anyOf":[{"type":"string"},{"type":"null"}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"groups":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"windows":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"breached":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"healthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noData":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"belowMinSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"minValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"maxValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"totalSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","windows","breached","healthy","noData","belowMinSamples","noValue","minValue","maxValue","totalSamples"],"additionalProperties":true}},"points":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"bucket":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"provisional":{"type":"boolean"}},"required":["groupKey","bucket","status","skipReason","value","sampleCount","provisional"],"additionalProperties":true}},"wouldFire":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"}},"required":["groupKey","start","end"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"seriesFailure":{"type":"string","enum":["query_too_slow","query_too_much_memory","not_time_grouped"]},"queryTiming":{"type":"object","properties":{"elapsedMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"failure":{"anyOf":[{"type":"string"},{"type":"null"}]},"probeLimitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"intervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"limitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"fitsIntervalMinutes":{"type":"array","items":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}}},"required":["elapsedMs","failure","probeLimitMs","intervalMinutes","limitMs","fitsIntervalMinutes"],"additionalProperties":true},"performanceHints":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"line":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["code","message","line"],"additionalProperties":true}}},"required":["timeRange","truncatedToStart","windowMinutes","comparator","threshold","thresholdUpper","minimumSampleCount","groups","points","wouldFire","warnings"],"additionalProperties":true},"annotations":{"title":"Preview Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"get_incident_timeline","title":"Incident Timeline","description":"Alert-rule incidents (not error-issue incidents) for one rule or all, with first and last trigger, last notification and resolution timestamps: the per-incident detail behind list_alert_incidents. Pass rule_id to follow one rule over time.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Only incidents of this rule (ids from list_alert_rules): its UUID or its REST id (alrt_\u2026)"},"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"limit":{"type":"number","description":"Max incidents to return (default 20, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"firstTriggeredAt":{"type":"string"},"lastTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastNotifiedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","comparator","threshold","lastObservedValue","firstTriggeredAt","lastTriggeredAt","resolvedAt","lastNotifiedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"ruleId":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"Incident Timeline","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"create_alert_rule","title":"Create Alert Rule","description":"Create an alert rule. Pick a `template` for the common cases; otherwise pass signal_type, comparator and threshold yourself. destination_ids is required (at least one, from list_alert_destinations).","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Rule name"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Required, at least one: destination IDs to notify, from list_alert_destinations. Each is its UUID or its REST id (dest_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity (default: warning)"},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"enabled":{"type":"boolean","description":"Whether the rule evaluates (default true)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents (see anomaly_signals). All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"notification_title":{"type":"string","description":"Custom notification title template. Supports {{ variable }} substitution, e.g. \"{{ event.emoji }} {{ rule.name }}: {{ event.label }}\". Omit for the built-in format. Variables: rule.name, severity, signal.label, comparator.label, threshold, value, observed.summary, group, window, links.app, links.chat."},"notification_body":{"type":"string","description":"Custom notification body template (Markdown). Supports {{ variable }} substitution and {{#if key}}\u2026{{/if}} blocks, e.g. \"*Severity:* {{ severity }}\\n*Observed:* {{ observed.summary }}\". Omit for the built-in format."}},"required":["name","destination_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Create Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}}, + {"name":"update_alert_rule","title":"Update Alert Rule","description":"Update an alert rule. Pass only the fields to change; the rest keep their current value (get_alert_rule shows it). rule_ids applies one change to many rules (e.g. add_destination_ids on every rule with rule_ids=[\"*\"]). Ids from list_alert_rules and list_alert_destinations.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID to update (use list_alert_rules to find IDs): its UUID or its REST id (alrt_\u2026)"},"rule_ids":{"type":"array","items":{"type":"string"},"description":"Apply the same change to several rules in one call instead of rule_id; [\"*\"] means every rule. name cannot be set in bulk. Each is its UUID or its REST id (alrt_\u2026)"},"name":{"type":"string","description":"New rule name"},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity"},"threshold":{"type":"number","description":"Threshold value. E.g. 0.05 for 5% error rate, 1000 for 1s latency"},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit to keep the current one; pass \"auto\" to derive it from the window (a fifth, 1 to 5). Any edit also clears a slowdown the scheduler applied."},"services":{"type":"array","items":{"type":"string"},"description":"Service scope (replaces the current one; an empty list means all services)"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (replaces the current scope; pass an empty list for all environments). Ignored for builder_query / raw_query."},"enabled":{"type":"boolean","description":"Whether the rule is enabled"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to notify, from list_alert_destinations; replaces the current ones. A rule needs at least one. Each is its UUID or its REST id (dest_\u2026)"},"add_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to add, keeping the current ones. Each is its UUID or its REST id (dest_\u2026)"},"remove_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to remove, keeping the rest. Each is its UUID or its REST id (dest_\u2026)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does. Replaces the current list; an empty list means every signal."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold"},"group_by":{"type":"array","items":{"type":"string"},"description":"Dimensions to evaluate the alert per-group (replaces the current grouping; an empty list removes it). Built-in tokens: service.name, span.name, status.code, http.method, severity. Attribute keys: attr.."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window. For raw_query it sums the `samples` column; without one each returned row counts as 1."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach instead of skipping it. Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes"},"apdex_threshold_ms":{"type":"number","description":"Apdex threshold in milliseconds (for signal_type=apdex)"},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"Query-builder draft, same shape as create_alert_rule (for signal_type=builder_query)"},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column (for signal_type=raw_query), at most 100 groups. Must reference $__orgFilter and $__timeFilter(col)."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How to collapse raw_query result rows into one value."},"notification_title":{"type":"string","description":"Notification title template with {{ variable }} substitution; variables as in create_alert_rule"},"notification_body":{"type":"string","description":"Notification body template (Markdown) with {{ variable }} and {{#if key}}...{{/if}}"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Update Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"delete_alert_rule","title":"Delete Alert Rule","description":"Permanently delete an alert rule together with its incident history, delivery events and evaluation state. Irreversible; requires confirm=true. Ids from list_alert_rules.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"},"confirm":{"type":"boolean","description":"Must be true. There is no undo."}},"required":["rule_id","confirm"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":true},"annotations":{"title":"Delete Alert Rule","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}}, {"name":"acknowledge_alert_incident","title":"Acknowledge Alert Incident","description":"Mark an open alert incident as acknowledged (someone is on it) or, with acknowledged=false, clear the acknowledgement. While acknowledged, reminders pause; an escalation to a higher severity still notifies and clears it. Only open incidents; ids from list_alert_incidents.","inputSchema":{"type":"object","properties":{"incident_id":{"type":"string","description":"Alert incident ID: its UUID or its REST id (inc_\u2026)"},"acknowledged":{"type":"boolean","description":"false to unacknowledge. Default true."}},"required":["incident_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"status":{"type":"string"},"acknowledged":{"type":"boolean"},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"changed":{"type":"boolean"}},"required":["id","ruleId","ruleName","status","acknowledged","acknowledgedAt","acknowledgedBy","changed"],"additionalProperties":true},"annotations":{"title":"Acknowledge Alert Incident","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"describe_dashboard_schema","title":"Describe Dashboard Schema","description":"What a dashboard widget can be: panel types, the four data-source kinds, the unit vocabulary, aggregations and group-by tokens, the display config, and (section templates) the create_dashboard templates. Generated from the live schema; read it before authoring or editing widgets rather than working from a remembered example.","inputSchema":{"type":"object","properties":{"section":{"type":"string","enum":["panel_types","data_sources","units","queries","display","raw_sql","templates"],"description":"Omit for the index plus the panel-type table."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"section":{"type":"string"},"markdown":{"type":"string"}},"required":["markdown"],"additionalProperties":true},"annotations":{"title":"Describe Dashboard Schema","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, diff --git a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts index 052420297a..e614a78651 100644 --- a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts +++ b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts @@ -552,6 +552,42 @@ describe("alert tools", () => { expect(seen.created?.groupBy).toBeUndefined() }) + it("create_alert_rule takes an anomaly rule without comparator or threshold", async () => { + const seen: Seen = {} + await ok( + "create_alert_rule", + { + name: "Checkout anomalies", + signal_type: "anomaly", + severity: "critical", + destination_ids: [DEST_ID], + services: ["checkout"], + anomaly_signals: ["error_rate", "latency_p95"], + }, + seen, + ) + expect(seen.created).toMatchObject({ + signalType: "anomaly", + severity: "critical", + serviceNames: ["checkout"], + anomalySignals: ["error_rate", "latency_p95"], + }) + }) + + it("update_alert_rule replaces an anomaly rule's signals only when asked", () => { + const anomalyRule = Schema.decodeSync(AlertRuleDocument)({ + ...rule, + signalType: "anomaly", + comparator: "gte", + anomalySignals: ["error_rate"], + }) + const signalsAfter = (params: Parameters[1]) => + Effect.runSync(buildUpdatedRequest(anomalyRule, params)).anomalySignals + expect(signalsAfter({ name: "Renamed" })).toEqual(["error_rate"]) + expect(signalsAfter({ anomaly_signals: ["throughput"] })).toEqual(["throughput"]) + expect(signalsAfter({ anomaly_signals: [] })).toEqual([]) + }) + it("create_alert_rule decodes query_builder_draft JSON and names missing inputs", async () => { const seen: Seen = {} await ok( diff --git a/apps/ai/src/mcp/tools/create-alert-rule.ts b/apps/ai/src/mcp/tools/create-alert-rule.ts index e46bf748e7..b3b18b5291 100644 --- a/apps/ai/src/mcp/tools/create-alert-rule.ts +++ b/apps/ai/src/mcp/tools/create-alert-rule.ts @@ -18,6 +18,8 @@ import { ALERT_REDUCERS, ALERT_SEVERITIES, ALERT_SIGNAL_TYPES, + ANOMALY_SIGNAL_TYPES, + ANOMALY_SIGNALS_DESCRIPTION, renderRuleWrite, ruleConfigWarnings, ruleWriteInputErrors, @@ -97,7 +99,7 @@ export const CreateAlertRuleParameters = Schema.Struct({ // Custom-mode params (used when template is 'custom' or omitted) signal_type: P.optionalOneOf( ALERT_SIGNAL_TYPES, - "What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql. " + + "What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents (see anomaly_signals). " + // Load-bearing caveat, not filler: without it an agent will happily create a // rule that can never fire. Compressed, not dropped. "All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point " + @@ -107,6 +109,7 @@ export const CreateAlertRuleParameters = Schema.Struct({ ALERT_COMPARATORS, "How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set.", ), + anomaly_signals: P.optionalOneOfList(ANOMALY_SIGNAL_TYPES, ANOMALY_SIGNALS_DESCRIPTION), group_by: P.optionalList( "Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route).", ), @@ -162,8 +165,10 @@ export const buildAlertRuleRequest = Effect.fnUntraced(function* ( ? undefined : ALERT_TEMPLATES[params.template] const signalType = template?.signalType ?? params.signal_type - const comparator = template?.comparator ?? params.comparator - const threshold = params.threshold ?? template?.defaultThreshold + // An anomaly rule's comparator and tier values are pinned by the server; these are placeholders. + const anomaly = signalType === "anomaly" + const comparator = anomaly ? "gte" : (template?.comparator ?? params.comparator) + const threshold = anomaly ? 0 : (params.threshold ?? template?.defaultThreshold) if (signalType === undefined) { return yield* invalid( @@ -246,6 +251,7 @@ export const buildAlertRuleRequest = Effect.fnUntraced(function* ( ...(params.environments === undefined || params.environments.length === 0 ? undefined : { environments: params.environments }), + ...(params.anomaly_signals === undefined ? undefined : { anomalySignals: params.anomaly_signals }), ...(effectiveGroupBy === undefined ? undefined : { groupBy: effectiveGroupBy }), ...(params.minimum_sample_count === undefined ? undefined diff --git a/apps/ai/src/mcp/tools/delete-alert-rule.ts b/apps/ai/src/mcp/tools/delete-alert-rule.ts index 4ad7da2e36..b4e41c6307 100644 --- a/apps/ai/src/mcp/tools/delete-alert-rule.ts +++ b/apps/ai/src/mcp/tools/delete-alert-rule.ts @@ -36,6 +36,8 @@ export function registerDeleteAlertRuleTool(server: McpToolRegistrar) { const result = yield* alerts.deleteRule(tenant.orgId, tenant.roles, ruleId).pipe( Effect.catchTags({ "@maple/http/errors/AlertRuleNotFoundError": ruleNotFoundFromError, + "@maple/http/errors/AlertValidationError": (error) => + Effect.fail(new McpInvalidInputError({ message: error.message })), "@maple/http/errors/AlertForbiddenError": (error) => Effect.fail( new McpQueryError({ diff --git a/apps/ai/src/mcp/tools/get-alert-rule.ts b/apps/ai/src/mcp/tools/get-alert-rule.ts index f1d4b29b9d..49ec5c0bf4 100644 --- a/apps/ai/src/mcp/tools/get-alert-rule.ts +++ b/apps/ai/src/mcp/tools/get-alert-rule.ts @@ -6,6 +6,7 @@ import { CurrentMcpTenant } from "../lib/query-warehouse" import { AlertRulesService } from "@maple/backend/services/alerts/AlertRulesService" import { AlertsService } from "@maple/backend/services/alerts/AlertsService" import { + formatAnomalySignals, formatCheckInterval, formatCondition, ruleConfigWarnings, @@ -131,6 +132,7 @@ export function registerGetAlertRuleTool(server: McpToolRegistrar) { "Environments", rule.environments.length > 0 ? Arr.join(rule.environments, ", ") : "All environments", ], + ["Anomaly signals", formatAnomalySignals(rule)], [ "Group By", rule.groupBy && rule.groupBy.length > 0 ? Arr.join(rule.groupBy, ", ") : undefined, diff --git a/apps/ai/src/mcp/tools/get-incident-timeline.ts b/apps/ai/src/mcp/tools/get-incident-timeline.ts index d7a4e841b0..10898d6cd8 100644 --- a/apps/ai/src/mcp/tools/get-incident-timeline.ts +++ b/apps/ai/src/mcp/tools/get-incident-timeline.ts @@ -79,6 +79,7 @@ export function registerGetIncidentTimelineTool(server: McpToolRegistrar) { ruleId: i.ruleId, ruleName: i.ruleName, groupKey: i.groupKey, + ...(i.groupLabel === undefined ? undefined : { groupLabel: i.groupLabel }), signalType: i.signalType, severity: i.severity, status: i.status, @@ -119,7 +120,7 @@ export function registerGetIncidentTimelineTool(server: McpToolRegistrar) { ["Status", inc.status], ["Severity", inc.severity], ["Signal type", inc.signalType], - ["Group", inc.groupKey ?? undefined], + ["Group", inc.groupLabel ?? inc.groupKey ?? undefined], ["Condition", `value ${formatCondition(inc)}`], [ "Last observed value", diff --git a/apps/ai/src/mcp/tools/list-alert-incidents.ts b/apps/ai/src/mcp/tools/list-alert-incidents.ts index c7afe8765d..a39a372b60 100644 --- a/apps/ai/src/mcp/tools/list-alert-incidents.ts +++ b/apps/ai/src/mcp/tools/list-alert-incidents.ts @@ -119,6 +119,10 @@ export function registerListAlertIncidentsTool(server: McpToolRegistrar) { firstTriggeredAt: i.firstTriggeredAt, resolvedAt: i.resolvedAt, lastObservedValue: i.lastObservedValue, + ...(i.anomalyIncidentId === undefined + ? undefined + : { anomalyIncidentId: i.anomalyIncidentId }), + ...(i.groupLabel === undefined ? undefined : { groupLabel: i.groupLabel }), })), total: incidents.length, openCount, @@ -216,7 +220,7 @@ export function registerListAlertIncidentsTool(server: McpToolRegistrar) { : i.acknowledgedAt != null ? `open (acknowledged by ${i.acknowledgedBy ?? "someone"})` : i.status, - i.groupKey ?? "all", + i.groupLabel ?? i.groupKey ?? "all", i.signalType, formatCondition(i), i.lastObservedValue != null ? String(i.lastObservedValue) : "-", diff --git a/apps/ai/src/mcp/tools/preview-alert-rule.ts b/apps/ai/src/mcp/tools/preview-alert-rule.ts index e7a7defd80..64fcac1953 100644 --- a/apps/ai/src/mcp/tools/preview-alert-rule.ts +++ b/apps/ai/src/mcp/tools/preview-alert-rule.ts @@ -177,6 +177,14 @@ export function registerPreviewAlertRuleTool(server: McpToolRegistrar) { ), ) + if (request.rule.signalType === "anomaly") { + return yield* new McpInvalidInputError({ + message: + "An anomaly rule has no query to replay: it pages on the anomaly detector's open incidents. Use list_alert_incidents to see what it opened.", + parameter: "signal_type", + }) + } + const alerts = yield* AlertsService const preview = yield* alerts.previewRule(tenant.orgId, tenant.roles, request).pipe( Effect.catchTags({ diff --git a/apps/ai/src/mcp/tools/update-alert-rule.ts b/apps/ai/src/mcp/tools/update-alert-rule.ts index 325300eebe..90ec670c45 100644 --- a/apps/ai/src/mcp/tools/update-alert-rule.ts +++ b/apps/ai/src/mcp/tools/update-alert-rule.ts @@ -22,6 +22,8 @@ import { ALERT_REDUCERS, ALERT_SEVERITIES, ALERT_SIGNAL_TYPES, + ANOMALY_SIGNAL_TYPES, + ANOMALY_SIGNALS_DESCRIPTION, renderBulkRuleWrite, renderRuleWrite, ruleConfigWarnings, @@ -68,7 +70,11 @@ export const UpdateAlertRuleParameters = Schema.Struct({ ), signal_type: P.optionalOneOf( ALERT_SIGNAL_TYPES, - "What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql.", + "What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents.", + ), + anomaly_signals: P.optionalOneOfList( + ANOMALY_SIGNAL_TYPES, + `${ANOMALY_SIGNALS_DESCRIPTION} Replaces the current list; an empty list means every signal.`, ), comparator: P.optionalOneOf(ALERT_COMPARATORS, "How the observed value is compared with threshold"), group_by: P.optionalList( @@ -139,6 +145,12 @@ export const buildUpdatedRequest = Effect.fnUntraced(function* ( serviceNames: [...(params.services ?? current.serviceNames)], excludeServiceNames: [...current.excludeServiceNames], environments: [...(params.environments ?? current.environments)], + // Only an anomaly rule has a signal scope; the server rejects one on any other rule. + ...(signalType === "anomaly" + ? { anomalySignals: [...(params.anomaly_signals ?? current.anomalySignals ?? [])] } + : params.anomaly_signals === undefined + ? undefined + : { anomalySignals: [...params.anomaly_signals] }), tags: [...current.tags], groupBy: params.group_by !== undefined diff --git a/apps/alerting/src/scheduled.ts b/apps/alerting/src/scheduled.ts index 0fa1e9da4d..7a71d1af2b 100644 --- a/apps/alerting/src/scheduled.ts +++ b/apps/alerting/src/scheduled.ts @@ -7,6 +7,7 @@ */ import { AlertsService, type AlertSchedulerLane } from "@maple/backend/services/alerts/AlertsService" import { AnomalyDetectionService } from "@maple/backend/services/alerts/AnomalyDetectionService" +import { AnomalyAlertSource } from "@maple/backend/services/alerts/anomaly/AnomalyAlertSource" import { CloudflareAnalyticsService } from "@maple/backend/services/integrations/CloudflareAnalyticsService" import { DigestService } from "@maple/backend/services/digest/DigestService" import { WebAnalyticsDigestService } from "@maple/backend/services/digest/WebAnalyticsDigestService" @@ -71,6 +72,7 @@ export const layerTick = ( Layer.mergeAll( AlertsService.layer, AnomalyDetectionService.layer, + AnomalyAlertSource.layer, CloudflareAnalyticsService.layer, GcpMetricsService.layer, PlanetScaleService.layer, @@ -260,7 +262,7 @@ const serviceMapRollupTick = makeTick( }), ) -const anomalyTick = makeTick( +const anomalyDetectionTick = makeTick( AnomalyDetectionService.use((anomalies) => anomalies.runTick), "anomaly", (result) => ({ @@ -275,6 +277,16 @@ const anomalyTick = makeTick( }), ) +// Gives each org with an open anomaly its default anomaly alert rule. Its own tick, so a +// failure only logs and never touches detection. +const anomalyDefaultRulesTick = makeTick( + AnomalyAlertSource.use((source) => source.backfillDefaultRules), + "anomaly_default_rules", + (created) => (created > 0 ? { defaultAnomalyRulesCreated: created } : undefined), +) + +const anomalyTick = Effect.andThen(anomalyDetectionTick, anomalyDefaultRulesTick) + const cloudflareAnalyticsTick = makeTick( CloudflareAnalyticsService.use((analytics) => analytics.pollAllOrgs), "cloudflare_analytics", @@ -540,6 +552,7 @@ export const selectLaneProgram = ( type ScheduledServices = | AlertsService | AnomalyDetectionService + | AnomalyAlertSource | AwsConnectionService | CloudflareAnalyticsService | DigestService diff --git a/apps/api/src/routes/v2/alchemy-provider.integration.test.ts b/apps/api/src/routes/v2/alchemy-provider.integration.test.ts index caac85a016..6fd917fdbf 100644 --- a/apps/api/src/routes/v2/alchemy-provider.integration.test.ts +++ b/apps/api/src/routes/v2/alchemy-provider.integration.test.ts @@ -44,6 +44,7 @@ import { DashboardPersistenceService } from "@maple/backend/services/dashboards/ import { SharedDashboardService } from "@maple/backend/services/dashboards/SharedDashboardService" import { AlertRuntime, AlertsService } from "@maple/backend/services/alerts/AlertsService" import { UptimeAlertSource } from "@maple/backend/services/uptime/UptimeAlertSource" +import { AnomalyAlertSource } from "@maple/backend/services/alerts/anomaly/AnomalyAlertSource" import { SloAlertSource } from "@maple/backend/services/slos/SloAlertSource" import { AlertDestinationsService } from "@maple/backend/services/alerts/AlertDestinationsService" import { AlertReadModelsService } from "@maple/backend/services/alerts/AlertReadModelsService" @@ -187,6 +188,7 @@ const makeHarness = () => { alertReadModelsLive, alertRulesLive, UptimeAlertSource.layer.pipe(Layer.provide(testDb.layer)), + AnomalyAlertSource.layer.pipe(Layer.provide(testDb.layer)), Layer.effect(SloAlertSource, SloAlertSource.make).pipe( Layer.provide(Layer.mergeAll(testDb.layer, warehouseLive)), ), diff --git a/apps/api/src/routes/v2/alert-incidents.http.ts b/apps/api/src/routes/v2/alert-incidents.http.ts index cf3b09f7be..5159ee97df 100644 --- a/apps/api/src/routes/v2/alert-incidents.http.ts +++ b/apps/api/src/routes/v2/alert-incidents.http.ts @@ -46,6 +46,8 @@ const toV2Incident = (doc: AlertIncidentDocument): V2AlertIncident => ({ : { user_id: doc.acknowledgedByUserId, name: doc.acknowledgedByName }, acknowledged_via: doc.acknowledgedVia, error_issue_id: doc.errorIssueId, + ...(doc.anomalyIncidentId === undefined ? undefined : { anomaly_incident_id: doc.anomalyIncidentId }), + ...(doc.groupLabel === undefined ? undefined : { group_label: doc.groupLabel }), }) const toV2Event = (doc: AlertIncidentEventDocument): V2AlertIncidentEvent => ({ diff --git a/apps/api/src/routes/v2/alert-rules.http.ts b/apps/api/src/routes/v2/alert-rules.http.ts index 5b24ec59a0..8fbdddffd0 100644 --- a/apps/api/src/routes/v2/alert-rules.http.ts +++ b/apps/api/src/routes/v2/alert-rules.http.ts @@ -79,6 +79,7 @@ const toV2Rule = (doc: AlertRuleDocument): V2AlertRule => ({ service_names: doc.serviceNames, exclude_service_names: doc.excludeServiceNames, environments: doc.environments, + ...(doc.anomalySignals === undefined ? undefined : { anomaly_signals: doc.anomalySignals }), tags: doc.tags, group_by: doc.groupBy, signal_type: doc.signalType, @@ -111,6 +112,12 @@ const toV2Rule = (doc: AlertRuleDocument): V2AlertRule => ({ updated_by: doc.updatedBy, }) +/** The wire rule with every audited key present, `anomaly_signals` included when absent. */ +const toAuditedRule = (doc: AlertRuleDocument) => { + const rule = toV2Rule(doc) + return { ...rule, anomaly_signals: rule.anomaly_signals } +} + /** Update-payload fields diffable through the wire shape (drafts get summarized). */ const ruleAuditDiff = auditDiff({ fields: [ @@ -123,6 +130,7 @@ const ruleAuditDiff = auditDiff { await harness.dispose() }) + it("round-trips an anomaly rule's anomaly_signals and keeps them through PATCH", async () => { + const harness = makeHarness() + const key = await harness.bootstrapKey(["alerts:write"]) + const { rule: errorRate } = await createWebhookAndRule(harness, key.secret) + expect(errorRate.body).not.toHaveProperty("anomaly_signals") + + const created = await harness.request("POST", "/v2/alerts/rules", key.secret, { + name: "Checkout anomalies", + signal_type: "anomaly", + severity: "critical", + comparator: "gte", + window_minutes: 5, + service_names: ["checkout"], + anomaly_signals: ["error_rate", "latency_p95"], + destination_ids: [errorRate.body.destination_ids[0]], + }) + expect(created.status, JSON.stringify(created.body)).toBe(200) + expect(created.body.anomaly_signals).toEqual(["error_rate", "latency_p95"]) + + const renamed = await harness.request("PATCH", `/v2/alerts/rules/${created.body.id}`, key.secret, { + name: "Renamed anomalies", + }) + expect(renamed.status, JSON.stringify(renamed.body)).toBe(200) + expect(renamed.body.anomaly_signals).toEqual(["error_rate", "latency_p95"]) + + const widened = await harness.request("PATCH", `/v2/alerts/rules/${created.body.id}`, key.secret, { + anomaly_signals: [], + }) + expect(widened.body.anomaly_signals).toEqual([]) + + const misplaced = await harness.request( + "PATCH", + `/v2/alerts/rules/${errorRate.body.id}`, + key.secret, + { + anomaly_signals: ["throughput"], + }, + ) + expect(misplaced.status).toBe(400) + + await harness.dispose() + }) + it("keeps a rule's severity tiers through PATCH and rejects single-threshold edits on them", async () => { const harness = makeHarness() const key = await harness.bootstrapKey(["alerts:write"]) diff --git a/apps/ios/Maple/Components/AlertFormatting.swift b/apps/ios/Maple/Components/AlertFormatting.swift index bdbeb3db82..a1c7cf9642 100644 --- a/apps/ios/Maple/Components/AlertFormatting.swift +++ b/apps/ios/Maple/Components/AlertFormatting.swift @@ -51,6 +51,8 @@ struct SignalDisplay: Codable { case .uptime: self.init(label: "Uptime check", unit: .plain) // An SLO's hidden rule: the value is how many times faster than sustainable the budget burns. case .sloBurnRate: self.init(label: "SLO burn rate", unit: .burnRate) + // An anomaly rule: the value is a severity code (1 warning, 2 critical), read as a plain number. + case .anomaly: self.init(label: "Anomaly", unit: .plain) } } diff --git a/apps/ios/Maple/Features/Home/HomeView.swift b/apps/ios/Maple/Features/Home/HomeView.swift index a36510afed..5845fe26ab 100644 --- a/apps/ios/Maple/Features/Home/HomeView.swift +++ b/apps/ios/Maple/Features/Home/HomeView.swift @@ -241,7 +241,7 @@ struct IncidentCardView: View { if names.isEmpty { "All services" } else if names.count <= 2 { names.joined(separator: ", ") } else { "\(names[0]), \(names[1]) +\(names.count - 2)" } - if let group = incident.groupKey, group != "__total__" { label += " · \(group)" } + if let group = incident.groupLabel ?? incident.groupKey, group != "__total__" { label += " · \(group)" } return label } } diff --git a/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json b/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json index 1e63788df9..8475e07805 100644 --- a/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json +++ b/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json @@ -351,6 +351,10 @@ "slack" ] }, + "anomaly_incident_id": { + "$ref": "#/components/schemas/_maple_AnomalyIncidentId", + "description": "`anomaly` rules only: the anomaly incident this alert incident pages on. Absent on other rules." + }, "comparator": { "$ref": "#/components/schemas/_maple_AlertComparator" }, @@ -369,6 +373,10 @@ "description": "The breaching group for grouped rules, or `null` for ungrouped rules.", "type": "string" }, + "group_label": { + "description": "What to show as the incident's group when `group_key` is not readable, e.g. an anomaly's `checkout (production)`. Absent when the group key is its own label.", + "type": "string" + }, "held_since": { "description": "When the current hold began, or `null` when the incident is not held.", "type": "string" @@ -657,6 +665,19 @@ ], "type": "boolean" }, + "anomaly_signals": { + "description": "`anomaly` rules only: the detector signals the rule pages on. Empty for every signal. Absent on other rules.", + "examples": [ + [ + "error_rate", + "latency_p95" + ] + ], + "items": { + "$ref": "#/components/schemas/_maple_AnomalySignalType" + }, + "type": "array" + }, "apdex_threshold_ms": { "description": "For `apdex` rules: the satisfied-latency threshold in milliseconds.", "exclusiveMinimum": 0, @@ -976,6 +997,18 @@ "description": "Count a window with no data (e.g. a raw query returning no rows) as a breach, so a rule that goes blind opens an incident. Default `false`: such windows are skipped. Not supported on grouped rules, where a group that stops reporting keeps its incident open until telemetry returns.", "type": "boolean" }, + "anomaly_signals": { + "description": "`anomaly` rules only: the detector signals to page on. Omit or pass `[]` for every signal.", + "examples": [ + [ + "error_rate" + ] + ], + "items": { + "$ref": "#/components/schemas/_maple_AnomalySignalType" + }, + "type": "array" + }, "apdex_threshold_ms": { "exclusiveMinimum": 0, "type": "number" @@ -7022,7 +7055,8 @@ "builder_query", "raw_query", "uptime", - "slo_burn_rate" + "slo_burn_rate", + "anomaly" ], "examples": [ "error_rate" @@ -7080,7 +7114,6 @@ "type": "string" }, "_maple_AnomalySignalType": { - "description": "Filter by signal type.", "enum": [ "error_rate", "latency_p95", diff --git a/apps/web/src/page/alerts/hub/view-inbox.ts b/apps/web/src/page/alerts/hub/view-inbox.ts index dabd9b7d7b..53a907d3a6 100644 --- a/apps/web/src/page/alerts/hub/view-inbox.ts +++ b/apps/web/src/page/alerts/hub/view-inbox.ts @@ -138,7 +138,7 @@ export const inboxTab = ( ), ...(isQuiet ? [ - headroomList(h, ruleHeadroom(rules, statesByRule), now), + headroomList(h, ruleHeadroom(rules, statesByRule, incidents), now), lanes.length === 0 && spots.length === 0 ? typography(h, { size: "xs", tone: "muted", mono: true, as: "p" }, [ "No rule fired in the last 24 hours · no blind spots: every enabled rule is evaluating and routed.", @@ -555,12 +555,12 @@ const headroomRow = (h: HtmlBuilder, row: RuleHeadroom, now: number): Html }, [rule.name], ), - ...(row.groupKey !== null + ...(row.groupLabel !== null ? [ typography( h, { size: "2xs", tone: "muted", mono: true, truncate: true, as: "p" }, - [row.groupKey], + [row.groupLabel], ), ] : []), diff --git a/apps/web/src/page/alerts/incident/view-rail.ts b/apps/web/src/page/alerts/incident/view-rail.ts index a305e3f24b..905c586de9 100644 --- a/apps/web/src/page/alerts/incident/view-rail.ts +++ b/apps/web/src/page/alerts/incident/view-rail.ts @@ -50,6 +50,11 @@ const groupsSection = ( }) const breaching = Arr.filter(rows, (row) => row.last_status === "breached").length const openByGroup = new Map(Arr.map(openIncidents, (open) => [open.groupKey, open])) + // An anomaly rule's group key is an incident id; people see the stored label instead. + const groupName = (groupKey: string): string => + openByGroup.get(groupKey)?.groupLabel ?? + (groupKey === incident.groupKey ? incident.groupLabel : null) ?? + (rule.signalType === "anomaly" ? "Anomaly" : groupKey) return railSection(h, [`Groups · ${breaching} of ${rows.length} breaching`], null, [ h.ul( [h.Class("space-y-2")], @@ -69,7 +74,7 @@ const groupsSection = ( h.Href(incidentHref(open.id)), h.Class("min-w-0 flex-1 truncate hover:underline active:underline"), ], - [row.group_key], + [groupName(row.group_key)], ) : h.span( [ @@ -83,7 +88,7 @@ const groupsSection = ( ), ), ], - [row.group_key], + [groupName(row.group_key)], ), h.span( [ diff --git a/apps/web/src/page/alerts/rule/view-history.ts b/apps/web/src/page/alerts/rule/view-history.ts index bb5769329b..25aee57f58 100644 --- a/apps/web/src/page/alerts/rule/view-history.ts +++ b/apps/web/src/page/alerts/rule/view-history.ts @@ -233,9 +233,9 @@ const incidentRow = ( h.span( [ h.Class("block max-w-[180px] truncate font-mono text-muted-foreground"), - h.Title(incident.groupKey ?? "all"), + h.Title(incident.groupLabel ?? incident.groupKey ?? "all"), ], - [incident.groupKey ?? "all"], + [incident.groupLabel ?? incident.groupKey ?? "all"], ), ]), tableCell(h, [ diff --git a/packages/backend/src/services/alerts/AlertChatCardService.ts b/packages/backend/src/services/alerts/AlertChatCardService.ts index 878e514646..ab27b9ddf5 100644 --- a/packages/backend/src/services/alerts/AlertChatCardService.ts +++ b/packages/backend/src/services/alerts/AlertChatCardService.ts @@ -150,6 +150,7 @@ export class AlertChatCardService extends Context.Service stored.rule?.windowMinutes ?? 5, onSome: (value) => value.windowMinutes, diff --git a/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts b/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts index aaac788254..c7e703ebbd 100644 --- a/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts +++ b/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts @@ -19,7 +19,7 @@ import { import { dispatchDelivery as dispatchDeliveryRaw } from "./delivery/dispatch" import { FetchHttpClient } from "effect/http" import type { EffectTransportDeps } from "./delivery/Transport" -import type { TemplateRenderContext } from "./alert-formatting" +import { formatAlertHeadline, formatObservedSummary, type TemplateRenderContext } from "./alert-formatting" import { resolveSignalDisplay } from "./alert-signal-display" import { renderTemplate } from "./alert-templating/renderer" import { DEFAULT_BODY_TEMPLATE, DEFAULT_TITLE_TEMPLATE } from "./alert-templating/defaultTemplates" @@ -225,6 +225,60 @@ describe("buildSummaryLine", () => { }) }) +describe("anomaly notifications", () => { + const anomalyContext: TemplateRenderContext = { + ...baseContext, + ruleName: "Anomaly detection", + signalType: "anomaly", + comparator: "gte", + threshold: 2, + value: 2, + sampleCount: 1, + groupKey: "error_rate:production:checkout", + detail: "error rate 4.2% vs baseline 0.3% in checkout (production)", + } + + it("leads with the detector's sentence, never the severity code it compares", () => { + const texts = [ + formatAlertHeadline(anomalyContext), + buildSummaryLine(anomalyContext, (value) => value), + formatObservedSummary(anomalyContext), + ] + assert.deepStrictEqual(texts, [ + "Critical anomaly: error rate 4.2% vs baseline 0.3% in checkout (production)", + "Error rate 4.2% vs baseline 0.3% in checkout (production).", + "error rate 4.2% vs baseline 0.3% in checkout (production)", + ]) + Arr.forEach(texts, (text) => assert.notInclude(text, ">= 2")) + }) + + it("shows the anomaly's service and environment as its group, never its incident id", () => { + const keyedById = { ...anomalyContext, groupKey: "00000000-0000-4000-8000-00000000a001" } + const labelled = buildTemplateContext( + { ...keyedById, groupLabel: "checkout (production)" }, + LINK, + CHAT, + ) + assert.strictEqual(labelled.group, "checkout (production)") + // A notification queued without a label shows no group rather than the id. + assert.strictEqual(buildTemplateContext(keyedById, LINK, CHAT).group, "all") + }) + + it("says the anomaly ended on resolve", () => { + const resolved = { + ...anomalyContext, + eventType: "resolve" as const, + value: 0, + detail: "The anomaly ended", + } + assert.strictEqual(formatAlertHeadline(resolved), "Anomaly detection: anomaly resolved") + assert.strictEqual( + buildSummaryLine(resolved, (value) => value), + "The anomaly ended.", + ) + }) +}) + describe("buildDiscordEmbedsFromTemplate", () => { it("maps title/body to the embed and color-codes by severity", () => { const [embed] = buildDiscordEmbedsFromTemplate("T", "B", baseContext, LINK) as Array<{ diff --git a/packages/backend/src/services/alerts/AlertDeliveryDispatch.ts b/packages/backend/src/services/alerts/AlertDeliveryDispatch.ts index 908d77b2ba..9519dd8788 100644 --- a/packages/backend/src/services/alerts/AlertDeliveryDispatch.ts +++ b/packages/backend/src/services/alerts/AlertDeliveryDispatch.ts @@ -14,7 +14,7 @@ import type { DispatchContext } from "./delivery/context" import { comparatorBreachPhrase, discordEmbedColor, - displayGroupKey, + displayGroup, eventTypeEmoji, formatComparator, formatAlertHeadline, @@ -28,6 +28,8 @@ import { signalDisplayOf, truncate, uptimeSummaryLine, + anomalySummaryLine, + hasSignalHeadline, type TemplateRenderContext, } from "./alert-formatting" import { DEFAULT_BODY_TEMPLATE, DEFAULT_TITLE_TEMPLATE } from "./alert-templating/defaultTemplates" @@ -82,6 +84,7 @@ type SummaryLineContext = Pick< */ export const buildSummaryLine = (context: SummaryLineContext, em: (value: string) => string): string => { if (context.signalType === "uptime") return em(uptimeSummaryLine(context)) + if (context.signalType === "anomaly") return em(anomalySummaryLine(context)) // An SLO's check names both windows and the tier, which one value and window cannot. if (context.signalType === "slo_burn_rate" && context.detail != null && context.eventType !== "test") return em(context.detail) @@ -122,7 +125,7 @@ export const buildDiscordEmbeds = (context: DispatchContext, linkUrl: string) => fields: [ { name: "Severity", value: context.severity, inline: true }, { name: "Signal", value: formatSignalLabel(context), inline: true }, - { name: "Group", value: displayGroupKey(context.groupKey) ?? "all", inline: true }, + { name: "Group", value: displayGroup(context) ?? "all", inline: true }, { name: "Observed", value: formatObservedSummary(context), inline: true }, { name: "Window", value: formatWindow(context.windowMinutes), inline: true }, { @@ -163,9 +166,9 @@ const telegramFooter = (context: Pick, + context: Pick, ): string => { - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) const parts = [ `Severity ${escapeTelegramHtml(formatSeverityLabel(context.severity))}`, `Window ${escapeTelegramHtml(formatWindow(context.windowMinutes))}`, @@ -179,10 +182,9 @@ const telegramBody = (title: string, lines: ReadonlyArray): string => /** No link arguments: Telegram carries both links in the inline keyboard. */ export const buildTelegramText = (context: DispatchContext): string => { - const title = - context.signalType === "uptime" - ? `${eventTypeEmoji(context.eventType)} ${escapeTelegramHtml(formatAlertHeadline(context))}` - : `${eventTypeEmoji(context.eventType)} ${escapeTelegramHtml(context.ruleName)} \u{2014} ${escapeTelegramHtml(formatEventTypeLabel(context.eventType))}` + const title = hasSignalHeadline(context.signalType) + ? `${eventTypeEmoji(context.eventType)} ${escapeTelegramHtml(formatAlertHeadline(context))}` + : `${eventTypeEmoji(context.eventType)} ${escapeTelegramHtml(context.ruleName)} \u{2014} ${escapeTelegramHtml(formatEventTypeLabel(context.eventType))}` return telegramBody(title, [ escapeTelegramHtml(buildSummaryLine(context, (value) => value)), "", @@ -259,7 +261,7 @@ export const buildTemplateContext = ( observed: formatSignalMetric(context.value, display), "observed.summary": formatObservedSummary(context), sampleCount: context.sampleCount != null ? String(context.sampleCount) : "", - group: displayGroupKey(context.groupKey) ?? "all", + group: displayGroup(context) ?? "all", window: formatWindow(context.windowMinutes), incidentId: context.incidentId ?? "", incidentStatus: context.incidentStatus, diff --git a/packages/backend/src/services/alerts/AlertDestinationDelivery.ts b/packages/backend/src/services/alerts/AlertDestinationDelivery.ts index 53393541be..8ac066549b 100644 --- a/packages/backend/src/services/alerts/AlertDestinationDelivery.ts +++ b/packages/backend/src/services/alerts/AlertDestinationDelivery.ts @@ -158,6 +158,7 @@ export const makeAlertDestinationDelivery = (options: { signalType: context.signalType, severity: context.severity, groupKey: context.groupKey, + ...(context.groupLabel == null ? undefined : { groupLabel: context.groupLabel }), comparator: context.comparator, threshold: context.threshold, thresholdUpper: context.thresholdUpper, diff --git a/packages/backend/src/services/alerts/AlertReadModelsService.ts b/packages/backend/src/services/alerts/AlertReadModelsService.ts index 283e4cac37..114fca6622 100644 --- a/packages/backend/src/services/alerts/AlertReadModelsService.ts +++ b/packages/backend/src/services/alerts/AlertReadModelsService.ts @@ -192,6 +192,8 @@ export const rowToIncidentDocument = (row: AlertIncidentRow) => ? null : Option.getOrNull(decodeAlertIncidentAckSource(row.acknowledgedVia)), errorIssueId: row.errorIssueId != null ? decodeErrorIssueIdSync(row.errorIssueId) : null, + ...(row.signalType === "anomaly" ? { anomalyIncidentId: row.anomalyIncidentId } : undefined), + ...(row.groupLabel === null ? undefined : { groupLabel: row.groupLabel }), }) /** An optional request bound, or `null` when it does not parse (the filter is then skipped). */ diff --git a/packages/backend/src/services/alerts/AlertRuleModel.ts b/packages/backend/src/services/alerts/AlertRuleModel.ts index cb5d721581..23f8cb20c2 100644 --- a/packages/backend/src/services/alerts/AlertRuleModel.ts +++ b/packages/backend/src/services/alerts/AlertRuleModel.ts @@ -37,6 +37,7 @@ import { type AlertSeverity, type AlertSignalType, type AlertThresholds, + type AnomalySignalType, type QueryBuilderQueryDraftPayload, type OrgId, isManagedSignalType, @@ -48,6 +49,7 @@ import { DateTime, Array as Arr, Effect, Match, Result, Schema, String as Str } import type { AlertRuntimeApi } from "./AlertRuntime" import type { QueryBuilderDataSource } from "@maple/query-model" import { validateBreachWindow, validateThresholds } from "./alert-thresholds" +import { ANOMALY_RULE_DEFAULTS, anomalySeverityCode } from "./anomaly/anomaly-rule" const StringArraySchema = Schema.Array(Schema.String) @@ -104,6 +106,8 @@ export interface NormalizedRule { readonly serviceNames: ReadonlyArray readonly excludeServiceNames: ReadonlyArray readonly environments: ReadonlyArray + /** Anomaly rules only: detector signals in scope; empty is every signal. */ + readonly anomalySignals: ReadonlyArray readonly tags: ReadonlyArray readonly groupBy: AlertGroupBy | null readonly signalType: AlertSignalType @@ -159,6 +163,7 @@ export const normalizedRuleToDocument = ( serviceNames: [...rule.serviceNames], excludeServiceNames: [...rule.excludeServiceNames], environments: [...rule.environments], + ...(rule.signalType === "anomaly" ? { anomalySignals: [...rule.anomalySignals] } : undefined), tags: [...rule.tags], groupBy: rule.groupBy, signalType: rule.signalType, @@ -238,8 +243,8 @@ export const managedRuleMessage = (signalType: ManagedSignalType): string => : "slo_burn_rate rules belong to an SLO; create, edit or delete the SLO instead" /** - * The stored plan of every managed rule (`uptime`, `slo_burn_rate`): no query, the evaluator - * reads the owning resource instead. + * The stored plan of every queryless rule (`uptime`, `slo_burn_rate`, `anomaly`): the evaluator + * reads the owning resource or the anomaly detector's incidents instead. */ export const MANAGED_RULE_PLAN = new CompiledAlertQueryPlan({ kind: "spec", @@ -278,6 +283,7 @@ export const uptimeRuleFields = (monitor: { serviceNamesJson: monitor.serviceName === null ? null : [monitor.serviceName], excludeServiceNamesJson: null, environmentsJson: null, + anomalySignalsJson: null, tagsJson: null, groupBy: null, signalType: "uptime", @@ -546,6 +552,8 @@ export const compileRulePlan = Effect.fn("AlertsService.compileRulePlan")(functi sampleCountStrategy: null, noDataBehavior, }) + } else if (rule.signalType === "anomaly") { + return MANAGED_RULE_PLAN } else if (isManagedSignalType(rule.signalType)) { return yield* makeAlertValidationError(managedRuleMessage(rule.signalType)) } else { @@ -582,7 +590,9 @@ const parseCompiledPlan = ( cause, }) // A managed rule reads its owner (a monitor's state, an SLO's burn), never a stored query. - if (isManagedSignalType(row.signalType)) return Effect.succeed(MANAGED_RULE_PLAN) + if (isManagedSignalType(row.signalType) || row.signalType === "anomaly") { + return Effect.succeed(MANAGED_RULE_PLAN) + } if (row.signalType === "raw_query") { if (row.rawQuerySql == null) { return Effect.fail(invalid("Stored raw alert is missing its SQL query", "rawQuerySql is null")) @@ -746,6 +756,9 @@ export const rowToRuleDocument = ( serviceNames: [...stored.serviceNames], excludeServiceNames: [...stored.excludeServiceNames], environments: [...stored.environments], + ...(row.signalType === "anomaly" + ? { anomalySignals: [...(row.anomalySignalsJson ?? [])] } + : undefined), tags: [...stored.tags], groupBy: stored.groupBy, signalType: decodeAlertSignalTypeSync(row.signalType), @@ -844,6 +857,7 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { serviceNames: stored.serviceNames, excludeServiceNames: stored.excludeServiceNames, environments: stored.environments, + anomalySignals: row.anomalySignalsJson ?? [], tags: stored.tags, groupBy: decoded.groupBy, signalType: decoded.signalType, @@ -879,6 +893,8 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { return yield* invalidRuleError([managedRuleMessage(request.signalType)]) } const name = Str.trim(request.name) + // An anomaly rule pages on the detector's open incidents, so most fields are pinned. + const anomaly = request.signalType === "anomaly" // A builder query's draft carries its own scope and never reads the // service fields, so keeping them would only fan out identical queries. const draftOwnsScope = request.signalType === "builder_query" @@ -891,7 +907,7 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { : [] const serviceName = serviceNames.length === 1 ? (serviceNames[0] ?? null) : null const excludeServiceNames = - request.excludeServiceNames && !draftOwnsScope + request.excludeServiceNames && !draftOwnsScope && !anomaly ? Arr.filter( Arr.map(request.excludeServiceNames, (service) => Str.trim(service)), (service) => service.length > 0, @@ -907,31 +923,50 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { }), ) : [] + const anomalySignals = anomaly ? Arr.dedupe(request.anomalySignals ?? []) : [] const tags = normalizeTags(request.tags) - const groupBy = request.groupBy ?? null + const groupBy = anomaly ? null : (request.groupBy ?? null) + const comparator = anomaly ? ANOMALY_RULE_DEFAULTS.comparator : request.comparator const details: string[] = [] if (name.length === 0 && !options?.forPreview) details.push("name is required") + if (!anomaly && request.anomalySignals !== undefined && request.anomalySignals.length > 0) { + details.push("anomalySignals is only supported for anomaly alerts") + } // `thresholds` wins; the single-threshold shape is still one tier. const tiered = request.thresholds !== undefined - const requestedThresholds: AlertThresholds | null = + const singleTier = (threshold: number) => + Arr.of( + legacyAlertThreshold({ + severity: request.severity ?? "warning", + threshold, + thresholdUpper: request.thresholdUpper ?? null, + destinationIds: request.destinationIds ?? [], + }), + ) + const authoredThresholds: AlertThresholds | null = request.thresholds !== undefined ? request.thresholds - : request.threshold === undefined - ? null - : Arr.of( - legacyAlertThreshold({ - severity: request.severity ?? "warning", - threshold: request.threshold, - thresholdUpper: request.thresholdUpper ?? null, - destinationIds: request.destinationIds ?? [], - }), - ) + : request.threshold !== undefined + ? singleTier(request.threshold) + : anomaly + ? singleTier(0) + : null + // An anomaly tier's value is its severity code, the value the rule observes. + const requestedThresholds: AlertThresholds | null = + anomaly && authoredThresholds !== null + ? Arr.map(authoredThresholds, (tier) => ({ + ...tier, + value: anomalySeverityCode(tier.severity), + upper: null, + recoveryValue: null, + })) + : authoredThresholds if (requestedThresholds === null) { details.push("threshold must be a finite number") } else { - if (!tiered && request.severity === undefined) details.push("severity is required") + if (!tiered && request.severity === undefined && !anomaly) details.push("severity is required") details.push( - ...validateThresholds(request.comparator, requestedThresholds, { + ...validateThresholds(comparator, requestedThresholds, { label: (index, field) => tiered ? `thresholds[${index}].${field}` @@ -940,18 +975,25 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { : field === "upper" ? "thresholdUpper" : "recoveryValue", - requireDestinations: !options?.forPreview, + // An unrouted anomaly rule still shows its incidents in the app. + requireDestinations: !options?.forPreview && !anomaly, }), ) } - const consecutiveBreachesRequired = request.consecutiveBreachesRequired ?? 2 - const breachesOutOf = request.breachesOutOf ?? null + const consecutiveBreachesRequired = anomaly + ? ANOMALY_RULE_DEFAULTS.consecutiveBreachesRequired + : (request.consecutiveBreachesRequired ?? 2) + const breachesOutOf = anomaly ? ANOMALY_RULE_DEFAULTS.breachesOutOf : (request.breachesOutOf ?? null) details.push(...validateBreachWindow(consecutiveBreachesRequired, breachesOutOf)) - const evaluationIntervalMinutes = request.evaluationIntervalMinutes ?? null + // An anomaly rule reads open incidents, not a window, so both are pinned. + const windowMinutes = anomaly ? ANOMALY_RULE_DEFAULTS.windowMinutes : request.windowMinutes + const evaluationIntervalMinutes = anomaly + ? ANOMALY_RULE_DEFAULTS.evaluationIntervalMinutes + : (request.evaluationIntervalMinutes ?? null) // A check reads only its window, so a longer gap between checks would leave data unread. - if (evaluationIntervalMinutes !== null && evaluationIntervalMinutes > request.windowMinutes) { + if (evaluationIntervalMinutes !== null && evaluationIntervalMinutes > windowMinutes) { details.push( - `windowMinutes (${request.windowMinutes}) must be at least evaluationIntervalMinutes (${evaluationIntervalMinutes}), or data between checks is never evaluated`, + `windowMinutes (${windowMinutes}) must be at least evaluationIntervalMinutes (${evaluationIntervalMinutes}), or data between checks is never evaluated`, ) } if (request.signalType === "builder_query" && !request.queryBuilderDraft) { @@ -1017,27 +1059,34 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { serviceNames, excludeServiceNames, environments, + anomalySignals, tags, groupBy, signalType: request.signalType, - comparator: request.comparator, + comparator, threshold: legacy.threshold, thresholdUpper: legacy.thresholdUpper, - windowMinutes: request.windowMinutes, + windowMinutes, evaluationIntervalMinutes, // An edit gives a slowed rule a fresh start at the interval it asks for. slowedIntervalMinutes: null, - minimumSampleCount: request.minimumSampleCount ?? 0, + minimumSampleCount: anomaly + ? ANOMALY_RULE_DEFAULTS.minimumSampleCount + : (request.minimumSampleCount ?? 0), consecutiveBreachesRequired, breachesOutOf, - consecutiveHealthyRequired: request.consecutiveHealthyRequired ?? 2, + consecutiveHealthyRequired: anomaly + ? ANOMALY_RULE_DEFAULTS.consecutiveHealthyRequired + : (request.consecutiveHealthyRequired ?? 2), renotifyIntervalMinutes: request.renotifyIntervalMinutes ?? 30, - apdexThresholdMs: request.apdexThresholdMs ?? (request.signalType === "apdex" ? 500 : null), - queryBuilderDraft: request.queryBuilderDraft ?? null, + apdexThresholdMs: anomaly + ? null + : (request.apdexThresholdMs ?? (request.signalType === "apdex" ? 500 : null)), + queryBuilderDraft: anomaly ? null : (request.queryBuilderDraft ?? null), rawQuerySql: request.signalType === "raw_query" ? normalizeOptionalString(request.rawQuerySql) : null, rawQueryReducer: request.signalType === "raw_query" ? (request.rawQueryReducer ?? null) : null, - alertOnNoData: request.alertOnNoData ?? false, + alertOnNoData: anomaly ? false : (request.alertOnNoData ?? false), destinationIds: legacy.destinationIds, createdAt: nowMs, updatedAt: nowMs, diff --git a/packages/backend/src/services/alerts/AlertRulesService.ts b/packages/backend/src/services/alerts/AlertRulesService.ts index e694d52b93..8e733a9901 100644 --- a/packages/backend/src/services/alerts/AlertRulesService.ts +++ b/packages/backend/src/services/alerts/AlertRulesService.ts @@ -18,7 +18,6 @@ import { isManagedSignalType, MANAGED_SIGNAL_TYPES, } from "@maple/domain/http" -import * as Orm from "@maple-dev/effect-orm/database" import * as PG from "@maple-dev/effect-orm/postgres" import { AlertDeliveryEvents, @@ -45,8 +44,15 @@ import { type RuleEvaluationState, } from "./AlertRuleModel" import { tierConditionChanged } from "./alert-thresholds" -import { makePersistenceError } from "./alert-persistence" +import { lockOrgAlertRules, makePersistenceError } from "./alert-persistence" import { AlertRuntime, type AlertRuntimeApi } from "./AlertRuntime" +import { + DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE, + DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, + DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, + isDefaultAnomalyScope, + storedAnomalyScope, +} from "./anomaly/anomaly-rule" import * as Policy from "@maple/domain/policy" const decodeErrorKindOption = Schema.decodeUnknownOption(AlertEvaluationErrorKind) @@ -71,10 +77,24 @@ export interface AlertRulesServiceApi { ruleId: AlertRuleDocument["id"], ) => Effect.Effect< AlertRuleDeleteResponse, - AlertForbiddenError | AlertPersistenceError | AlertRuleNotFoundError + AlertForbiddenError | AlertPersistenceError | AlertRuleNotFoundError | AlertValidationError > } +/** The org's default anomaly rule (anomaly, no scope), if it has one. */ +const findDefaultAnomalyRule = (db: MapleDb, orgId: OrgId) => + db + .run( + PG.from(AlertRules) + .select("id", "serviceNamesJson", "environmentsJson", "anomalySignalsJson") + .where(($) => [$.orgId.eq(orgId), $.signalType.eq("anomaly")]), + ) + .pipe( + Effect.map((rows) => + Arr.findFirst(rows, (row) => isDefaultAnomalyScope(storedAnomalyScope(row))), + ), + ) + /** * Deletes a rule with its deliveries, incidents, states and claim. Run it inside a * transaction; the uptime monitor delete reuses it for the monitor's backing rule. @@ -171,6 +191,8 @@ export const makeAlertRulePersistence = (options: { userId: UserId, existingId: AlertRuleId | null, request: AlertRuleUpsertRequest, + /** The stored rule's signal type on an edit; null on create. */ + previousSignalType: string | null = null, ) { const normalized = yield* normalizeRule(orgId, request) const ruleId = existingId ?? normalized.id @@ -196,6 +218,7 @@ export const makeAlertRulePersistence = (options: { excludeServiceNamesJson: normalized.excludeServiceNames.length > 0 ? normalized.excludeServiceNames : null, environmentsJson: normalized.environments.length > 0 ? normalized.environments : null, + anomalySignalsJson: normalized.anomalySignals.length > 0 ? normalized.anomalySignals : null, tagsJson: normalized.tags.length > 0 ? normalized.tags : null, groupBy: groupByJson, signalType: normalized.signalType, @@ -227,7 +250,7 @@ export const makeAlertRulePersistence = (options: { const writeRows = yield* dbExecute((db) => db.transaction( Effect.gen(function* () { - yield* db.execute(Orm.sql`select pg_advisory_xact_lock(hashtext(${orgId}))`) + yield* lockOrgAlertRules(db, orgId) // Destination existence is checked INSIDE the lock: destination // deletion takes the same per-org advisory lock around its reference // scan, so a rule can no longer commit a reference to a destination @@ -250,10 +273,42 @@ export const makeAlertRulePersistence = (options: { }) } } + // One default anomaly rule per org, kept enabled and unscoped while it exists. + // Only an edit that is or was an anomaly rule can touch it. + const touchesAnomaly = + normalized.signalType === "anomaly" || previousSignalType === "anomaly" + const defaultAnomalyRule = touchesAnomaly + ? yield* findDefaultAnomalyRule(db, orgId) + : Option.none() + const becomesDefault = + normalized.signalType === "anomaly" && isDefaultAnomalyScope(normalized) + // A disabled unscoped rule would hold the default slot and block the backfill. + if (becomesDefault && !normalized.enabled) { + return yield* makeAlertValidationError( + Option.isSome(defaultAnomalyRule) && defaultAnomalyRule.value.id === existingId + ? DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE + : DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, + ) + } + if (Option.isSome(defaultAnomalyRule)) { + const isTheDefault = defaultAnomalyRule.value.id === existingId + if (isTheDefault && !(becomesDefault && normalized.enabled)) { + return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + } + if (!isTheDefault && becomesDefault) { + return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE) + } + } if (normalized.enabled) { const activeRows = yield* db.run( PG.from(AlertRules) - .select("id") + .select( + "id", + "signalType", + "serviceNamesJson", + "environmentsJson", + "anomalySignalsJson", + ) // Managed rules (monitors, SLOs) are not counted against the rule cap. .where(($) => [ $.orgId.eq(orgId), @@ -263,7 +318,16 @@ export const makeAlertRulePersistence = (options: { ) const alreadyActive = existingId != null && Arr.some(activeRows, (row) => row.id === existingId) - if (!alreadyActive && activeRows.length >= MAX_ACTIVE_ALERT_RULES_PER_ORG) { + // The system creates the default anomaly rule, so it takes no slot from the cap. + const counted = Arr.filter( + activeRows, + (row) => + !( + row.signalType === "anomaly" && + isDefaultAnomalyScope(storedAnomalyScope(row)) + ), + ).length + if (!alreadyActive && counted >= MAX_ACTIVE_ALERT_RULES_PER_ORG) { return yield* makeAlertValidationError( `Organizations may have at most ${MAX_ACTIVE_ALERT_RULES_PER_ORG} active alert rules`, ) @@ -349,8 +413,9 @@ export const makeAlertRulePersistence = (options: { userId: UserId, existingId: AlertRuleId, request: AlertRuleUpsertRequest, + previousSignalType: string | null = null, ) { - const { ruleId, txid } = yield* writeRuleRow(orgId, userId, existingId, request) + const { ruleId, txid } = yield* writeRuleRow(orgId, userId, existingId, request, previousSignalType) const row = yield* findRuleRow(orgId, ruleId) if (row === undefined) { return yield* new AlertPersistenceError({ @@ -420,7 +485,18 @@ export const makeAlertRulePersistence = (options: { if (isManagedSignalType(row.signalType)) { return yield* new AlertRuleNotFoundError({ message: managedRuleMessage(row.signalType), ruleId }) } - const deleted = yield* dbExecute((db) => db.transaction(deleteRuleRows(db, orgId, ruleId))) + const deleted = yield* dbExecute((db) => + db.transaction( + Effect.gen(function* () { + yield* lockOrgAlertRules(db, orgId) + const defaultAnomalyRule = yield* findDefaultAnomalyRule(db, orgId) + if (Option.isSome(defaultAnomalyRule) && defaultAnomalyRule.value.id === ruleId) { + return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + } + return yield* deleteRuleRows(db, orgId, ruleId) + }), + ), + ) const txid = readTxid(deleted) return new AlertRuleDeleteResponse({ id: ruleId, diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts new file mode 100644 index 0000000000..766669ff19 --- /dev/null +++ b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts @@ -0,0 +1,514 @@ +// SAFETY-FILE: JSON in this test is emitted by the fixture or unit under test before its fields are asserted. +import { afterEach, assert, describe, expect, it } from "@effect/vitest" +import * as PG from "@maple-dev/effect-orm/postgres" +import { AnomalyIncidents } from "@maple/db/tables" +import { + AlertRuleUpsertRequest, + AnomalyIncidentId, + ErrorIssueId, + type AlertDestinationId, + type AlertThresholds, + type AnomalyIncidentSeverity, + type AnomalySignalType, +} from "@maple/domain/http" +import { Array as Arr, Clock, Duration, Effect, Layer, Result, Schema } from "effect" +import { TestClock } from "effect/testing" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { fakeChatSessions } from "@maple/backend/platform/chat-sessions-fake" +import { + cleanupTestDbs, + createTestDb, + executeSql, + queryFirstRow, + type TestDb, +} from "@maple/backend/platform/test-pglite" +import { AlertsService } from "./AlertsService" +import { AnomalyAlertSource } from "./anomaly/AnomalyAlertSource" +import { + DEFAULT_CLOCK_EPOCH_MS, + adminRoles, + asOrgId, + asUserId, + createWebhookDestination, + makeLayer, + makeWarehouseStub, +} from "./alerts-service-test-support" + +const trackedDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(trackedDbs)) + +const asAnomalyId = Schema.decodeUnknownSync(AnomalyIncidentId) +const asIssueId = Schema.decodeUnknownSync(ErrorIssueId) + +const orgId = asOrgId("org_anomaly_alerts") +const userId = asUserId("user_anomaly_alerts") + +/** Fetch bodies arrive as a string or, from the Effect HTTP client, as bytes. */ +const requestBodyText = (body: RequestInit["body"]): string => + body instanceof Uint8Array ? new TextDecoder().decode(body) : typeof body === "string" ? body : "" + +const harness = () => { + const testDb = createTestDb(trackedDbs) + const bodies: Array = [] + const turns: Array = [] + const fetchImpl: typeof fetch = async (_input, init) => { + bodies.push(requestBodyText(init?.body)) + return new Response("ok", { status: 200 }) + } + const chatSessions = fakeChatSessions(() => ({ + beginTurn: async (input: { sessionId: string; messageId: string }) => { + turns.push(input.sessionId) + return { cursor: 0, messageId: input.messageId, turnMessageId: input.messageId } + }, + })) + const layer = Layer.mergeAll( + makeLayer(testDb, makeWarehouseStub({}), { fetch: fetchImpl, chatSessions }), + AnomalyAlertSource.layer.pipe(Layer.provide(testDb.layer)), + testDb.layer, + ) + return { testDb, bodies, turns, layer } +} + +interface SeededAnomaly { + readonly id: string + readonly signalType: AnomalySignalType + readonly serviceName: string + readonly severity: AnomalyIncidentSeverity + readonly lastObservedValue: number + readonly baselineMedian: number + readonly errorIssueId?: string +} + +const detectorKey = (anomaly: Pick) => + `${anomaly.signalType}:production:${anomaly.serviceName}` + +const insertAnomaly = (anomaly: SeededAnomaly) => + Effect.gen(function* () { + const database = yield* Database + const now = yield* Clock.currentTimeMillis + yield* database.execute((db) => + db.run( + PG.insertInto(AnomalyIncidents).values({ + id: asAnomalyId(anomaly.id), + orgId, + detectorKey: detectorKey(anomaly), + signalType: anomaly.signalType, + serviceName: anomaly.serviceName, + deploymentEnv: "production", + fingerprintHash: null, + errorIssueId: anomaly.errorIssueId === undefined ? null : asIssueId(anomaly.errorIssueId), + status: "open", + severity: anomaly.severity, + openedValue: anomaly.lastObservedValue, + baselineMedian: anomaly.baselineMedian, + baselineSigma: 0.001, + thresholdValue: anomaly.baselineMedian * 2, + lastObservedValue: anomaly.lastObservedValue, + firstTriggeredAt: now, + lastTriggeredAt: now, + resolvedAt: null, + resolveReason: null, + dedupeKey: `dedupe-${anomaly.id}`, + createdAt: now, + updatedAt: now, + }), + ), + ) + }).pipe(Effect.orDie) + +const updateAnomaly = (testDb: TestDb, id: string, set: "severity = 'critical'" | "status = 'resolved'") => + Effect.promise(() => executeSql(testDb, `update anomaly_incidents set ${set} where id = $1`, [id])) + +const countRows = (testDb: TestDb, table: "error_issues" | "alert_delivery_events") => + Effect.promise(() => + queryFirstRow<{ n: number }>(testDb, `select count(*)::int as n from ${table} where org_id = $1`, [ + orgId, + ]), + ).pipe(Effect.map((row) => row?.n ?? 0)) + +const enableTriage = (testDb: TestDb) => + Effect.promise(() => + executeSql( + testDb, + "insert into ai_triage_settings (org_id, enabled, max_runs_per_day, updated_at) values ($1, true, 100, now())", + [orgId], + ), + ) + +const criticalTier = (destinationId: AlertDestinationId) => ({ + severity: "critical" as const, + value: 2, + upper: null, + recoveryValue: null, + destinationIds: [destinationId], +}) + +/** Both tiers; only the critical one pages `destinationId`. */ +const criticalRouted = (destinationId: AlertDestinationId): AlertThresholds => [ + { severity: "warning", value: 1, upper: null, recoveryValue: null, destinationIds: [] }, + { severity: "critical", value: 2, upper: null, recoveryValue: null, destinationIds: [destinationId] }, +] + +const anomalyRule = (input: { + readonly name: string + readonly thresholds: AlertThresholds + readonly serviceNames?: ReadonlyArray + readonly enabled?: boolean +}) => + new AlertRuleUpsertRequest({ + name: input.name, + enabled: input.enabled ?? true, + signalType: "anomaly", + comparator: "gte", + windowMinutes: 5, + thresholds: input.thresholds, + ...(input.serviceNames === undefined ? undefined : { serviceNames: input.serviceNames }), + }) + +const nextTick = Effect.gen(function* () { + yield* TestClock.adjust(Duration.minutes(1)) + const alerts = yield* AlertsService + yield* alerts.runSchedulerTick() +}) + +const errorRateSpike: SeededAnomaly = { + id: "00000000-0000-4000-8000-00000000a001", + signalType: "error_rate", + serviceName: "checkout", + severity: "warning", + lastObservedValue: 0.042, + baselineMedian: 0.003, + errorIssueId: "00000000-0000-4000-8000-00000000e001", +} + +describe("AlertsService anomaly rules", () => { + it.effect("the default rule opens a linked incident for an open anomaly without paging", () => { + const { testDb, turns, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* enableTriage(testDb) + yield* insertAnomaly(errorRateSpike) + const created = yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + assert.strictEqual(created, 1) + + yield* alerts.runSchedulerTick() + + const incidents = (yield* alerts.listIncidents(orgId)).incidents + assert.lengthOf(incidents, 1) + expect(incidents[0]).toMatchObject({ + status: "open", + signalType: "anomaly", + groupKey: errorRateSpike.id, + severity: "warning", + anomalyIncidentId: errorRateSpike.id, + errorIssueId: errorRateSpike.errorIssueId, + }) + // The anomaly already has its issue and its triage; the alert adds neither. + assert.strictEqual(yield* countRows(testDb, "error_issues"), 0) + assert.deepStrictEqual(turns, []) + // The default rule routes no tier, so nothing is delivered. + assert.strictEqual(yield* countRows(testDb, "alert_delivery_events"), 0) + }).pipe(Effect.provide(layer)) + }) + + it.effect("a routed critical tier pages its destination with the anomaly's own sentence", () => { + const { bodies, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ name: "Anomalies", thresholds: criticalRouted(destination.id) }), + ) + yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) + + yield* alerts.runSchedulerTick() + + const incidents = (yield* alerts.listIncidents(orgId)).incidents + expect(incidents[0]).toMatchObject({ status: "open", severity: "critical" }) + const events = (yield* alerts.listDeliveryEvents(orgId)).events + assert.deepStrictEqual( + Arr.map(events, (event) => [event.eventType, event.destinationId]), + [["trigger", destination.id]], + ) + const trigger = Schema.decodeSync( + Schema.fromJsonString( + Schema.Struct({ + eventType: Schema.String, + rule: Schema.Struct({ groupKey: Schema.String, groupLabel: Schema.String }), + observed: Schema.Struct({ value: Schema.Number, detail: Schema.String }), + }), + ), + )(bodies[0] ?? "") + assert.deepStrictEqual(trigger, { + eventType: "trigger", + // The group key is the anomaly incident's id; people see its service and environment. + rule: { groupKey: errorRateSpike.id, groupLabel: "checkout (production)" }, + observed: { + value: 2, + detail: "error rate 4.2% vs baseline 0.3% in checkout (production)", + }, + }) + }).pipe(Effect.provide(layer)) + }) + + it.effect("follows the anomaly from warning to critical and resolves with it", () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ name: "Anomalies", thresholds: criticalRouted(destination.id) }), + ) + yield* insertAnomaly(errorRateSpike) + + yield* alerts.runSchedulerTick() + const opened = (yield* alerts.listIncidents(orgId)).incidents + expect(opened[0]).toMatchObject({ status: "open", severity: "warning" }) + assert.strictEqual(yield* countRows(testDb, "alert_delivery_events"), 0) + + yield* updateAnomaly(testDb, errorRateSpike.id, "severity = 'critical'") + yield* nextTick + const escalated = (yield* alerts.listIncidents(orgId)).incidents + assert.lengthOf(escalated, 1) + expect(escalated[0]).toMatchObject({ + status: "open", + severity: "critical", + peakSeverity: "critical", + }) + + yield* updateAnomaly(testDb, errorRateSpike.id, "status = 'resolved'") + yield* nextTick + const resolved = (yield* alerts.listIncidents(orgId)).incidents + expect(resolved[0]).toMatchObject({ status: "resolved" }) + // The escalation paged the critical destination, so its resolve reaches it too. + const events = (yield* alerts.listDeliveryEvents(orgId)).events + assert.includeMembers( + Arr.map(events, (event) => `${event.eventType}:${event.destinationId}`), + [`resolve:${destination.id}`], + ) + assert.strictEqual(yield* countRows(testDb, "error_issues"), 0) + }).pipe(Effect.provide(layer)) + }) + + it.effect( + "a warning stays on the default rule and hands over to a critical-only rule once critical", + () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* insertAnomaly(errorRateSpike) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + const scoped = yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ + name: "Checkout critical anomalies", + serviceNames: ["checkout"], + thresholds: [criticalTier(destination.id)], + }), + ) + + // The scoped rule has no warning tier, so the default rule takes the warning, unrouted. + yield* alerts.runSchedulerTick() + const warning = (yield* alerts.listIncidents(orgId)).incidents + assert.lengthOf(warning, 1) + assert.notStrictEqual(warning[0]?.ruleId, scoped.id) + assert.strictEqual(yield* countRows(testDb, "alert_delivery_events"), 0) + + yield* updateAnomaly(testDb, errorRateSpike.id, "severity = 'critical'") + yield* nextTick + const handedOver = (yield* alerts.listIncidents(orgId)).incidents + assert.sameDeepMembers( + Arr.map(handedOver, (incident) => [incident.ruleId === scoped.id, incident.status]), + [ + [false, "resolved"], + [true, "open"], + ], + ) + const events = (yield* alerts.listDeliveryEvents(orgId)).events + assert.deepStrictEqual( + Arr.map(events, (event) => [event.eventType, event.destinationId]), + [["trigger", destination.id]], + ) + }).pipe(Effect.provide(layer)) + }, + ) + + it.effect("a more specific rule takes an open anomaly over, and takes it back when re-enabled", () => { + const { bodies, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* insertAnomaly(errorRateSpike) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + yield* alerts.runSchedulerTick() + const openOn = Effect.gen(function* () { + const incidents = (yield* alerts.listIncidents(orgId)).incidents + return Arr.filterMap(incidents, (incident) => + incident.status === "open" ? Result.succeed(incident.ruleId) : Result.failVoid, + ) + }) + const [defaultRuleId] = yield* openOn + + const scopedRequest = (enabled: boolean) => + anomalyRule({ + name: "Checkout anomalies", + serviceNames: ["checkout"], + thresholds: [ + { ...criticalTier(destination.id), severity: "warning", value: 1 }, + criticalTier(destination.id), + ], + enabled, + }) + const scoped = yield* alerts.createRule(orgId, userId, adminRoles, scopedRequest(true)) + yield* nextTick + assert.deepStrictEqual(yield* openOn, [scoped.id]) + + // Disabling it resolves its incident from the stored label, with no observation at hand. + yield* alerts.updateRule(orgId, userId, adminRoles, scoped.id, scopedRequest(false)) + yield* nextTick + assert.deepStrictEqual(yield* openOn, [defaultRuleId]) + const sent = Arr.map( + bodies, + Schema.decodeUnknownSync( + Schema.fromJsonString( + Schema.Struct({ + eventType: Schema.String, + rule: Schema.Struct({ groupLabel: Schema.optionalKey(Schema.String) }), + }), + ), + ), + ) + assert.deepStrictEqual( + Arr.filter(sent, (body) => body.eventType === "resolve"), + [{ eventType: "resolve", rule: { groupLabel: "checkout (production)" } }], + ) + + yield* alerts.updateRule(orgId, userId, adminRoles, scoped.id, scopedRequest(true)) + yield* nextTick + assert.deepStrictEqual(yield* openOn, [scoped.id]) + }).pipe(Effect.provide(layer)) + }) + + it.effect("copies an issue the anomaly gains after its alert incident opened", () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const { errorIssueId: _issue, ...withoutIssue } = errorRateSpike + yield* insertAnomaly(withoutIssue) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + yield* alerts.runSchedulerTick() + assert.isNull((yield* alerts.listIncidents(orgId)).incidents[0]?.errorIssueId ?? null) + + yield* Effect.promise(() => + executeSql(testDb, "update anomaly_incidents set error_issue_id = $2 where id = $1", [ + errorRateSpike.id, + errorRateSpike.errorIssueId, + ]), + ) + yield* nextTick + const [linked] = (yield* alerts.listIncidents(orgId)).incidents + expect(linked).toMatchObject({ + errorIssueId: errorRateSpike.errorIssueId, + groupLabel: "checkout (production)", + }) + }).pipe(Effect.provide(layer)) + }) + + it.effect("a two-service rule opens incidents only for real anomalies in its scope", () => { + const { layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-00000000a003", + signalType: "latency_p95", + serviceName: "search", + severity: "critical", + lastObservedValue: 1800, + baselineMedian: 300, + }) + const rule = yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ + name: "Checkout and cart anomalies", + serviceNames: ["checkout", "cart"], + thresholds: [ + { + severity: "warning", + value: 1, + upper: null, + recoveryValue: null, + destinationIds: [], + }, + ], + }), + ) + + yield* alerts.runSchedulerTick() + yield* nextTick + + assert.deepStrictEqual( + Arr.map((yield* alerts.listIncidents(orgId)).incidents, (incident) => [ + incident.ruleId, + incident.groupKey, + incident.status, + ]), + [[rule.id, errorRateSpike.id, "open"]], + ) + }).pipe(Effect.provide(layer)) + }) + + it.effect("keeps one alert incident and its state while the detector rewrites the anomaly's key", () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* insertAnomaly(errorRateSpike) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + + yield* alerts.runSchedulerTick() + const [opened] = (yield* alerts.listIncidents(orgId)).incidents + yield* Effect.promise(() => + executeSql(testDb, "update anomaly_incidents set detector_key = $2 where id = $1", [ + errorRateSpike.id, + "error_spike:production:promoted-fingerprint", + ]), + ) + yield* nextTick + yield* nextTick + + const incidents = (yield* alerts.listIncidents(orgId)).incidents + assert.deepStrictEqual( + Arr.map(incidents, (incident) => [incident.id, incident.status]), + [[opened?.id, "open"]], + ) + // Housekeeping treats the rule as grouped, so its per-anomaly state survives each tick. + const states = yield* Effect.promise(() => + queryFirstRow<{ keys: Array }>( + testDb, + "select array_agg(group_key) as keys from alert_rule_states where org_id = $1", + [orgId], + ), + ) + assert.deepStrictEqual(states?.keys, [errorRateSpike.id]) + }).pipe(Effect.provide(layer)) + }) +}) diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index 2fe8b312e0..a154aa6e67 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -140,7 +140,7 @@ import { } from "./alert-evaluation-errors" import { ChatSessions } from "@maple/backend/platform/bindings" import { Database } from "@maple/backend/platform/DatabaseLive" -import { formatComparator } from "./alert-formatting" +import { formatComparator, signalCarriesDetail } from "./alert-formatting" import { ruleAtSeverity } from "./alert-thresholds" import { buildChatDigestBlocks, CHAT_DIGEST_THRESHOLD, type ChatDigestItem } from "./delivery/chat-digest" import { makeIncidentPushBudget, type IncidentPushBudget } from "./alert-push-budget" @@ -166,6 +166,7 @@ import { AlertReadModelsService, type AlertReadModelsServiceApi } from "./AlertR import { AlertRulesService, makeAlertRulePersistence, type AlertRulesServiceApi } from "./AlertRulesService" import { UptimeAlertSource } from "@maple/backend/services/uptime/UptimeAlertSource" import { SloAlertSource } from "@maple/backend/services/slos/SloAlertSource" +import { AnomalyAlertSource, type AnomalyAlertObservation } from "./anomaly/AnomalyAlertSource" import { evaluateSloBurn } from "@maple/backend/services/slos/slo-alert-rule" import { openForAlertIncident, @@ -196,7 +197,10 @@ const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)) export { AlertRuntime, type AlertRuntimeApi } from "./AlertRuntime" export type { AlertSchedulerLane } from "./AlertRuleModel" -type EvaluatedRule = import("@maple/alerting-core").AlertEvaluation +/** An anomaly rule's evaluation also carries the anomaly it observed, for linking and display. */ +type EvaluatedRule = import("@maple/alerting-core").AlertEvaluation & { + readonly anomaly?: AnomalyAlertObservation +} /** * A tick's claim on a rule expired and another tick re-claimed it before this @@ -595,6 +599,7 @@ export class AlertsService extends Context.Service ({ + evaluation: { + ...applyEvaluationLogic( + rule, + { value: observed.value, sampleCount: 1, hasData: true }, + observed.detail, + ), + anomaly: observed, + }, + groupKey: observed.groupKey, + })) + } // Snapped, not raw `now`: excludes the still-filling current minute and // lets rules over the same signal share bucket reads within a tick. See // `snapAlertWindowEndMs`. A scheduler tick passes its own. @@ -1289,8 +1310,9 @@ export class AlertsService extends Context.Service 1) { + // An anomaly rule applies its service scope inside its observation, never per service. + if (normalized.serviceNames.length > 1 && normalized.signalType !== "anomaly") { const serviceRules = yield* perServiceRules(normalized) const results = yield* Effect.forEach( serviceRules, @@ -1781,6 +1805,9 @@ export class AlertsService extends Context.Service + db.run( + PG.update(AlertIncidents) + .set({ errorIssueId: linkedIssueId, updatedAt: timestamp }) + .where(($) => [ + $.orgId.eq(row.orgId), + $.id.eq(linkTarget.id), + $.status.eq("open"), + $.errorIssueId.isNull(), + ]), + ), + ) + } + if (outcome.transition === "opened") { yield* Metric.update(AlertingMetrics.incidentsOpenedTotal, 1) } if (outcome.transition === "resolved") { yield* Metric.update(AlertingMetrics.incidentsResolvedTotal, 1) } - if (outcome.transition === "escalated" && outcome.severity === "critical") { + // An anomaly incident already has its issue and AI triage; the alert must not add a second. + const ownsIssue = normalized.signalType !== "anomaly" + if (ownsIssue && outcome.transition === "escalated" && outcome.severity === "critical") { yield* escalateAlertIssue({ orgId: row.orgId, ruleId: row.id, @@ -3501,7 +3555,7 @@ export class AlertsService extends Context.Service remaining > 0 ? [true, remaining - 1] : [false, remaining], @@ -3642,8 +3696,15 @@ export class AlertsService extends Context.Service - isGroupedPlan(r.compiledPlan) ? "grouped" : r.serviceNames.length > 1 ? "multi" : "single" + r.signalType === "anomaly" + ? "anomaly" + : isGroupedPlan(r.compiledPlan) + ? "grouped" + : r.serviceNames.length > 1 + ? "multi" + : "single" return mode(oldRule) !== mode(newRule) } @@ -4849,7 +4910,7 @@ export class AlertsService extends Context.Service Effect.gen(function* () { // Uptime rules never read the warehouse, so its outage does not skip them. - if (row.signalType !== "uptime") { + if (row.signalType !== "uptime" && row.signalType !== "anomaly") { const unavailable = yield* Ref.get(unavailableOrgs) if (HashSet.has(unavailable, row.orgId)) { return yield* new WarehouseUpstreamError({ @@ -4867,7 +4928,7 @@ export class AlertsService extends Context.Service 1) { + if (normalized.serviceNames.length > 1 && normalized.signalType !== "anomaly") { const serviceRules = yield* perServiceRules(normalized) yield* Effect.forEach(serviceRules, ({ groupKey, rule }) => Effect.gen(function* () { @@ -4925,10 +4986,13 @@ export class AlertsService extends Context.Service r.groupKey !== UNGROUPED_GROUP_KEY) - : isGroupedPlan(normalized.compiledPlan) + normalized.signalType === "anomaly" + ? true + : normalized.signalType === "uptime" + ? results.some((r) => r.groupKey !== UNGROUPED_GROUP_KEY) + : isGroupedPlan(normalized.compiledPlan) const excludeSet = HashSet.fromIterable(normalized.excludeServiceNames) const eligible = grouped ? Arr.filter(results, (r) => !HashSet.has(excludeSet, r.groupKey)) @@ -5393,6 +5457,7 @@ export class AlertsService extends Context.Service { export const displayGroupKey = (groupKey: string | null): string | null => groupKey == null || groupKey === UNGROUPED_GROUP_KEY ? null : groupKey +/** + * What a notification shows as the group: the observation's own label when it has one (an + * anomaly's "checkout (production)"), else the group key. An anomaly's key is an incident id, + * which never reaches a human. + */ +export const displayGroup = (context: { + readonly groupKey: string | null + readonly groupLabel?: string | null + readonly signalType?: string +}): string | null => + context.groupLabel ?? (context.signalType === "anomaly" ? null : displayGroupKey(context.groupKey)) + /** Clamp to a provider's field limit, marking the cut with an ellipsis. */ export const truncate = (value: string, max: number): string => value.length > max ? `${Str.slice(0, max - 1)(value)}…` : value @@ -187,23 +204,56 @@ export const formatThresholdSummary = (context: ThresholdContext): string => { const uptimeObserved = (context: Pick): string => context.detail ?? (context.value === 1 ? "The check is failing" : "The check is passing") +/** Whether a rule's notifications carry the evaluator's reason as their `detail`. */ +export const signalCarriesDetail = (signalType: string): boolean => + signalType === "uptime" || signalType === "slo_burn_rate" || signalType === "anomaly" + +/** An anomaly rule's value is its severity code; what it says is the detector's sentence. */ +const anomalyObserved = (context: Pick): string => + context.detail ?? + (context.value === 2 ? "Critical anomaly" : context.value === 1 ? "Warning anomaly" : "No open anomaly") + +const asSentence = (text: string): string => { + const capitalized = Str.capitalize(text) + return /[.!?]$/u.test(capitalized) ? capitalized : `${capitalized}.` +} + +/** One sentence for an anomaly rule's notification body. */ +export const anomalySummaryLine = ( + context: Pick, +): string => + context.eventType === "test" + ? "This is a test notification. Live alerts fire when the anomaly detector opens an anomaly in this rule's scope." + : asSentence(anomalyObserved(context)) + /** A breach without a value is a rule alerting on an empty window. */ export const formatObservedSummary = (context: ObservedContext): string => context.signalType === "uptime" ? uptimeObserved(context) - : context.signalType === "slo_burn_rate" && context.detail != null - ? context.detail - : context.value === null - ? `No data (threshold ${formatThresholdSummary(context)})` - : `${formatSignalMetric(context.value, signalDisplayOf(context))} ${formatThresholdSummary(context)}` + : context.signalType === "anomaly" + ? anomalyObserved(context) + : context.signalType === "slo_burn_rate" && context.detail != null + ? context.detail + : context.value === null + ? `No data (threshold ${formatThresholdSummary(context)})` + : `${formatSignalMetric(context.value, signalDisplayOf(context))} ${formatThresholdSummary(context)}` /** * A notification's subject line without its emoji: `checkout-api is down` for an uptime rule, * the rule name and event label for the rest. A resolve with no measured value was a config change. */ export const formatAlertHeadline = ( - context: Pick, + context: Pick & + Partial>, ): string => { + // An anomaly names what moved, e.g. `Critical anomaly: error rate 4.2% vs baseline 0.3% in checkout`. + if (context.signalType === "anomaly" && context.detail != null) { + if (context.eventType === "resolve") return `${context.ruleName}: anomaly resolved` + if (context.eventType !== "test" && context.value !== 0) { + const severity = context.severity === undefined ? "" : `${formatSeverityLabel(context.severity)} ` + return Str.capitalize(`${severity}anomaly: ${context.detail}`) + } + } if (context.signalType === "uptime") { if (context.eventType === "trigger") return `${context.ruleName} is down` if (context.eventType === "renotify") return `${context.ruleName} is still down` @@ -212,6 +262,10 @@ export const formatAlertHeadline = ( return `${context.ruleName} \u2014 ${formatEventTypeLabel(context.eventType)}` } +/** Whether a provider's short title should be the signal's own headline rather than rule and event. */ +export const hasSignalHeadline = (signalType: AlertSignalType): boolean => + signalType === "uptime" || signalType === "anomaly" + /** One sentence for an uptime rule's notification body. */ export const uptimeSummaryLine = ( context: Pick, diff --git a/packages/backend/src/services/alerts/alert-signal-display.ts b/packages/backend/src/services/alerts/alert-signal-display.ts index c20b58a016..f1a602ac5c 100644 --- a/packages/backend/src/services/alerts/alert-signal-display.ts +++ b/packages/backend/src/services/alerts/alert-signal-display.ts @@ -47,6 +47,7 @@ const PRESET_SIGNALS: Record = { throughput: { label: "Throughput", unit: "rpm" }, uptime: { label: "Uptime check", unit: "plain" }, slo_burn_rate: { label: "SLO burn rate", unit: "burn" }, + anomaly: { label: "Anomaly", unit: "plain" }, } satisfies Record /** diff --git a/packages/backend/src/services/alerts/alerts-service-test-support.ts b/packages/backend/src/services/alerts/alerts-service-test-support.ts index 6102371ff1..75bd52cce1 100644 --- a/packages/backend/src/services/alerts/alerts-service-test-support.ts +++ b/packages/backend/src/services/alerts/alerts-service-test-support.ts @@ -36,6 +36,7 @@ import { AlertReadModelsService } from "./AlertReadModelsService" import { AlertRulesService } from "./AlertRulesService" import { SloAlertSource } from "@maple/backend/services/slos/SloAlertSource" import { UptimeAlertSource } from "@maple/backend/services/uptime/UptimeAlertSource" +import { AnomalyAlertSource } from "@maple/backend/services/alerts/anomaly/AnomalyAlertSource" import { BucketCacheService } from "@maple/query-engine/caching" import { EdgeCacheService } from "@maple/cache" import { baselineWarehouseCapabilities } from "@maple/query-engine" @@ -328,6 +329,7 @@ export const makeLayer = ( Layer.provide(alertReadModelsLive), Layer.provide(alertRulesLive), Layer.provide(UptimeAlertSource.layer.pipe(Layer.provide(databaseLive))), + Layer.provide(AnomalyAlertSource.layer.pipe(Layer.provide(databaseLive))), Layer.provide( Layer.effect(SloAlertSource, SloAlertSource.make).pipe( Layer.provide(Layer.mergeAll(databaseLive, warehouseLive)), diff --git a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.test.ts b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.test.ts new file mode 100644 index 0000000000..eee42545cc --- /dev/null +++ b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.test.ts @@ -0,0 +1,422 @@ +import { afterEach, assert, describe, it } from "@effect/vitest" +import * as PG from "@maple-dev/effect-orm/postgres" +import { AlertIncidents, AlertRules, AnomalyIncidents } from "@maple/db/tables" +import { + AlertIncidentId, + AlertRuleId, + AnomalyIncidentId, + OrgId, + UserId, + type AlertThresholds, + type AnomalyIncidentSeverity, + type AnomalySignalType, +} from "@maple/domain/http" +import { Array as Arr, Effect, Layer, Order, Schema } from "effect" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { cleanupTestDbs, createTestDb, type TestDb } from "@maple/backend/platform/test-pglite" +import { AnomalyAlertSource } from "./AnomalyAlertSource" +import { + DEFAULT_ANOMALY_RULE_FALLBACK_NAME, + DEFAULT_ANOMALY_RULE_NAME, + defaultAnomalyRuleFields, +} from "./anomaly-rule" + +const asOrgId = Schema.decodeUnknownSync(OrgId) +const asRuleId = Schema.decodeUnknownSync(AlertRuleId) +const asAnomalyId = Schema.decodeUnknownSync(AnomalyIncidentId) +const asAlertIncidentId = Schema.decodeUnknownSync(AlertIncidentId) +const asUserId = Schema.decodeUnknownSync(UserId) + +const ORG = asOrgId("org_anomaly_source") +const QUIET_ORG = asOrgId("org_anomaly_source_quiet") +const USER = asUserId("user_anomaly_source") +const DEFAULT_RULE = asRuleId("00000000-0000-4000-8000-000000000301") +const CHECKOUT_RULE = asRuleId("00000000-0000-4000-8000-000000000302") +const NOW = Date.parse("2026-11-06T12:00:00.000Z") + +const createdDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(createdDbs)) + +const makeLayer = (testDb: TestDb) => AnomalyAlertSource.layer.pipe(Layer.provide(testDb.layer)) + +const insertRule = ( + orgId: OrgId, + id: AlertRuleId, + name: string, + scope: { readonly serviceNames?: ReadonlyArray; readonly thresholds?: AlertThresholds } = {}, +) => + Effect.gen(function* () { + const database = yield* Database + const fields = defaultAnomalyRuleFields(orgId, name) + yield* database.execute((db) => + db.run( + PG.insertInto(AlertRules).values({ + ...fields, + id, + serviceNamesJson: scope.serviceNames === undefined ? null : [...scope.serviceNames], + thresholdsJson: scope.thresholds ?? fields.thresholdsJson, + lastScheduledAt: null, + createdAt: NOW, + updatedAt: NOW, + createdBy: USER, + updatedBy: USER, + }), + ), + ) + }) + +const insertAnomaly = (fields: { + readonly id: string + readonly orgId?: OrgId + readonly signalType: AnomalySignalType + readonly serviceName: string + readonly severity: AnomalyIncidentSeverity + readonly status?: "open" | "resolved" + readonly lastObservedValue: number + readonly baselineMedian: number +}) => + Effect.gen(function* () { + const database = yield* Database + yield* database.execute((db) => + db.run( + PG.insertInto(AnomalyIncidents).values({ + id: asAnomalyId(fields.id), + orgId: fields.orgId ?? ORG, + detectorKey: `${fields.signalType}:production:${fields.serviceName}`, + signalType: fields.signalType, + serviceName: fields.serviceName, + deploymentEnv: "production", + fingerprintHash: null, + errorIssueId: null, + status: fields.status ?? "open", + severity: fields.severity, + openedValue: fields.lastObservedValue, + baselineMedian: fields.baselineMedian, + baselineSigma: 0.001, + thresholdValue: fields.baselineMedian * 2, + lastObservedValue: fields.lastObservedValue, + firstTriggeredAt: NOW, + lastTriggeredAt: NOW, + resolvedAt: null, + resolveReason: null, + dedupeKey: `dedupe-${fields.id}`, + createdAt: NOW, + updatedAt: NOW, + }), + ), + ) + }) + +const insertOpenAlertIncident = (ruleId: AlertRuleId, groupKey: string) => + Effect.gen(function* () { + const database = yield* Database + yield* database.execute((db) => + db.run( + PG.insertInto(AlertIncidents).values({ + id: asAlertIncidentId("00000000-0000-4000-8000-000000000399"), + orgId: ORG, + ruleId, + incidentKey: `incident-${groupKey}`, + ruleName: "Anomaly detection", + groupKey, + signalType: "anomaly", + severity: "warning", + status: "open", + comparator: "gte", + threshold: 1, + firstTriggeredAt: NOW, + lastTriggeredAt: NOW, + dedupeKey: `dedupe-${groupKey}`, + createdAt: NOW, + updatedAt: NOW, + }), + ), + ) + }) + +const byGroupKey = Order.mapInput( + Order.String, + (observation: { readonly groupKey: string }) => observation.groupKey, +) + +const CHECKOUT_SPIKE = "00000000-0000-4000-8000-000000000311" +const API_DRIFT = "00000000-0000-4000-8000-000000000312" + +const checkoutSpike = (severity: AnomalyIncidentSeverity = "critical") => + insertAnomaly({ + id: CHECKOUT_SPIKE, + signalType: "error_rate", + serviceName: "checkout", + severity, + lastObservedValue: 0.042, + baselineMedian: 0.003, + }) + +const disableRule = (ruleId: AlertRuleId) => + Effect.gen(function* () { + const database = yield* Database + yield* database.execute((db) => + db.run( + PG.update(AlertRules) + .set({ enabled: false }) + .where(($) => [$.id.eq(ruleId)]), + ), + ) + }) + +const observedKeys = (ruleId: AlertRuleId) => + Effect.gen(function* () { + const source = yield* AnomalyAlertSource + const observations = Arr.sort(yield* source.observe(ORG, ruleId), byGroupKey) + return Arr.map(observations, (observation) => [observation.groupKey, observation.value]) + }) + +describe("AnomalyAlertSource.observe", () => { + it.effect("emits the anomalies a rule is assigned, keyed by anomaly incident", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertRule(ORG, DEFAULT_RULE, DEFAULT_ANOMALY_RULE_NAME) + yield* insertRule(ORG, CHECKOUT_RULE, "Checkout anomalies", { serviceNames: ["checkout"] }) + yield* checkoutSpike() + yield* insertAnomaly({ + id: API_DRIFT, + signalType: "latency_p95", + serviceName: "api", + severity: "warning", + lastObservedValue: 840, + baselineMedian: 210, + }) + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000313", + signalType: "throughput", + serviceName: "api", + severity: "critical", + status: "resolved", + lastObservedValue: 3, + baselineMedian: 1200, + }) + const source = yield* AnomalyAlertSource + + const checkout = yield* source.observe(ORG, CHECKOUT_RULE) + assert.deepStrictEqual(checkout, [ + { + groupKey: CHECKOUT_SPIKE, + value: 2, + detail: "error rate 4.2% vs baseline 0.3% in checkout (production)", + anomalyIncidentId: asAnomalyId(CHECKOUT_SPIKE), + errorIssueId: null, + serviceName: "checkout", + deploymentEnv: "production", + signalType: "error_rate", + groupLabel: "checkout (production)", + }, + ]) + + // The default rule gets what no narrower rule covers, and never a resolved anomaly. + assert.deepStrictEqual(yield* observedKeys(DEFAULT_RULE), [[API_DRIFT, 1]]) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) + + it.effect( + "reports 0, with the anomaly's own scope, for an open alert incident whose anomaly ended", + () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertRule(ORG, DEFAULT_RULE, DEFAULT_ANOMALY_RULE_NAME) + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000313", + signalType: "throughput", + serviceName: "cart", + severity: "warning", + status: "resolved", + lastObservedValue: 3, + baselineMedian: 1200, + }) + yield* insertOpenAlertIncident(DEFAULT_RULE, "00000000-0000-4000-8000-000000000313") + const source = yield* AnomalyAlertSource + + assert.deepStrictEqual(yield* source.observe(ORG, DEFAULT_RULE), [ + { + groupKey: "00000000-0000-4000-8000-000000000313", + value: 0, + detail: "The anomaly ended", + anomalyIncidentId: asAnomalyId("00000000-0000-4000-8000-000000000313"), + errorIssueId: null, + serviceName: "cart", + deploymentEnv: "production", + signalType: "throughput", + groupLabel: "cart (production)", + }, + ]) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }, + ) + + it.effect("hands an open anomaly to a strictly more specific rule, never to an equal one", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertRule(ORG, DEFAULT_RULE, DEFAULT_ANOMALY_RULE_NAME) + yield* checkoutSpike() + yield* insertOpenAlertIncident(DEFAULT_RULE, CHECKOUT_SPIKE) + yield* insertRule(ORG, CHECKOUT_RULE, "Checkout anomalies", { serviceNames: ["checkout"] }) + + // The default resolves its incident and the narrower rule opens one. + assert.deepStrictEqual(yield* observedKeys(DEFAULT_RULE), [[CHECKOUT_SPIKE, 0]]) + assert.deepStrictEqual(yield* observedKeys(CHECKOUT_RULE), [[CHECKOUT_SPIKE, 2]]) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) + + it.effect("keeps an open anomaly when an equally specific rule with a lower id appears", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertRule(ORG, CHECKOUT_RULE, "Checkout anomalies", { serviceNames: ["checkout"] }) + yield* checkoutSpike() + yield* insertOpenAlertIncident(CHECKOUT_RULE, CHECKOUT_SPIKE) + yield* insertRule(ORG, DEFAULT_RULE, "Checkout again", { serviceNames: ["checkout"] }) + + assert.deepStrictEqual(yield* observedKeys(CHECKOUT_RULE), [[CHECKOUT_SPIKE, 2]]) + assert.deepStrictEqual(yield* observedKeys(DEFAULT_RULE), []) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) + + it.effect("releases an anomaly from a disabled rule to the next rule covering it", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertRule(ORG, DEFAULT_RULE, DEFAULT_ANOMALY_RULE_NAME) + yield* insertRule(ORG, CHECKOUT_RULE, "Checkout anomalies", { serviceNames: ["checkout"] }) + yield* checkoutSpike() + yield* insertOpenAlertIncident(CHECKOUT_RULE, CHECKOUT_SPIKE) + yield* disableRule(CHECKOUT_RULE) + + assert.deepStrictEqual(yield* observedKeys(DEFAULT_RULE), [[CHECKOUT_SPIKE, 2]]) + const source = yield* AnomalyAlertSource + const released = yield* source.observe(ORG, CHECKOUT_RULE) + assert.deepStrictEqual( + Arr.map(released, (observation) => [ + observation.groupKey, + observation.value, + observation.detail, + ]), + [[CHECKOUT_SPIKE, 0, "The anomaly moved off this rule"]], + ) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) +}) + +describe("AnomalyAlertSource.backfillDefaultRules", () => { + const anomalyRuleNames = (orgId: OrgId) => + Effect.gen(function* () { + const database = yield* Database + const rows = yield* database.execute((db) => + db.run( + PG.from(AlertRules) + .select("name", "signalType", "enabled") + .where(($) => [$.orgId.eq(orgId), $.signalType.eq("anomaly")]), + ), + ) + return Arr.map(rows, (row) => row.name) + }) + + it.effect("gives orgs with an open anomaly one default rule, once", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000312", + signalType: "latency_p95", + serviceName: "api", + severity: "warning", + lastObservedValue: 840, + baselineMedian: 210, + }) + // Only resolved anomalies: nothing to page on, so no rule yet. + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000314", + orgId: QUIET_ORG, + signalType: "throughput", + serviceName: "api", + severity: "warning", + status: "resolved", + lastObservedValue: 3, + baselineMedian: 1200, + }) + const source = yield* AnomalyAlertSource + + assert.strictEqual(yield* source.backfillDefaultRules, 1) + assert.strictEqual(yield* source.backfillDefaultRules, 0) + assert.deepStrictEqual(yield* anomalyRuleNames(ORG), [DEFAULT_ANOMALY_RULE_NAME]) + assert.deepStrictEqual(yield* anomalyRuleNames(QUIET_ORG), []) + + // The new rule owns the open anomaly. + const [ruleRow] = yield* (yield* Database).execute((db) => + db.run( + PG.from(AlertRules) + .select("id") + .where(($) => [$.orgId.eq(ORG)]), + ), + ) + assert.isDefined(ruleRow) + const observations = yield* source.observe(ORG, ruleRow?.id ?? DEFAULT_RULE) + assert.deepStrictEqual( + Arr.map(observations, (observation) => observation.groupKey), + ["00000000-0000-4000-8000-000000000312"], + ) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) + + it.effect("skips orgs that have a default rule and works around a taken name", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000312", + signalType: "latency_p95", + serviceName: "api", + severity: "warning", + lastObservedValue: 840, + baselineMedian: 210, + }) + // A scoped anomaly rule holds the default name but is not the default. + yield* insertRule(ORG, CHECKOUT_RULE, DEFAULT_ANOMALY_RULE_NAME, { serviceNames: ["checkout"] }) + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000314", + orgId: QUIET_ORG, + signalType: "throughput", + serviceName: "api", + severity: "warning", + lastObservedValue: 3, + baselineMedian: 1200, + }) + yield* insertRule(QUIET_ORG, DEFAULT_RULE, "Already here") + const source = yield* AnomalyAlertSource + + assert.strictEqual(yield* source.backfillDefaultRules, 1) + assert.deepStrictEqual(Arr.sort(yield* anomalyRuleNames(ORG), Order.String), [ + DEFAULT_ANOMALY_RULE_NAME, + DEFAULT_ANOMALY_RULE_FALLBACK_NAME, + ]) + assert.deepStrictEqual(yield* anomalyRuleNames(QUIET_ORG), ["Already here"]) + assert.strictEqual(yield* source.backfillDefaultRules, 0) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) + + it.effect("numbers the default rule's name when both default names are taken", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* insertAnomaly({ + id: "00000000-0000-4000-8000-000000000312", + signalType: "latency_p95", + serviceName: "api", + severity: "warning", + lastObservedValue: 840, + baselineMedian: 210, + }) + yield* insertRule(ORG, CHECKOUT_RULE, DEFAULT_ANOMALY_RULE_NAME, { serviceNames: ["checkout"] }) + yield* insertRule(ORG, DEFAULT_RULE, DEFAULT_ANOMALY_RULE_FALLBACK_NAME, { + serviceNames: ["cart"], + }) + const source = yield* AnomalyAlertSource + + assert.strictEqual(yield* source.backfillDefaultRules, 1) + assert.include(yield* anomalyRuleNames(ORG), `${DEFAULT_ANOMALY_RULE_NAME} (default 2)`) + }).pipe(Effect.provide(Layer.merge(makeLayer(testDb), testDb.layer))) + }) +}) diff --git a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts new file mode 100644 index 0000000000..f6e3f213b0 --- /dev/null +++ b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts @@ -0,0 +1,362 @@ +import { randomUUID } from "node:crypto" +import * as PG from "@maple-dev/effect-orm/postgres" +import { AlertIncidents, AlertRules, AnomalyIncidents } from "@maple/db/tables" +import type { MapleDb } from "@maple/db/client" +import { + AlertPersistenceError, + AlertSeverity as AlertSeveritySchema, + AlertThresholds as AlertThresholdsSchema, + type AnomalySignalType, +} from "@maple/domain/http" +import { + AlertRuleId, + AnomalyIncidentId, + UserId, + type ErrorIssueId, + type OrgId, +} from "@maple/domain/primitives" +import { Array as Arr, Clock, Context, Effect, HashMap, HashSet, Layer, Option, Result, Schema } from "effect" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { makeDbExecute } from "@maple/backend/platform/db-execute" +import { lockOrgAlertRules, makePersistenceError } from "../alert-persistence" +import { + anomalyGroupLabel, + anomalySeverityCode, + assignAnomalyRule, + DEFAULT_ANOMALY_RULE_FALLBACK_NAME, + DEFAULT_ANOMALY_RULE_NAME, + defaultAnomalyRuleFields, + describeAnomaly, + isDefaultAnomalyScope, + storedAnomalyScope, + type AnomalySeverityCode, +} from "./anomaly-rule" + +/** What an `anomaly` alert rule observes for one anomaly incident on a tick. */ +export interface AnomalyAlertObservation { + /** The anomaly incident's id: stable while the detector rewrites its detector key. */ + readonly groupKey: string + readonly value: AnomalySeverityCode + readonly detail: string + /** Null only for a stale alert incident whose anomaly row is gone. */ + readonly anomalyIncidentId: AnomalyIncidentId | null + /** The anomaly incident's own issue, which the alert incident shares. */ + readonly errorIssueId: ErrorIssueId | null + readonly serviceName: string + readonly deploymentEnv: string + readonly signalType: AnomalySignalType | null + /** Shown where a group key would be, e.g. "checkout (production)". */ + readonly groupLabel: string +} + +export interface AnomalyAlertSourceApi { + /** + * The open anomalies assigned to `ruleId`, plus a value-0 observation for each of the rule's + * open alert incidents whose anomaly ended or moved, so the evaluator resolves them without a probe. + */ + readonly observe: ( + orgId: OrgId, + ruleId: AlertRuleId, + ) => Effect.Effect, AlertPersistenceError> + /** + * Creates the default anomaly rule for orgs with an open anomaly and no default rule, at most + * {@link MAX_DEFAULT_RULE_BACKFILL} per call. Idempotent; returns how many it created. + */ + readonly backfillDefaultRules: Effect.Effect +} + +const MAX_DEFAULT_RULE_BACKFILL = 100 +const SYSTEM_USER_ID = Schema.decodeSync(UserId)("system-alerting") +const decodeAnomalyIncidentId = Schema.decodeUnknownOption(AnomalyIncidentId) +const decodeThresholds = Schema.decodeUnknownOption(AlertThresholdsSchema) +const decodeSeverity = Schema.decodeUnknownOption(AlertSeveritySchema) + +/** A rule's lowest tier as a severity code; a row whose tiers do not decode reads its severity. */ +const lowestSeverityCode = (rule: { readonly severity: string; readonly thresholdsJson: unknown }): 1 | 2 => + Option.match(decodeThresholds(rule.thresholdsJson), { + onSome: (tiers) => (Arr.some(tiers, (tier) => anomalySeverityCode(tier.severity) === 1) ? 1 : 2), + onNone: () => + Option.match(decodeSeverity(rule.severity), { + onSome: anomalySeverityCode, + onNone: () => 1 as const, + }), + }) + +/** Names the backfill tries in order, so a user rule holding one never blocks the default. */ +const DEFAULT_RULE_NAMES = [ + DEFAULT_ANOMALY_RULE_NAME, + DEFAULT_ANOMALY_RULE_FALLBACK_NAME, + ...Arr.makeBy(8, (index) => `${DEFAULT_ANOMALY_RULE_NAME} (default ${index + 2})`), +] + +/** + * The alert evaluator's view of the anomaly detector: which open anomalies each anomaly rule + * pages on. Its own service with only the database, like `UptimeAlertSource`. + */ +export class AnomalyAlertSource extends Context.Service()( + "@maple/backend/services/alerts/anomaly/AnomalyAlertSource", + { + make: Effect.gen(function* () { + const database = yield* Database + const dbExecute = makeDbExecute(database, "AnomalyAlertSource", makePersistenceError) + + const observe = Effect.fn("AnomalyAlertSource.observe")(function* ( + orgId: OrgId, + ruleId: AlertRuleId, + ) { + yield* Effect.annotateCurrentSpan({ orgId, "maple.alert.rule_id": ruleId }) + const { rules, anomalies, openAlertIncidents } = yield* dbExecute((db) => + Effect.all({ + rules: db.run( + PG.from(AlertRules) + .select( + "id", + "severity", + "thresholdsJson", + "serviceNamesJson", + "environmentsJson", + "anomalySignalsJson", + ) + .where(($) => [ + $.orgId.eq(orgId), + $.signalType.eq("anomaly"), + $.enabled.eq(true), + ]), + ), + anomalies: db.run( + PG.from(AnomalyIncidents) + .select( + "id", + "signalType", + "serviceName", + "deploymentEnv", + "severity", + "lastObservedValue", + "baselineMedian", + "errorIssueId", + ) + .where(($) => [$.orgId.eq(orgId), $.status.eq("open")]), + ), + // Every anomaly rule's open incidents, so an anomaly stays on the rule paging it. + openAlertIncidents: db.run( + PG.from(AlertIncidents) + .select("ruleId", "groupKey") + .where(($) => [ + $.orgId.eq(orgId), + $.signalType.eq("anomaly"), + $.status.eq("open"), + ]), + ), + }), + ) + const candidates = Arr.map(rules, (rule) => ({ + id: rule.id, + ...storedAnomalyScope(rule), + lowestSeverityCode: lowestSeverityCode(rule), + })) + const pagingRule = HashMap.fromIterable( + Arr.filterMap(openAlertIncidents, (incident) => + incident.groupKey === null + ? Result.failVoid + : Result.succeed([incident.groupKey, incident.ruleId] as const), + ), + ) + const assigned = Arr.filter(anomalies, (incident) => + Option.exists( + assignAnomalyRule( + candidates, + incident, + Option.getOrNull(HashMap.get(pagingRule, incident.id)), + ), + (rule) => rule.id === ruleId, + ), + ) + const firing = Arr.map(assigned, (incident): AnomalyAlertObservation => ({ + groupKey: incident.id, + value: anomalySeverityCode(incident.severity), + detail: describeAnomaly(incident), + anomalyIncidentId: incident.id, + errorIssueId: incident.errorIssueId, + serviceName: incident.serviceName, + deploymentEnv: incident.deploymentEnv, + signalType: incident.signalType, + groupLabel: anomalyGroupLabel(incident), + })) + const firingKeys = HashSet.fromIterable( + Arr.map(firing, (observation) => observation.groupKey), + ) + const endedKeys = Arr.filterMap(openAlertIncidents, (incident) => + incident.ruleId === ruleId && + incident.groupKey !== null && + !HashSet.has(firingKeys, incident.groupKey) + ? Result.succeed(incident.groupKey) + : Result.failVoid, + ) + const endedIds = Arr.filterMap(endedKeys, (key) => + Option.match(decodeAnomalyIncidentId(key), { + onNone: () => Result.failVoid, + onSome: Result.succeed, + }), + ) + // Resolved, or still open but now paged by another rule: either way it ends here. + const endedRows = + endedIds.length === 0 + ? [] + : yield* dbExecute((db) => + db.run( + PG.from(AnomalyIncidents) + .select( + "id", + "signalType", + "serviceName", + "deploymentEnv", + "status", + "errorIssueId", + ) + .where(($) => [$.orgId.eq(orgId), $.id.in_(...endedIds)]), + ), + ) + const endedById = HashMap.fromIterable( + Arr.map(endedRows, (row): readonly [string, (typeof endedRows)[number]] => [row.id, row]), + ) + const ended = Arr.map(endedKeys, (groupKey): AnomalyAlertObservation => { + const row = Option.getOrNull(HashMap.get(endedById, groupKey)) + if (row === null) { + return { + groupKey, + value: 0, + detail: "The anomaly ended", + anomalyIncidentId: null, + errorIssueId: null, + serviceName: "", + deploymentEnv: "", + signalType: null, + groupLabel: "anomaly", + } + } + return { + groupKey, + value: 0, + detail: + row.status === "open" ? "The anomaly moved off this rule" : "The anomaly ended", + anomalyIncidentId: row.id, + errorIssueId: row.errorIssueId, + serviceName: row.serviceName, + deploymentEnv: row.deploymentEnv, + signalType: row.signalType, + groupLabel: anomalyGroupLabel(row), + } + }) + yield* Effect.annotateCurrentSpan({ + "maple.alert.observation_count": firing.length + ended.length, + "maple.anomaly.ended_count": ended.length, + }) + return [...firing, ...ended] + }) + + /** Inserts one org's default rule under the org lock; false when it already has one. */ + const backfillOne = (orgId: OrgId) => + dbExecute((db: MapleDb) => + db.transaction( + Effect.gen(function* () { + yield* lockOrgAlertRules(db, orgId) + const rules = yield* db.run( + PG.from(AlertRules) + .select( + "name", + "signalType", + "serviceNamesJson", + "environmentsJson", + "anomalySignalsJson", + ) + .where(($) => [$.orgId.eq(orgId)]), + ) + const hasDefault = Arr.some( + rules, + (rule) => + rule.signalType === "anomaly" && + isDefaultAnomalyScope(storedAnomalyScope(rule)), + ) + if (hasDefault) return false + const taken = HashSet.fromIterable(Arr.map(rules, (rule) => rule.name)) + const free = Arr.findFirst( + DEFAULT_RULE_NAMES, + (candidate) => !HashSet.has(taken, candidate), + ) + if (Option.isNone(free)) { + yield* Effect.logWarning( + "Default anomaly rule backfill found every default name taken", + ).pipe(Effect.annotateLogs({ orgId })) + return false + } + const name = free.value + const now = yield* Clock.currentTimeMillis + const inserted = yield* db.run( + PG.insertInto(AlertRules) + .values({ + id: AlertRuleId.make(randomUUID()), + ...defaultAnomalyRuleFields(orgId, name), + lastScheduledAt: null, + createdAt: now, + updatedAt: now, + createdBy: SYSTEM_USER_ID, + updatedBy: SYSTEM_USER_ID, + }) + .onConflictDoNothing() + .returning("id"), + ) + return inserted.length > 0 + }), + ), + ) + + const backfillDefaultRules = Effect.gen(function* () { + // System path across orgs: orgs with an open anomaly, then those already defaulted. + const { anomalyOrgs, anomalyRules } = yield* dbExecute((db) => + Effect.all({ + anomalyOrgs: db.run( + PG.from(AnomalyIncidents) + .select("orgId") + .distinct() + .where(($) => [$.status.eq("open")]), + ), + anomalyRules: db.run( + PG.from(AlertRules) + .select("orgId", "serviceNamesJson", "environmentsJson", "anomalySignalsJson") + .where(($) => [$.signalType.eq("anomaly")]), + ), + }), + ) + const defaulted = HashSet.fromIterable( + Arr.filterMap(anomalyRules, (rule) => + isDefaultAnomalyScope(storedAnomalyScope(rule)) + ? Result.succeed(rule.orgId) + : Result.failVoid, + ), + ) + const missing = Arr.take( + Arr.filter(anomalyOrgs, (row) => !HashSet.has(defaulted, row.orgId)), + MAX_DEFAULT_RULE_BACKFILL, + ) + // One org's failure must not stop the rest; it is retried on the next pass. + const created = yield* Effect.forEach(missing, (row) => + backfillOne(row.orgId).pipe( + Effect.catch((error) => + Effect.logWarning("Default anomaly rule backfill skipped an org").pipe( + Effect.annotateLogs({ orgId: row.orgId, error: error.message }), + Effect.as(false), + ), + ), + ), + ) + const count = Arr.filter(created, (made) => made).length + yield* Effect.annotateCurrentSpan("maple.anomaly.default_rules_backfilled", count) + return count + }).pipe(Effect.withSpan("AnomalyAlertSource.backfillDefaultRules")) + + return { observe, backfillDefaultRules } satisfies AnomalyAlertSourceApi + }), + }, +) { + static readonly layer = Layer.effect(this, this.make) +} diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.test.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.test.ts new file mode 100644 index 0000000000..922550ce52 --- /dev/null +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.test.ts @@ -0,0 +1,179 @@ +import { assert, describe, it } from "@effect/vitest" +import { Option } from "effect" +import { + assignAnomalyRule, + anomalyGroupLabel, + describeAnomaly, + type AnomalyIncidentScope, + type AnomalyRuleCandidate, +} from "./anomaly-rule" + +const rule = (id: string, overrides: Partial = {}): AnomalyRuleCandidate => ({ + id, + serviceNames: [], + environments: [], + anomalySignals: [], + lowestSeverityCode: 1, + ...overrides, +}) + +const incident = (overrides: Partial = {}): AnomalyIncidentScope => ({ + serviceName: "checkout", + deploymentEnv: "production", + signalType: "error_rate", + severity: "warning", + ...overrides, +}) + +const assignedId = ( + rules: ReadonlyArray, + scope: AnomalyIncidentScope, + currentRuleId: string | null = null, +) => Option.getOrNull(Option.map(assignAnomalyRule(rules, scope, currentRuleId), (r) => r.id)) + +describe("assignAnomalyRule", () => { + it("gives an incident to the default rule when nothing narrower covers it", () => { + const rules = [rule("default"), rule("payments", { serviceNames: ["payments"] })] + assert.strictEqual(assignedId(rules, incident()), "default") + }) + + it("matches each scope field, with an empty list matching everything", () => { + const byService = rule("svc", { serviceNames: ["checkout"] }) + const byEnv = rule("env", { environments: ["staging"] }) + const bySignal = rule("sig", { anomalySignals: ["latency_p95"] }) + const rules = [byService, byEnv, bySignal] + assert.strictEqual(assignedId(rules, incident()), "svc") + assert.strictEqual( + assignedId(rules, incident({ serviceName: "cart", deploymentEnv: "staging" })), + "env", + ) + assert.strictEqual( + assignedId(rules, incident({ serviceName: "cart", signalType: "latency_p95" })), + "sig", + ) + assert.isNull(assignedId(rules, incident({ serviceName: "cart" }))) + }) + + it("prefers the rule with more scope fields set", () => { + const rules = [ + rule("service-only", { serviceNames: ["checkout"] }), + rule("service-and-env", { serviceNames: ["checkout"], environments: ["production"] }), + rule("all-three", { + serviceNames: ["checkout"], + environments: ["production"], + anomalySignals: ["error_rate"], + }), + rule("default"), + ] + assert.strictEqual(assignedId(rules, incident()), "all-three") + assert.strictEqual(assignedId(rules, incident({ signalType: "throughput" })), "service-and-env") + assert.strictEqual( + assignedId(rules, incident({ signalType: "throughput", deploymentEnv: "staging" })), + "service-only", + ) + }) + + it("breaks a specificity tie on the lowest rule id, whatever the input order", () => { + const b = rule("b-rule", { serviceNames: ["checkout"] }) + const a = rule("a-rule", { environments: ["production"] }) + assert.strictEqual(assignedId([b, a], incident()), "a-rule") + assert.strictEqual(assignedId([a, b], incident()), "a-rule") + }) + + it("passes a warning over a rule with no warning tier", () => { + const criticalOnly = rule("critical-checkout", { serviceNames: ["checkout"], lowestSeverityCode: 2 }) + const rules = [criticalOnly, rule("default")] + assert.strictEqual(assignedId(rules, incident({ severity: "warning" })), "default") + assert.strictEqual(assignedId(rules, incident({ severity: "critical" })), "critical-checkout") + assert.isNull(assignedId([criticalOnly], incident({ severity: "warning" }))) + }) + + it("keeps an anomaly on its paging rule unless a strictly more specific rule qualifies", () => { + const fallback = rule("default") + const byEnv = rule("a-env", { environments: ["production"] }) + const byService = rule("z-checkout", { serviceNames: ["checkout"] }) + const both = rule("both", { serviceNames: ["checkout"], environments: ["production"] }) + const peers = [fallback, byEnv, byService] + // An equally specific rule, even one with a lower id, does not take it. + assert.strictEqual(assignedId(peers, incident(), "z-checkout"), "z-checkout") + // A strictly more specific rule takes it over. + assert.strictEqual(assignedId(peers, incident(), "default"), "a-env") + assert.strictEqual(assignedId([...peers, both], incident(), "z-checkout"), "both") + // A paging rule that no longer qualifies lets the order decide. + const criticalOnly = rule("z-checkout", { serviceNames: ["checkout"], lowestSeverityCode: 2 }) + assert.strictEqual(assignedId([rule("default"), criticalOnly], incident(), "z-checkout"), "default") + assert.strictEqual(assignedId([...peers, both], incident(), "deleted-rule"), "both") + }) + + it("assigns nothing when the org has no anomaly rules", () => { + assert.isNull(assignedId([], incident())) + }) +}) + +describe("anomalyGroupLabel", () => { + it("names the service and environment, or the signal when there is no service", () => { + assert.strictEqual( + anomalyGroupLabel({ + serviceName: "checkout", + deploymentEnv: "production", + signalType: "error_rate", + }), + "checkout (production)", + ) + assert.strictEqual( + anomalyGroupLabel({ serviceName: "", deploymentEnv: "", signalType: "error_spike" }), + "error spike", + ) + }) +}) + +describe("describeAnomaly", () => { + const base = { serviceName: "checkout", deploymentEnv: "production" } + + it("states each signal in its own unit against the baseline", () => { + assert.strictEqual( + describeAnomaly({ + ...base, + signalType: "error_rate", + lastObservedValue: 0.042, + baselineMedian: 0.003, + }), + "error rate 4.2% vs baseline 0.3% in checkout (production)", + ) + assert.strictEqual( + describeAnomaly({ + ...base, + signalType: "latency_p95", + lastObservedValue: 840, + baselineMedian: 210, + }), + "p95 latency 840ms vs baseline 210ms in checkout (production)", + ) + assert.strictEqual( + describeAnomaly({ + ...base, + signalType: "throughput", + lastObservedValue: 3, + baselineMedian: 1200, + }), + "throughput dropped to 3 rpm vs baseline 1,200 rpm in checkout (production)", + ) + assert.strictEqual( + describeAnomaly({ ...base, signalType: "error_spike", lastObservedValue: 42, baselineMedian: 3 }), + "error spike: 42 errors in 30 min vs baseline 3 in checkout (production)", + ) + }) + + it("omits an empty environment", () => { + assert.strictEqual( + describeAnomaly({ + serviceName: "api", + deploymentEnv: "", + signalType: "log_volume", + lastObservedValue: 120, + baselineMedian: 4, + }), + "error log volume 120/min vs baseline 4/min in api", + ) + }) +}) diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts new file mode 100644 index 0000000000..25a3cd8691 --- /dev/null +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -0,0 +1,210 @@ +import { + alertThresholdLegacyFields, + type AlertThresholds, + type AnomalyIncidentSeverity, + type AnomalySignalType, + type OrgId, +} from "@maple/domain/http" +import { Array as Arr, Option, Order } from "effect" +import { formatSignalMetric } from "../alert-formatting" +import type { SignalDisplay } from "../alert-signal-display" + +/** The observed value of an anomaly rule: 0 is no open anomaly, then warning and critical. */ +export type AnomalySeverityCode = 0 | 1 | 2 + +export const anomalySeverityCode = (severity: AnomalyIncidentSeverity): 1 | 2 => + severity === "critical" ? 2 : 1 + +/** The fields every anomaly rule is pinned to: one breach opens, one quiet check resolves. */ +export const ANOMALY_RULE_DEFAULTS = { + comparator: "gte", + windowMinutes: 5, + evaluationIntervalMinutes: 1, + consecutiveBreachesRequired: 1, + breachesOutOf: null, + consecutiveHealthyRequired: 1, + minimumSampleCount: 0, + renotifyIntervalMinutes: 30, +} as const + +export const DEFAULT_ANOMALY_RULE_NAME = "Anomaly detection" +/** Used when a user already took the default name for another rule. */ +export const DEFAULT_ANOMALY_RULE_FALLBACK_NAME = "Anomaly detection (default)" + +export const DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE = + "An org has one default anomaly rule; scope this rule by service, environment or signal" +export const DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE = + "The default anomaly rule must stay enabled; turn anomaly detection off in anomaly settings instead" +export const DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE = + "The default anomaly rule cannot be deleted or disabled; turn anomaly detection off in anomaly settings instead" + +/** What an anomaly rule pages on. Each empty list matches everything. */ +export interface AnomalyRuleScope { + readonly serviceNames: ReadonlyArray + readonly environments: ReadonlyArray + readonly anomalySignals: ReadonlyArray +} + +/** The org default: an anomaly rule with no scope at all. */ +export const isDefaultAnomalyScope = (scope: AnomalyRuleScope): boolean => + scope.serviceNames.length === 0 && scope.environments.length === 0 && scope.anomalySignals.length === 0 + +/** The scope of a stored row, whose scope columns are null when empty. */ +export const storedAnomalyScope = (row: { + readonly serviceNamesJson: ReadonlyArray | null + readonly environmentsJson: ReadonlyArray | null + readonly anomalySignalsJson: ReadonlyArray | null +}): AnomalyRuleScope => ({ + serviceNames: row.serviceNamesJson ?? [], + environments: row.environmentsJson ?? [], + anomalySignals: row.anomalySignalsJson ?? [], +}) + +export interface AnomalyRuleCandidate extends AnomalyRuleScope { + readonly id: string + /** The code of the rule's lowest tier: a critical-only rule (2) has no tier for a warning. */ + readonly lowestSeverityCode: 1 | 2 +} + +export interface AnomalyIncidentScope { + readonly serviceName: string + readonly deploymentEnv: string + readonly signalType: AnomalySignalType + readonly severity: AnomalyIncidentSeverity +} + +const inScope = (values: ReadonlyArray, value: A): boolean => + values.length === 0 || Arr.contains(values, value) + +const covers = (rule: AnomalyRuleScope, incident: AnomalyIncidentScope): boolean => + inScope(rule.serviceNames, incident.serviceName) && + inScope(rule.environments, incident.deploymentEnv) && + inScope(rule.anomalySignals, incident.signalType) + +const specificity = (scope: AnomalyRuleScope): number => + Arr.filter([scope.serviceNames, scope.environments, scope.anomalySignals], (values) => values.length > 0) + .length + +/** Most specific first, then the lowest id, so every tick assigns the same way. */ +const candidateOrder = (): Order.Order => + Order.combine( + Order.mapInput(Order.flip(Order.Number), (rule: R) => specificity(rule)), + Order.mapInput(Order.String, (rule: R) => rule.id), + ) + +/** + * The one rule an open anomaly incident pages through: the most specific enabled rule whose + * scope covers it and that has a tier at or below its severity, ties going to the lowest id. + * An anomaly already paged by `currentRuleId` stays there while that rule still qualifies, + * unless a qualifying rule is strictly more specific; then it hands over. + */ +export const assignAnomalyRule = ( + rules: ReadonlyArray, + incident: AnomalyIncidentScope, + currentRuleId: string | null = null, +): Option.Option => { + const code = anomalySeverityCode(incident.severity) + const qualifying = Arr.sort( + Arr.filter(rules, (rule) => covers(rule, incident) && rule.lowestSeverityCode <= code), + candidateOrder(), + ) + const best = Arr.head(qualifying) + const current = Arr.findFirst(qualifying, (rule) => rule.id === currentRuleId) + if (Option.isNone(current) || Option.isNone(best)) return best + return specificity(best.value) > specificity(current.value) ? best : current +} + +const SIGNAL_NAMES = { + error_rate: "error rate", + latency_p95: "p95 latency", + throughput: "throughput", + log_volume: "error log volume", + error_spike: "error spike", +} satisfies Record + +/** How an anomaly's series is named where a group would be, e.g. "checkout (production)". */ +export const anomalyGroupLabel = (incident: { + readonly serviceName: string + readonly deploymentEnv: string + readonly signalType: AnomalySignalType +}): string => { + const subject = incident.serviceName.length > 0 ? incident.serviceName : SIGNAL_NAMES[incident.signalType] + return incident.deploymentEnv.length > 0 ? `${subject} (${incident.deploymentEnv})` : subject +} + +const metric = (value: number, unit: SignalDisplay["unit"]): string => + formatSignalMetric(value, { label: "", unit }) + +/** One sentence on what an open anomaly is doing, e.g. "error rate 4.2% vs baseline 0.3% in checkout (production)". */ +export const describeAnomaly = (incident: { + readonly signalType: AnomalySignalType + readonly serviceName: string + readonly deploymentEnv: string + readonly lastObservedValue: number + readonly baselineMedian: number +}): string => { + const observed = incident.lastObservedValue + const baseline = incident.baselineMedian + const where = + incident.serviceName.length === 0 + ? "" + : ` in ${incident.serviceName}${incident.deploymentEnv.length > 0 ? ` (${incident.deploymentEnv})` : ""}` + switch (incident.signalType) { + case "error_rate": + return `error rate ${metric(observed, "ratio")} vs baseline ${metric(baseline, "ratio")}${where}` + case "latency_p95": + return `p95 latency ${metric(observed, "ms")} vs baseline ${metric(baseline, "ms")}${where}` + case "throughput": + return `throughput ${observed < baseline ? "dropped" : "rose"} to ${metric(observed, "rpm")} vs baseline ${metric(baseline, "rpm")}${where}` + case "log_volume": + return `error log volume ${metric(observed, "count")}/min vs baseline ${metric(baseline, "count")}/min${where}` + case "error_spike": + return `error spike: ${metric(Math.round(observed), "count")} errors in 30 min vs baseline ${metric(baseline, "count")}${where}` + } +} + +/** The org default anomaly rule's `alert_rules` columns: both tiers, unrouted until a user adds destinations. */ +export const defaultAnomalyRuleFields = (orgId: OrgId, name: string = DEFAULT_ANOMALY_RULE_NAME) => { + const thresholds: AlertThresholds = [ + { severity: "warning", value: 1, upper: null, recoveryValue: null, destinationIds: [] }, + { severity: "critical", value: 2, upper: null, recoveryValue: null, destinationIds: [] }, + ] + const legacy = alertThresholdLegacyFields(thresholds) + return { + orgId, + name, + notes: null, + notificationTemplateJson: null, + enabled: true, + severity: legacy.severity, + serviceNamesJson: null, + excludeServiceNamesJson: null, + environmentsJson: null, + anomalySignalsJson: null, + tagsJson: null, + groupBy: null, + signalType: "anomaly", + comparator: ANOMALY_RULE_DEFAULTS.comparator, + threshold: legacy.threshold, + thresholdUpper: legacy.thresholdUpper, + thresholdsJson: thresholds, + windowMinutes: ANOMALY_RULE_DEFAULTS.windowMinutes, + evaluationIntervalMinutes: ANOMALY_RULE_DEFAULTS.evaluationIntervalMinutes, + slowedIntervalMinutes: null, + slowedAt: null, + minimumSampleCount: ANOMALY_RULE_DEFAULTS.minimumSampleCount, + consecutiveBreachesRequired: ANOMALY_RULE_DEFAULTS.consecutiveBreachesRequired, + breachesOutOf: ANOMALY_RULE_DEFAULTS.breachesOutOf, + consecutiveHealthyRequired: ANOMALY_RULE_DEFAULTS.consecutiveHealthyRequired, + renotifyIntervalMinutes: ANOMALY_RULE_DEFAULTS.renotifyIntervalMinutes, + apdexThresholdMs: null, + queryBuilderDraftJson: null, + rawQuerySql: null, + destinationIdsJson: [...legacy.destinationIds], + // The queryless plan of `MANAGED_RULE_PLAN`, spelled out to keep this module free of the model. + querySpecJson: null, + reducer: "identity", + sampleCountStrategy: null, + noDataBehavior: "skip", + } as const +} diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts new file mode 100644 index 0000000000..591b7dce83 --- /dev/null +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts @@ -0,0 +1,311 @@ +import { afterEach, assert, describe, it } from "@effect/vitest" +import { + AlertDestinationId, + AlertRuleId, + AlertRuleUpsertRequest, + OrgId, + RoleName, + UserId, +} from "@maple/domain/http" +import { Array as Arr, Effect, Option, Schema, String as Str } from "effect" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { cleanupTestDbs, createTestDb, executeSql, type TestDb } from "@maple/backend/platform/test-pglite" +import { makeAlertRuleNormalizer } from "../AlertRuleModel" +import { makeAlertRulePersistence } from "../AlertRulesService" +import type { AlertRuntimeApi } from "../AlertRuntime" +import { + DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE, + DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, + DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, +} from "./anomaly-rule" + +const asOrgId = Schema.decodeUnknownSync(OrgId) +const asUserId = Schema.decodeUnknownSync(UserId) +const asRoleName = Schema.decodeUnknownSync(RoleName) +const asRuleId = Schema.decodeUnknownSync(AlertRuleId) + +const ORG = asOrgId("org_anomaly_rules") +const USER = asUserId("user_anomaly_rules") +const ADMIN_ROLES = [asRoleName("org:admin")] +const NOW = Date.parse("2026-11-06T12:00:00.000Z") +const DESTINATION_ID = Schema.decodeSync(AlertDestinationId)("00000000-0000-4000-8000-0000000000d1") + +const createdDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(createdDbs)) + +/** A runtime handing out sequential rule ids, so several rules can be created in one test. */ +const makeRuntime = Effect.sync((): AlertRuntimeApi => { + let next = 0 + return { + now: Effect.succeed(NOW), + makeUuid: () => asRuleId(`00000000-0000-4000-8000-${Str.padStart(12, "0")(String(100 + next++))}`), + deliveryTimeoutMs: () => 15_000, + schedulerTickBudgetMs: () => 25_000, + schedulerEvalTimeoutMs: () => 15_000, + schedulerSpreadMs: () => 0, + } +}) + +const anomalyRequest = (fields: Partial[0]> = {}) => + new AlertRuleUpsertRequest({ + name: "Anomalies", + signalType: "anomaly", + comparator: "lt", + windowMinutes: 5, + ...fields, + }) + +describe("anomaly rule normalization", () => { + it.effect("pins the evaluation fields and turns each tier's value into its severity code", () => + Effect.gen(function* () { + const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) + const rule = yield* normalizeRule( + ORG, + anomalyRequest({ + serviceNames: ["checkout"], + environments: ["production"], + anomalySignals: ["error_rate", "error_rate", "latency_p95"], + excludeServiceNames: ["cart"], + thresholds: [ + { + severity: "warning", + value: 40, + upper: null, + recoveryValue: 10, + destinationIds: [], + }, + { + severity: "critical", + value: 90, + upper: null, + recoveryValue: null, + destinationIds: [], + }, + ], + consecutiveBreachesRequired: 4, + consecutiveHealthyRequired: 5, + minimumSampleCount: 50, + alertOnNoData: true, + windowMinutes: 60, + evaluationIntervalMinutes: 10, + }), + ) + assert.strictEqual(rule.comparator, "gte") + assert.deepStrictEqual( + Arr.map(rule.thresholds, (tier) => [ + tier.severity, + tier.value, + tier.upper, + tier.recoveryValue, + ]), + [ + ["warning", 1, null, null], + ["critical", 2, null, null], + ], + ) + assert.deepStrictEqual(rule.anomalySignals, ["error_rate", "latency_p95"]) + assert.deepStrictEqual(rule.serviceNames, ["checkout"]) + assert.deepStrictEqual(rule.environments, ["production"]) + assert.deepStrictEqual(rule.excludeServiceNames, []) + assert.isNull(rule.groupBy) + assert.strictEqual(rule.consecutiveBreachesRequired, 1) + assert.isNull(rule.breachesOutOf) + assert.strictEqual(rule.consecutiveHealthyRequired, 1) + assert.strictEqual(rule.minimumSampleCount, 0) + assert.isFalse(rule.alertOnNoData) + assert.strictEqual(rule.evaluationIntervalMinutes, 1) + assert.strictEqual(rule.windowMinutes, 5) + // No query: the evaluator reads the detector's incidents. + assert.isNull(rule.compiledPlan.query) + assert.isNull(rule.compiledPlan.rawSql) + }), + ) + + it.effect("accepts tiers with no destinations, so an unrouted rule only shows in the app", () => + Effect.gen(function* () { + const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) + const rule = yield* normalizeRule(ORG, anomalyRequest({ severity: "critical" })) + assert.deepStrictEqual(rule.destinationIds, []) + assert.deepStrictEqual( + Arr.map(rule.thresholds, (tier) => [tier.severity, tier.value]), + [["critical", 2]], + ) + }), + ) + + it.effect("rejects anomalySignals on any other signal type", () => + Effect.gen(function* () { + const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) + const error = yield* Effect.flip( + normalizeRule( + ORG, + new AlertRuleUpsertRequest({ + name: "Errors", + signalType: "error_rate", + comparator: "gt", + threshold: 0.05, + severity: "warning", + windowMinutes: 5, + anomalySignals: ["error_rate"], + destinationIds: [], + }), + { forPreview: true }, + ), + ) + assert.strictEqual(error._tag, "@maple/http/errors/AlertValidationError") + assert.include(error.message, "anomalySignals is only supported for anomaly alerts") + }), + ) +}) + +describe("default anomaly rule", () => { + const makePersistence = Effect.gen(function* () { + const database = yield* Database + return makeAlertRulePersistence({ database, runtime: yield* makeRuntime }) + }) + + it.effect("allows one unscoped anomaly rule per org, and any number of scoped ones", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + const rules = yield* makePersistence + const first = yield* rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Default" })) + assert.strictEqual(first.signalType, "anomaly") + assert.deepStrictEqual(first.anomalySignals, []) + + const second = yield* Effect.flip( + rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Second default" })), + ) + assert.strictEqual(second._tag, "@maple/http/errors/AlertValidationError") + assert.strictEqual(second.message, DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE) + + const scoped = yield* rules.createRule( + ORG, + USER, + ADMIN_ROLES, + anomalyRequest({ name: "Latency only", anomalySignals: ["latency_p95"] }), + ) + const listed = yield* rules.listRules(ORG) + const stored = Arr.findFirst(listed.rules, (rule) => rule.id === scoped.id) + assert.deepStrictEqual(Option.getOrNull(Option.map(stored, (rule) => rule.anomalySignals)), [ + "latency_p95", + ]) + }).pipe(Effect.provide(testDb.layer)) + }) + + it.effect("refuses a disabled unscoped rule, which would hold the default slot", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + const rules = yield* makePersistence + const error = yield* Effect.flip( + rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Paused", enabled: false })), + ) + assert.strictEqual(error.message, DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE) + assert.deepStrictEqual((yield* rules.listRules(ORG)).rules, []) + }).pipe(Effect.provide(testDb.layer)) + }) + + it.effect("never counts the default rule against the active rule cap, whatever rule is written", () => { + const testDb = createTestDb(createdDbs) + const errorRateRule = (name: string) => + new AlertRuleUpsertRequest({ + name, + signalType: "error_rate", + comparator: "gt", + threshold: 0.05, + severity: "warning", + windowMinutes: 5, + destinationIds: [DESTINATION_ID], + }) + return Effect.gen(function* () { + yield* Effect.promise(() => + executeSql( + testDb, + `insert into alert_destinations (id, org_id, name, type, config_json, secret_ciphertext, secret_iv, secret_tag, created_at, updated_at, created_by, updated_by) + values ($1, $2, 'Hook', 'webhook', '{}'::jsonb, '', '', '', now(), now(), 'u', 'u')`, + [DESTINATION_ID, ORG], + ), + ) + const rules = yield* makePersistence + yield* rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Default" })) + const template = yield* rules.createRule(ORG, USER, ADMIN_ROLES, errorRateRule("r0")) + // 98 more copies of it: 99 counted rules beside the default. + yield* Effect.promise(() => + executeSql( + testDb, + `insert into alert_rules + select (jsonb_populate_record(r, jsonb_build_object('id', gen_random_uuid()::text, 'name', 'r' || n))).* + from alert_rules r, generate_series(1, 98) n where r.id = $1`, + [template.id], + ), + ) + + const hundredth = yield* rules.createRule(ORG, USER, ADMIN_ROLES, errorRateRule("r99")) + assert.strictEqual(hundredth.name, "r99") + const over = yield* Effect.flip(rules.createRule(ORG, USER, ADMIN_ROLES, errorRateRule("r100"))) + assert.include(over.message, "at most 100 active alert rules") + }).pipe(Effect.provide(testDb.layer)) + }) + + it.effect("cannot be deleted, disabled or scoped, but other fields can change", () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + const rules = yield* makePersistence + const created = yield* rules.createRule( + ORG, + USER, + ADMIN_ROLES, + anomalyRequest({ name: "Default" }), + ) + + const deleted = yield* Effect.flip(rules.deleteRule(ORG, ADMIN_ROLES, created.id)) + assert.strictEqual(deleted._tag, "@maple/http/errors/AlertValidationError") + assert.strictEqual(deleted.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + + const disabled = yield* Effect.flip( + rules.upsertRuleRow( + ORG, + USER, + created.id, + anomalyRequest({ name: "Default", enabled: false }), + ), + ) + assert.strictEqual(disabled.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + + const scoped = yield* Effect.flip( + rules.upsertRuleRow( + ORG, + USER, + created.id, + anomalyRequest({ name: "Default", serviceNames: ["api"] }), + ), + ) + assert.strictEqual(scoped.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + + const renamed = yield* rules.upsertRuleRow( + ORG, + USER, + created.id, + anomalyRequest({ name: "Everything anomalous", renotifyIntervalMinutes: 60 }), + ) + assert.strictEqual(renamed.name, "Everything anomalous") + assert.isTrue(renamed.enabled) + + // A scoped anomaly rule is an ordinary rule: it can be disabled and deleted. + const scopedRule = yield* rules.createRule( + ORG, + USER, + ADMIN_ROLES, + anomalyRequest({ name: "Checkout", serviceNames: ["checkout"] }), + ) + const paused = yield* rules.upsertRuleRow( + ORG, + USER, + scopedRule.id, + anomalyRequest({ name: "Checkout", serviceNames: ["checkout"], enabled: false }), + ) + assert.isFalse(paused.enabled) + const gone = yield* rules.deleteRule(ORG, ADMIN_ROLES, scopedRule.id) + assert.strictEqual(gone.id, scopedRule.id) + }).pipe(Effect.provide(testDb.layer)) + }) +}) diff --git a/packages/backend/src/services/alerts/delivery/chat-digest.ts b/packages/backend/src/services/alerts/delivery/chat-digest.ts index a3d3d13408..93e1b23c9d 100644 --- a/packages/backend/src/services/alerts/delivery/chat-digest.ts +++ b/packages/backend/src/services/alerts/delivery/chat-digest.ts @@ -2,7 +2,7 @@ import type { ChatBlock } from "@maple/chat-platform" import type { AlertSeverity } from "@maple/domain/http" import { alertAccentColor, - displayGroupKey, + displayGroup, eventTypeEmoji, formatEventTypeLabel, formatSeverityLabel, @@ -22,6 +22,9 @@ export interface ChatDigestItem { readonly eventType: string readonly severity: AlertSeverity readonly groupKey: string | null + /** Shown in place of the group key when set, e.g. an anomaly's "checkout (production)". */ + readonly groupLabel?: string | null + readonly signalType?: string readonly linkUrl: string } @@ -29,7 +32,7 @@ export interface ChatDigestItem { const linkText = (value: string) => Str.replace(/[[\]]/g, "")(truncate(value, 80)) const itemLine = (item: ChatDigestItem): string => { - const group = displayGroupKey(item.groupKey) + const group = displayGroup(item) return Arr.join( [ `${eventTypeEmoji(item.eventType)} [${linkText(item.ruleName)}](${item.linkUrl})`, diff --git a/packages/backend/src/services/alerts/delivery/context.ts b/packages/backend/src/services/alerts/delivery/context.ts index b113115136..58f223ec2b 100644 --- a/packages/backend/src/services/alerts/delivery/context.ts +++ b/packages/backend/src/services/alerts/delivery/context.ts @@ -29,6 +29,8 @@ export interface DispatchContext { readonly ruleId: string readonly ruleName: string readonly groupKey: string | null + /** Shown in place of the group key when set, e.g. an anomaly's "checkout (production)". */ + readonly groupLabel?: string | null readonly signalType: AlertSignalType /** * How this rule's measured quantity is named and unit-formatted. Resolved diff --git a/packages/backend/src/services/alerts/delivery/stored-payload.ts b/packages/backend/src/services/alerts/delivery/stored-payload.ts index 753a46578f..284aecee4c 100644 --- a/packages/backend/src/services/alerts/delivery/stored-payload.ts +++ b/packages/backend/src/services/alerts/delivery/stored-payload.ts @@ -19,6 +19,7 @@ export const StoredDeliveryPayloadSchema = Schema.StructWithRest( signalType: Schema.optionalKey(Schema.String), severity: Schema.optionalKey(Schema.String), groupKey: Schema.optionalKey(Schema.NullOr(Schema.String)), + groupLabel: Schema.optionalKey(Schema.NullOr(Schema.String)), comparator: Schema.optionalKey(Schema.String), threshold: Schema.optionalKey(Schema.Number), thresholdUpper: Schema.optionalKey(Schema.NullOr(Schema.Number)), diff --git a/packages/backend/src/services/alerts/delivery/transports/chat.ts b/packages/backend/src/services/alerts/delivery/transports/chat.ts index d149df76a2..789b0b42dd 100644 --- a/packages/backend/src/services/alerts/delivery/transports/chat.ts +++ b/packages/backend/src/services/alerts/delivery/transports/chat.ts @@ -2,7 +2,7 @@ import type { ChatAlertBlock, ChatBlock } from "@maple/chat-platform" import { Effect } from "effect" import { buildSummaryLine } from "../../AlertDeliveryDispatch" import { - displayGroupKey, + displayGroup, eventTypeEmoji, formatAlertHeadline, formatSeverityLabel, @@ -63,7 +63,7 @@ const ackControl = (context: ChatAlertCardInput["context"]): Pick => { const { context, templated, linkUrl } = input - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) const card: Omit = { ...ackControl(context), kind: "alert", diff --git a/packages/backend/src/services/alerts/delivery/transports/discord.ts b/packages/backend/src/services/alerts/delivery/transports/discord.ts index efb301b7b0..0f845c1a65 100644 --- a/packages/backend/src/services/alerts/delivery/transports/discord.ts +++ b/packages/backend/src/services/alerts/delivery/transports/discord.ts @@ -1,6 +1,6 @@ import { Schema } from "effect" import { buildDiscordEmbeds, buildDiscordEmbedsFromTemplate } from "../../AlertDeliveryDispatch" -import { formatAlertHeadline, formatEventTypeLabel } from "../../alert-formatting" +import { formatAlertHeadline, formatEventTypeLabel, hasSignalHeadline } from "../../alert-formatting" import type { HttpTransport, RenderInput, SecretConfigOf } from "../Transport" const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)) @@ -28,7 +28,7 @@ export const discordTransport: HttpTransport = { username: "Maple Alerts", content: templated?.title ?? - (context.signalType === "uptime" + (hasSignalHeadline(context.signalType) ? `**${formatAlertHeadline(context)}**` : `**${context.ruleName}**: ${formatEventTypeLabel(context.eventType)}`), embeds, diff --git a/packages/backend/src/services/alerts/delivery/transports/google-chat.ts b/packages/backend/src/services/alerts/delivery/transports/google-chat.ts index 40f2f2a84e..3954b083ec 100644 --- a/packages/backend/src/services/alerts/delivery/transports/google-chat.ts +++ b/packages/backend/src/services/alerts/delivery/transports/google-chat.ts @@ -3,7 +3,7 @@ import { replaceWith } from "@maple/strings" import { buildSummaryLine } from "../../AlertDeliveryDispatch" import { alertAccentColor, - displayGroupKey, + displayGroup, eventTypeEmoji, formatAlertHeadline, formatSeverityLabel, @@ -63,7 +63,7 @@ const threadedUrl = (webhookUrl: string): string => export const buildGoogleChatMessage = (input: RenderInput) => { const { context, templated, linkUrl, chatUrl } = input const accent = alertAccentColor(context.eventType, context.severity) - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) const title = templated?.title ?? `${eventTypeEmoji(context.eventType)} ${formatAlertHeadline(context)}` const lead = templated ? markdownToCardHtml(truncate(templated.body, MAX_TEMPLATED_BODY_CHARS)) diff --git a/packages/backend/src/services/alerts/delivery/transports/incident-io.ts b/packages/backend/src/services/alerts/delivery/transports/incident-io.ts index 253a136e39..04c5d7eb8f 100644 --- a/packages/backend/src/services/alerts/delivery/transports/incident-io.ts +++ b/packages/backend/src/services/alerts/delivery/transports/incident-io.ts @@ -1,11 +1,12 @@ import { Array as Arr, Schema, String as Str } from "effect" import { buildSummaryLine } from "../../AlertDeliveryDispatch" import { - displayGroupKey, + displayGroup, formatAlertHeadline, formatEventTypeLabel, formatSeverityLabel, formatWindow, + hasSignalHeadline, truncate, } from "../../alert-formatting" import type { DispatchContext } from "../context" @@ -35,12 +36,12 @@ const resolveEvent = (title: string, dedupKey: string) => ({ }) const defaultTitle = (context: DispatchContext): string => - context.signalType === "uptime" + hasSignalHeadline(context.signalType) ? formatAlertHeadline(context) : `${context.ruleName} ${Str.toLowerCase(formatEventTypeLabel(context.eventType))}` const defaultDescription = (context: DispatchContext, linkUrl: string, chatUrl: string): string => { - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) return Arr.join( [ buildSummaryLine(context, (value) => `**${value}**`), diff --git a/packages/backend/src/services/alerts/delivery/transports/linear.ts b/packages/backend/src/services/alerts/delivery/transports/linear.ts index b59b893e57..bce3adf476 100644 --- a/packages/backend/src/services/alerts/delivery/transports/linear.ts +++ b/packages/backend/src/services/alerts/delivery/transports/linear.ts @@ -6,7 +6,7 @@ import { createHash } from "node:crypto" import { type AlertDeliveryFailure, AlertDeliveryTargetMissingError } from "@maple/domain/http" import { Clock, Effect, Order, Schema, Array as Arr, String as Str } from "effect" import { buildSummaryLine } from "../../AlertDeliveryDispatch" -import { displayGroupKey, formatAlertHeadline, formatSeverityLabel, truncate } from "../../alert-formatting" +import { displayGroup, formatAlertHeadline, formatSeverityLabel, truncate } from "../../alert-formatting" import { timestampMs } from "@maple/backend/platform/time" import type { DispatchResult } from "../context" import { makeDeliveryError } from "../runTransport" @@ -49,7 +49,7 @@ export const linearIssueTitle = (input: Input): string => export const linearIssueDescription = (input: Input): string => { const { context } = input - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) const lines = [ input.templated?.body ?? buildSummaryLine(context, (value) => `**${value}**`), "", diff --git a/packages/backend/src/services/alerts/delivery/transports/microsoft-teams.ts b/packages/backend/src/services/alerts/delivery/transports/microsoft-teams.ts index 682043b517..4eb56c8240 100644 --- a/packages/backend/src/services/alerts/delivery/transports/microsoft-teams.ts +++ b/packages/backend/src/services/alerts/delivery/transports/microsoft-teams.ts @@ -1,7 +1,7 @@ import { Array as Arr, Schema, String as Str } from "effect" import { buildSummaryLine } from "../../AlertDeliveryDispatch" import { - displayGroupKey, + displayGroup, eventTypeEmoji, formatAlertHeadline, formatEventTypeLabel, @@ -39,7 +39,7 @@ const textBlock = (text: string, extra?: Record) => ({ export const buildTeamsAdaptiveCard = (input: RenderInput) => { const { context, templated, linkUrl, chatUrl } = input - const group = displayGroupKey(context.groupKey) + const group = displayGroup(context) const title = templated?.title ?? `${eventTypeEmoji(context.eventType)} ${formatAlertHeadline(context)}` const lead = templated ? truncate(templated.body, MAX_TEMPLATED_BODY_CHARS) diff --git a/packages/backend/src/services/alerts/delivery/transports/pagerduty.ts b/packages/backend/src/services/alerts/delivery/transports/pagerduty.ts index a2cac0ac52..a43c4da13f 100644 --- a/packages/backend/src/services/alerts/delivery/transports/pagerduty.ts +++ b/packages/backend/src/services/alerts/delivery/transports/pagerduty.ts @@ -1,6 +1,6 @@ import { Duration, Effect, Schema, Array as Arr, String as Str } from "effect" import { HttpClient, HttpClientRequest } from "effect/http" -import { displayGroupKey, formatAlertHeadline, truncate, uptimeSummaryLine } from "../../alert-formatting" +import { displayGroup, formatAlertHeadline, truncate, uptimeSummaryLine } from "../../alert-formatting" import type { DispatchContext } from "../context" import { closesCriticalKey, @@ -78,10 +78,12 @@ export const pagerDutyTransport: HttpTransport = { templated?.title ?? (context.signalType === "uptime" ? `${formatAlertHeadline(context)}: ${uptimeSummaryLine(context)}` - : `${context.ruleName} ${context.eventType}`), + : context.signalType === "anomaly" + ? formatAlertHeadline(context) + : `${context.ruleName} ${context.eventType}`), 1024, ), - source: displayGroupKey(context.groupKey) ?? "maple-alerts", + source: displayGroup(context) ?? "maple-alerts", // The tier the key was chosen for, so a stale critical event cannot raise the warning alert. severity: keySeverity(context) === "critical" ? "critical" : "warning", custom_details: { diff --git a/packages/backend/src/services/push/MobilePushService.test.ts b/packages/backend/src/services/push/MobilePushService.test.ts index e463091958..b441144429 100644 --- a/packages/backend/src/services/push/MobilePushService.test.ts +++ b/packages/backend/src/services/push/MobilePushService.test.ts @@ -163,6 +163,26 @@ describe("renderIncidentPush", () => { expect(rendered.priority).toBe(10) }) + it("puts an anomaly's own sentence in the body, not its severity code", () => { + const anomaly = { + signalType: "anomaly" as const, + signalDisplay: { label: "Anomaly", unit: "plain" as const }, + comparator: "gte" as const, + threshold: 2, + value: 2, + detail: "error rate 4.2% vs baseline 0.3% in checkout (production)", + } + expect(renderIncidentPush(event(anomaly)).alert).toEqual({ + title: "Critical · Checkout error rate", + subtitle: "checkout-api", + body: "Error rate 4.2% vs baseline 0.3% in checkout (production).", + }) + const resolved = renderIncidentPush( + event({ ...anomaly, eventType: "resolve", value: 0, openForMs: 12 * 60_000 }), + ) + expect(resolved.alert.body).toBe("The anomaly ended after 12m.") + }) + it("keeps warnings out of Focus, and resolutions quiet and silent", () => { expect(renderIncidentPush(event({ severity: "warning" })).interruptionLevel).toBe("active") const resolved = renderIncidentPush( diff --git a/packages/backend/src/services/push/MobilePushService.ts b/packages/backend/src/services/push/MobilePushService.ts index 48b680108f..74781ed68b 100644 --- a/packages/backend/src/services/push/MobilePushService.ts +++ b/packages/backend/src/services/push/MobilePushService.ts @@ -11,13 +11,14 @@ import { Cause, Clock, Context, Duration, Effect, Layer, Array as Arr, String as import { ApnsClient, type ApnsPush } from "@maple/backend/platform/Apns" import { summarizeCause } from "@maple/backend/platform/describe-cause" import { - displayGroupKey, + displayGroup, formatObservedSummary, formatSignalMetric, formatThresholdSummary, formatWindow, truncate, uptimeSummaryLine, + anomalySummaryLine, } from "@maple/backend/services/alerts/alert-formatting" import type { SignalDisplay } from "@maple/backend/services/alerts/alert-signal-display" import { LiveActivitiesService, type LiveActivity } from "./LiveActivitiesService" @@ -82,6 +83,8 @@ export interface IncidentPushEvent { /** Uptime rules only: the failing check's error. */ readonly detail?: string | null readonly groupKey: string | null + /** Shown in place of the group key when set, e.g. an anomaly's "checkout (production)". */ + readonly groupLabel?: string | null readonly serviceNames: ReadonlyArray readonly windowMinutes: number readonly dedupeKey: string @@ -268,7 +271,7 @@ export const renderIncidentPush = ( ): Pick => { const [firstService] = event.serviceNames const service = - displayGroupKey(event.groupKey) ?? + displayGroup(event) ?? (firstService === undefined ? null : event.serviceNames.length === 1 @@ -314,6 +317,19 @@ export const renderIncidentPush = ( } } + // An anomaly's value is a severity code: the detector's sentence is the body. + if (event.signalType === "anomaly" && event.eventType !== "resolve") { + return { + alert: { + title: `${event.eventType === "renotify" ? `Still ${Str.toLowerCase(severity)}` : severity} · ${rule}`, + subtitle: service ?? undefined, + body: anomalySummaryLine(event), + }, + interruptionLevel: event.severity === "critical" ? "time-sensitive" : "active", + priority: 10, + } + } + switch (event.eventType) { case "resolve": { const now = formatSignalMetric(event.value, event.signalDisplay) @@ -321,7 +337,9 @@ export const renderIncidentPush = ( const held = event.resolvedAfterHold ?? null const body = held === null - ? `${label} back to ${now}${after}.` + ? event.signalType === "anomaly" + ? `The anomaly ended${after}.` + : `${label} back to ${now}${after}.` : `${describeHoldForPush(held.reason)} for ${humanDuration(held.heldForMs)} with no breach observed; resolved${after}.` return { alert: { @@ -418,7 +436,7 @@ export const renderLiveActivityAttributes = ( incident_id: encodePublicId(PublicIdPrefixes.alertIncident, event.incidentId), rule_name: truncate(event.ruleName, 60), service: - displayGroupKey(event.groupKey) ?? + displayGroup(event) ?? (event.serviceNames.length === 1 ? event.serviceNames[0] : event.serviceNames.length > 1 diff --git a/packages/db/drizzle/20261106175508_alerts_anomaly_rules/migration.sql b/packages/db/drizzle/20261106175508_alerts_anomaly_rules/migration.sql new file mode 100644 index 0000000000..3d96ba8ad1 --- /dev/null +++ b/packages/db/drizzle/20261106175508_alerts_anomaly_rules/migration.sql @@ -0,0 +1,8 @@ +-- metadata +ALTER TABLE "alert_incidents" ADD COLUMN IF NOT EXISTS "anomaly_incident_id" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "alert_incidents" ADD COLUMN IF NOT EXISTS "group_label" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "alert_rules" ADD COLUMN IF NOT EXISTS "anomaly_signals_json" jsonb; diff --git a/packages/db/drizzle/20261106175508_alerts_anomaly_rules/snapshot.json b/packages/db/drizzle/20261106175508_alerts_anomaly_rules/snapshot.json new file mode 100644 index 0000000000..1ca655294c --- /dev/null +++ b/packages/db/drizzle/20261106175508_alerts_anomaly_rules/snapshot.json @@ -0,0 +1,19183 @@ +{ + "version": "1", + "dialect": "postgres", + "id": "0d481e0651366d2136953368edbbb681a0a2beb2c4576faa4cfdf4801e3f867d", + "prevIds": [ + "c17d814b80b3c744f9918aeef7c5edd59881efdcf959113f7259615b0b8bc7b5" + ], + "entities": [ + { + "kind": "table", + "name": "actors", + "primaryKey": { + "name": "actors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "agent_feedback", + "primaryKey": { + "name": "agent_feedback_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "ai_triage_settings", + "primaryKey": { + "name": "ai_triage_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "alert_delivery_events", + "primaryKey": { + "name": "alert_delivery_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_destinations", + "primaryKey": { + "name": "alert_destinations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incident_events", + "primaryKey": { + "name": "alert_incident_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incidents", + "primaryKey": { + "name": "alert_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_linear_issues", + "primaryKey": { + "name": "alert_linear_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_claims", + "primaryKey": { + "name": "alert_rule_claims_pkey", + "columns": [ + "rule_id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_states", + "primaryKey": { + "name": "alert_rule_states_org_id_rule_id_group_key_pk", + "columns": [ + "org_id", + "rule_id", + "group_key" + ] + } + }, + { + "kind": "table", + "name": "alert_rules", + "primaryKey": { + "name": "alert_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_settings", + "primaryKey": { + "name": "anomaly_detector_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_states", + "primaryKey": { + "name": "anomaly_detector_states_org_id_detector_key_pk", + "columns": [ + "org_id", + "detector_key" + ] + } + }, + { + "kind": "table", + "name": "anomaly_incidents", + "primaryKey": { + "name": "anomaly_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "api_keys", + "primaryKey": { + "name": "api_keys_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "aws_connections", + "primaryKey": { + "name": "aws_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cancellation_reviews", + "primaryKey": { + "name": "cancellation_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_identities", + "primaryKey": { + "name": "chat_identities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_workspaces", + "primaryKey": { + "name": "chat_workspaces_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cli_device_authorizations", + "primaryKey": { + "name": "cli_device_authorizations_pkey", + "columns": [ + "device_code_hash" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_analytics_state", + "primaryKey": { + "name": "cloudflare_analytics_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_hyperdrive_configs", + "primaryKey": { + "name": "cloudflare_hyperdrive_configs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_logpush_connectors", + "primaryKey": { + "name": "cloudflare_logpush_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_zone_settings", + "primaryKey": { + "name": "cloudflare_zone_settings_pkey", + "columns": [ + "org_id", + "account_id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_shares", + "primaryKey": { + "name": "dashboard_shares_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_versions", + "primaryKey": { + "name": "dashboard_versions_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboards", + "primaryKey": { + "name": "dashboards_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_findings", + "primaryKey": { + "name": "dependency_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scan_matches", + "primaryKey": { + "name": "dependency_scan_matches_pkey", + "columns": [ + "scan_id", + "lockfile_path", + "ecosystem", + "package_name", + "version", + "advisory_id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scans", + "primaryKey": { + "name": "dependency_scans_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_credentials", + "primaryKey": { + "name": "device_credentials_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_proof_replays", + "primaryKey": { + "name": "device_proof_replays_pkey", + "columns": [ + "key" + ] + } + }, + { + "kind": "table", + "name": "digest_subscriptions", + "primaryKey": { + "name": "digest_subscriptions_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_fingerprint_candidates", + "primaryKey": { + "name": "error_fingerprint_candidates_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_incidents", + "primaryKey": { + "name": "error_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_events", + "primaryKey": { + "name": "error_issue_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_fingerprints", + "primaryKey": { + "name": "error_issue_fingerprints_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_issue_pull_requests", + "primaryKey": { + "name": "error_issue_pull_requests_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_states", + "primaryKey": { + "name": "error_issue_states_org_id_issue_id_pk", + "columns": [ + "org_id", + "issue_id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_verifications", + "primaryKey": { + "name": "error_issue_verifications_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issues", + "primaryKey": { + "name": "error_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_deliveries", + "primaryKey": { + "name": "error_notification_deliveries_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_policies", + "primaryKey": { + "name": "error_notification_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "error_tick_states", + "primaryKey": { + "name": "error_tick_states_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "gcp_connectors", + "primaryKey": { + "name": "gcp_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "gcp_resources", + "primaryKey": { + "name": "gcp_resources_pkey", + "columns": [ + "connector_id", + "name" + ] + } + }, + { + "kind": "table", + "name": "investigations", + "primaryKey": { + "name": "investigations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalation_policies", + "primaryKey": { + "name": "issue_escalation_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalations", + "primaryKey": { + "name": "issue_escalations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "live_activities", + "primaryKey": { + "name": "live_activities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_authorizations", + "primaryKey": { + "name": "mcp_oauth_authorizations_pkey", + "columns": [ + "request_id_hash" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_clients", + "primaryKey": { + "name": "mcp_oauth_clients_pkey", + "columns": [ + "client_id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_refresh_tokens", + "primaryKey": { + "name": "mcp_oauth_refresh_tokens_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mobile_devices", + "primaryKey": { + "name": "mobile_devices_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oauth_auth_states", + "primaryKey": { + "name": "oauth_auth_states_pkey", + "columns": [ + "state" + ] + } + }, + { + "kind": "table", + "name": "oauth_connections", + "primaryKey": { + "name": "oauth_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_billing_links", + "primaryKey": { + "name": "org_billing_links_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_schema_apply_runs", + "primaryKey": { + "name": "org_clickhouse_schema_apply_runs_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_settings", + "primaryKey": { + "name": "org_clickhouse_settings_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_attribute_mappings", + "primaryKey": { + "name": "org_ingest_attribute_mappings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_keys", + "primaryKey": { + "name": "org_ingest_keys_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_settings", + "primaryKey": { + "name": "org_ingest_rule_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_stats", + "primaryKey": { + "name": "org_ingest_rule_stats_pkey", + "columns": [ + "org_id", + "rule_id", + "bucket", + "signal" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rules", + "primaryKey": { + "name": "org_ingest_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_sampling_policies", + "primaryKey": { + "name": "org_ingest_sampling_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_onboarding_state", + "primaryKey": { + "name": "org_onboarding_state_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_openrouter_keys", + "primaryKey": { + "name": "org_openrouter_keys_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_recommendation_issues", + "primaryKey": { + "name": "org_recommendation_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_support_channels", + "primaryKey": { + "name": "org_support_channels_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_connections", + "primaryKey": { + "name": "planetscale_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_databases", + "primaryKey": { + "name": "planetscale_databases_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_events", + "primaryKey": { + "name": "planetscale_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_issue_receipts", + "primaryKey": { + "name": "planetscale_issue_receipts_org_id_event_id_pk", + "columns": [ + "org_id", + "event_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_poll_state", + "primaryKey": { + "name": "planetscale_poll_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_edits", + "primaryKey": { + "name": "pr_review_edits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_finding_embeddings", + "primaryKey": { + "name": "pr_review_finding_embeddings_pkey", + "columns": [ + "finding_id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_findings", + "primaryKey": { + "name": "pr_review_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_merge_steps", + "primaryKey": { + "name": "pr_review_merge_steps_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_replies", + "primaryKey": { + "name": "pr_review_replies_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_settings", + "primaryKey": { + "name": "pr_review_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "pr_reviews", + "primaryKey": { + "name": "pr_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_connections", + "primaryKey": { + "name": "railway_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_environments", + "primaryKey": { + "name": "railway_environments_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_target_checks", + "primaryKey": { + "name": "scrape_target_checks_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_targets", + "primaryKey": { + "name": "scrape_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_events", + "primaryKey": { + "name": "security_audit_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_findings", + "primaryKey": { + "name": "security_audit_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_reservations", + "primaryKey": { + "name": "security_audit_org_reservations_pkey", + "columns": [ + "run_id", + "agent_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_settings", + "primaryKey": { + "name": "security_audit_org_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_run_findings", + "primaryKey": { + "name": "security_audit_run_findings_pkey", + "columns": [ + "run_id", + "fingerprint" + ] + } + }, + { + "kind": "table", + "name": "security_audit_runs", + "primaryKey": { + "name": "security_audit_runs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_targets", + "primaryKey": { + "name": "security_audit_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_units", + "primaryKey": { + "name": "security_audit_units_pkey", + "columns": [ + "run_id", + "coverage_id" + ] + } + }, + { + "kind": "table", + "name": "slos", + "primaryKey": { + "name": "slos_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "sources", + "primaryKey": { + "name": "sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_cleanups", + "primaryKey": { + "name": "status_page_cleanups_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_components", + "primaryKey": { + "name": "status_page_components_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_components", + "primaryKey": { + "name": "status_page_incident_components_pk", + "columns": [ + "incident_id", + "component_id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_updates", + "primaryKey": { + "name": "status_page_incident_updates_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incidents", + "primaryKey": { + "name": "status_page_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_pages", + "primaryKey": { + "name": "status_pages_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_connections", + "primaryKey": { + "name": "supabase_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_project_insights", + "primaryKey": { + "name": "supabase_project_insights_pkey", + "columns": [ + "project_id" + ] + } + }, + { + "kind": "table", + "name": "supabase_projects", + "primaryKey": { + "name": "supabase_projects_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_daily", + "primaryKey": { + "name": "uptime_monitor_daily_pk", + "columns": [ + "monitor_id", + "day" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_states", + "primaryKey": { + "name": "uptime_monitor_states_pkey", + "columns": [ + "monitor_id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitors", + "primaryKey": { + "name": "uptime_monitors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_commits", + "primaryKey": { + "name": "vcs_commits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_installations", + "primaryKey": { + "name": "vcs_installations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repositories", + "primaryKey": { + "name": "vcs_repositories_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repository_branches", + "primaryKey": { + "name": "vcs_repository_branches_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "column", + "table": "actors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "user_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "agent_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "model", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "capabilities_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "last_active_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "impact", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "summary", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "reason", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "details", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "related_to", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_type", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_name", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_model", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_version", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_runs_per_day", + "position": 2, + "type": "integer", + "notNull": true, + "default": "20", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_passes_per_day", + "position": 3, + "type": "integer", + "notNull": true, + "default": "90", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_by", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "rule_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "destination_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "delivery_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempt_number", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "scheduled_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claim_expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_by", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempted_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_message", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_reference", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "response_code", + "position": 16, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "error_message", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "payload_json", + "position": 18, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "config_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_tested_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_test_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "consecutive_failures", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_failure_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "source", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "incident_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_key", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "signal_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "severity", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "peak_severity", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "comparator", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold_upper", + "position": 12, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "first_triggered_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_triggered_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "resolved_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_observed_value", + "position": 16, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_sample_count", + "position": 17, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_evaluated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "dedupe_key", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_delivered_event_type", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_notified_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "hold_reason", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "held_since", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_user_id", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_name", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_via", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "error_issue_id", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "anomaly_incident_id", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_label", + "position": 30, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "created_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "updated_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "thread_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_team_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_issue_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_identifier", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "rule_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "last_scheduled_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "consecutive_limit_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "group_key", + "position": 2, + "type": "text", + "notNull": true, + "default": "'__total__'", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_breaches", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_healthy", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "history_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_status", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_value", + "position": 7, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_sample_count", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_evaluated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error_kind", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notes", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notification_template_json", + "position": 4, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "severity", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "service_names_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "exclude_service_names_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "environments_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "anomaly_signals_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "tags_json", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "signal_type", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "comparator", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold_upper", + "position": 15, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "thresholds_json", + "position": 16, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "window_minutes", + "position": 17, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "evaluation_interval_minutes", + "position": 18, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_interval_minutes", + "position": 19, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "minimum_sample_count", + "position": 21, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_breaches_required", + "position": 22, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "breaches_out_of", + "position": 23, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_healthy_required", + "position": 24, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "renotify_interval_minutes", + "position": 25, + "type": "integer", + "notNull": true, + "default": "30", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "apdex_threshold_ms", + "position": 26, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_builder_draft_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "raw_query_sql", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "group_by", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "destination_ids_json", + "position": 30, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_spec_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "reducer", + "position": 32, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "sample_count_strategy", + "position": 33, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "no_data_behavior", + "position": 34, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "last_scheduled_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_at", + "position": 36, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_at", + "position": 37, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_by", + "position": 38, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_by", + "position": 39, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "sensitivity", + "position": 2, + "type": "text", + "notNull": true, + "default": "'normal'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "muted_signals_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "last_tick_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "detector_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "signal_type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "service_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "deployment_env", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "fingerprint_hash", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_breaches", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_healthy", + "position": 7, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_value", + "position": 9, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "baseline_median", + "position": 10, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_sample_count", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_evaluated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "open_incident_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_resolved_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_incident_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "detector_key", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "signal_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "deployment_env", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprint_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "error_issue_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "opened_value", + "position": 10, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_median", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_sigma", + "position": 12, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "threshold_value", + "position": 13, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_observed_value", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_sample_count", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "first_triggered_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_triggered_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolved_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolve_reason", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "triage_status", + "position": 20, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "dedupe_key", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprints_json", + "position": 22, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "reopen_count", + "position": 23, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_reopened_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "created_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "updated_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_prefix", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "last_used_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "metadata_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "scopes", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "kind", + "position": 12, + "type": "text", + "notNull": true, + "default": "'standard'", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by_email", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "account_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "modules", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "granted_modules", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "template_version", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "verified_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_checked_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_check_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "check_lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream_changed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_hash", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_received_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "plan_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "subscription_started_at", + "position": 3, + "type": "bigint", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "canceled_at", + "position": 4, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "rule_reason", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "model_read", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "posted_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "external_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "display_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "external_workspace_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "settings", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_code_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "user_code_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_org_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_roles", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_email", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "api_key_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_ciphertext", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_iv", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_tag", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "denied_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "dataset", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_id", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "watermark_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backfill_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_json", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_fetched_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "quantiles_available", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "discovered_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "live_scripts_json", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "grant_call_cap", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_success_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "config_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_host", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_port", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_scheme", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_database", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_user", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "deleted_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "zone_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "service_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "dataset", + "position": 6, + "type": "text", + "notNull": true, + "default": "'http_requests'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_ciphertext", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_iv", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_tag", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_hash", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "enabled", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_rotated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "account_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "watch_new_zones", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "zone_overrides", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "widget_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "mode", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_hash", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_suffix", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "version_number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "snapshot_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_summary", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "source_version_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "payload_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "version", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "lockfile_path", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "ecosystem", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "package_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "advisory_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "aliases", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "severity", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "summary", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "versions", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "relation", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_scan_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_scan_id", + "position": 15, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_sha", + "position": 16, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_sha", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_scan_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_sha", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_reason", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_by", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "scan_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "lockfile_path", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "ecosystem", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "package_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "version", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "relation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "advisory_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "aliases", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "summary", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "commit_sha", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "ref", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "trigger", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "default_branch_head", + "position": 6, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'running'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "lockfiles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "package_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "vulnerable_count", + "position": 10, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "started_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "finished_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "public_jwk", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "jkt", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "roles", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "scopes", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attested", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attestation", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "last_used_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "expires_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "hard_expires_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "key", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "expires_at", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "email", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "opted_out_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "day_of_week", + "position": 6, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "timezone", + "position": 7, + "type": "text", + "notNull": true, + "default": "'UTC'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "send_hour", + "position": 8, + "type": "integer", + "notNull": true, + "default": "9", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "namespaces_json", + "position": 9, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "environments_json", + "position": 10, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_attempted_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_opted_out_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_sent_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_attempted_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_message", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "error_label", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "top_frame", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "first_seen_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "last_seen_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "reason", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "first_triggered_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "last_triggered_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "resolved_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "actor_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "type", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "from_state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "to_state", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "payload_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "source", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "origin_issue_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "repo_full_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "number", + "position": 6, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "title", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "author_login", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "state", + "position": 10, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merged_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merge_commit_sha", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "link_source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "linked_by_actor_id", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "issue_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_observed_occurrence_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_evaluated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "open_incident_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "pull_request_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "status", + "position": 4, + "type": "text", + "notNull": true, + "default": "'waiting'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "merged_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verify_after", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_rate_per_hour", + "position": 9, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "investigation_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict_note", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "post_merge_occurrence_count", + "position": 13, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "attempt", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "kind", + "position": 2, + "type": "text", + "notNull": true, + "default": "'error'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "source_ref_json", + "position": 3, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_version", + "position": 5, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "service_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_type", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_message", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "error_label", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "top_frame", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "workflow_state", + "position": 11, + "type": "text", + "notNull": true, + "default": "'triage'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "priority", + "position": 12, + "type": "integer", + "notNull": true, + "default": "3", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity_source", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "assigned_actor_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_holder_actor_id", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "claimed_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "notes", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "first_seen_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "occurrence_count", + "position": 22, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_by_actor_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_resolved_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_regressed_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "regression_count", + "position": 27, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "seen_versions_json", + "position": 28, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_versions_json", + "position": 29, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "snooze_until", + "position": 30, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "archived_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "merged_into_issue_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "created_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "updated_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "delivery_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "payload_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "status", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempt_count", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "scheduled_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claim_expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempted_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "error_message", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "destination_ids_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_first_seen", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_regression", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_resolve", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_in_review", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_done", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_claim", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "min_occurrence_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "severity", + "position": 10, + "type": "text", + "notNull": true, + "default": "'warning'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "processed_through", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "bootstrap_completed", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_token", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_expires_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "project_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "logs_enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_ciphertext", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_iv", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_tag", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_hash", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_logs_enabled", + "position": 14, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_metrics_enabled", + "position": 15, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "setup_reported_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_watermark_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_received_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_lease_until", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "resources_synced_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_resources_error", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_by", + "position": 23, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "updated_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "connector_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "asset_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "project_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "location", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "display_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "labels", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "details", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "last_seen_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'investigating'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "seeded_by", + "position": 3, + "type": "text", + "notNull": true, + "default": "'user'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "subject_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_kind", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "issue_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "report_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "progress_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "severity", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "confidence", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "input_tokens", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "output_tokens", + "position": 15, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "started_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "autonomous_turns", + "position": 18, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "diagnosed_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "updated_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "rules_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "severity", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "source", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "reason", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "run_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "investigation_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "payload_json", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "delivery_results_json", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "attempts", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "dedupe_key", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "processed_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "device_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "incident_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "activity_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "push_token", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_reason", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "request_id_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "redirect_uri", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "state", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "resource", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "scopes", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "code_challenge", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "authorization_code_hash", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_org_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_roles", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_email", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "denied_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "used_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_name", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "redirect_uris", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_uri", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "token_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "client_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "resource", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "scopes", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "org_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "roles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_email", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "access_key_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "replaced_by_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "revoked_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_expires_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "platform", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "token", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "environment", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "bundle_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "app_version", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "live_activity_start_token", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "device_name", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "preferences", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_reason", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_pushed_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_seen_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "state", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "initiated_by_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "redirect_uri", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "return_to", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "code_verifier", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "github_installation_ids", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_email", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_account_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "granted_accounts_json", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "connected_by_user_id", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "scope", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_ciphertext", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_iv", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_tag", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "expires_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "revoked_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "billing_org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "linked_by", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "created_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "workflow_instance_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "phase", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "current_migration", + "position": 4, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_total", + "position": 5, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_done", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "applied_versions", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "skipped", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "error_message", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "started_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "finished_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_url", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_user", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_ciphertext", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_iv", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_tag", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_database", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "sync_status", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "schema_version", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_context", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "target_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "operation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key_hash", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_hash", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_rotated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_rotated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "hash_salt", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "created_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "bucket", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "signal", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "matched", + "position": 4, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped", + "position": 5, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped_bytes", + "position": 6, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "redacted_values", + "position": 7, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "enabled", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "position", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "spec_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "trace_sample_ratio", + "position": 1, + "type": "double precision", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_error_spans", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_slow_spans_ms", + "position": 3, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "user_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "email", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "role", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "demo_data_requested", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "onboarding_completed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "checklist_dismissed_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "first_data_received_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "welcome_email_sent_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "connect_nudge_email_sent_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "stalled_email_sent_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "activation_email_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_reserved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_ciphertext", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_iv", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_tag", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_hint", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "number", + "position": 2, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "recommendation_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "source_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "canonical_key", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "usage_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "opened_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "resolved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_name", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reservation_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reserved_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_by_user_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "ps_organization", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "connected_by_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "scrape_target_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "detected_permissions_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": "'mysql'", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "region", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "plan", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "branches_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_ciphertext", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_iv", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_tag", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "deleted_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "branch_name", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "category", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "external_id", + "position": 8, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "title", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "source", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "actor_login", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "payload_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "occurred_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "event_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "processed_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "dataset", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "database_id", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "watermark_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_success_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "lease_until", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "reply_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "seq", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "path", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "old_text", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "new_text", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "finding_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "embedding", + "position": 4, + "type": "real[]", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "review_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "handle", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "path", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "line", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "category", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "title", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "status", + "position": 11, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "comment_id", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "resolved_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_up", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_down", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "updated_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "title", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "source", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "path", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "reminded_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "first_review_id", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "last_review_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "comment_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "surface", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "thread_root_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "author_login", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "command", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "head_sha", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "reply_url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "commit_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "model", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "defaults", + "position": 2, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "head_sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "base_sha", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "url", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "title", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "skip_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_attempt", + "position": 12, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "report_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "score", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "check_run_url", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_url", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "review_url", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "input_tokens", + "position": 21, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "output_tokens", + "position": 22, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "started_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "finished_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merged_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "telemetry_json", + "position": 26, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merge_commit_sha", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_status", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_after", + "position": 29, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_json", + "position": 30, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "created_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "updated_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "workspace_names", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "connected_by_user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "auth_failed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "discovered_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_success_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "services_json", + "position": 7, + "type": "text", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "enabled", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "watermark_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_success_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "id", + "position": 0, + "type": "integer", + "notNull": true, + "default": null, + "identity": "by default" + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "target_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "sub_target_key", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "error", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "duration_ms", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_scraped", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_post_relabel", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "url", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "target_type", + "position": 6, + "type": "text", + "notNull": true, + "default": "'prometheus'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "discovery_config_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "scrape_interval_seconds", + "position": 8, + "type": "integer", + "notNull": true, + "default": "15", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "labels_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_type", + "position": 10, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "managed_by", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_ciphertext", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_iv", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_tag", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_error", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "run_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "role", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "agent_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "outcome", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "detail", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "input_tokens", + "position": 9, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "output_tokens", + "position": 10, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fingerprint", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "verdict", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "overall_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "estimated_severity", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "record", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_run_id", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_run_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_ref", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_ref", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fixed_run_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_by", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "agent_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "cost_usd", + "position": 3, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "sandbox_fee_usd", + "position": 4, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "monthly_spend_limit_usd", + "position": 1, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "discovery_model", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "validation_model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "fingerprint", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "verdict", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "overall_severity", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "estimated_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "record", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "source_ref", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "profile", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "scope_paths", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "budget", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "metadata", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'in_progress'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "incomplete_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "architecture_md", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "reports", + "position": 12, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "execution_profile", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "invocations_spent", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "input_tokens", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "output_tokens", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "cost_usd", + "position": 17, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "sandbox_usd", + "position": 18, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "discovery_model", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "validation_model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "workflow_instance_id", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "started_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "finished_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "error", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "repository", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "vcs_repository_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "branch", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "profile", + "position": 6, + "type": "text", + "notNull": true, + "default": "'deep'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "scope_paths", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "slice_interval_hours", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "revalidate_on_push", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "budget_per_run", + "position": 10, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "monthly_invocation_budget", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "discovery_model", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "validation_model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "coverage_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "wave", + "position": 4, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "agent_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "unit", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "environments_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "sli_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "target_percent", + "position": 7, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "window_days", + "position": 8, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "alert_rule_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "platform", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_prefix", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "allowed_signals", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rate_limit_rps", + "position": 9, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "resource_attributes", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_until", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_used_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejected_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejection", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_ok_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "disconnected_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "poll_resources", + "position": 21, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rolled_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_by", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_by", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "delete_snapshot", + "position": 4, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "group_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "position", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "monitor_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "manual_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "show_uptime", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "incident_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "component_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "body", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "source", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "overlay_entry_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "title", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "impact", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "published", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "alert_incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_start", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_end", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "started_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "resolved_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "website_url", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "logo_url", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "privacy_url", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "terms_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "theme", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'{\"preset\":\"default\",\"appearance\":\"auto\",\"accent\":null,\"background\":null,\"text\":null,\"card\":null,\"border\":null,\"radius\":\"md\",\"font\":\"sans\"}'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "show_powered_by", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "auto_incidents", + "position": 11, + "type": "text", + "notNull": true, + "default": "'off'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_sha256", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_suffix", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "draft", + "position": 14, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "revision", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_revision", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "last_publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_hash", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "publish_attempted_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_by", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "updated_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_slug", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "connected_by_user_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "discovery_lease_until", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "project_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "top_queries_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_checked_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_error", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "lease_until", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "project_ref", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "region", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "scrape_target_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_enabled", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_source_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_json", + "position": 13, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_checked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_watermark_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_boundary_ids", + "position": 17, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_last_polled_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_error", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "removed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "day", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "checks", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "failures", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "down_checks", + "position": 5, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "last_check_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_successes", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status_changed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_check_json", + "position": 6, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "recent_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_ping_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "config_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "interval_seconds", + "position": 5, + "type": "integer", + "notNull": true, + "default": "60", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "failures_to_down", + "position": 6, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "service_name", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "alert_rule_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "heartbeat_token", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "enabled", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "message", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_email", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_avatar_url", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "authored_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "committed_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "html_url", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_installation_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_login", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_account_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_avatar_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_selection", + "position": 9, + "type": "text", + "notNull": true, + "default": "'all'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "suspended_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "ci_traces_enabled", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_discovery", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "installed_by_user_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "owner", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "full_name", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "default_branch", + "position": 8, + "type": "text", + "notNull": true, + "default": "'main'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "tracked_branch", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "html_url", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_private", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_archived", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "sync_status", + "position": 14, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_synced_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_sync_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_enabled", + "position": 17, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_config", + "position": 18, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "is_default", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "head_sha", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_agent_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"agent_name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_kind_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"kind\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"claim_expires_at\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_delivery_attempt_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"attempt_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_incident_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"incident_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_open_group_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"", + "\"group_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_rule_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_thread_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"destination_id\"", + "\"thread_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_claims", + "name": "alert_rule_claims_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_states", + "name": "alert_rule_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_evaluated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"last_evaluated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_open_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"open_incident_id\"" + ], + "where": "\"open_incident_id\" is not null" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_open_detector_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_detector_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_status_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"", + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_org_account_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_stream_secret_hash_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"stream_secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cancellation_reviews", + "name": "cancellation_reviews_subscription_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"plan_id\"", + "\"subscription_started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"external_user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connector\"", + "\"external_workspace_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_user_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"user_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_account_dataset_zone_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"", + "\"dataset\"", + "\"zone_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_config_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"config_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_live_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "coalesce(widget_id, '')" + ], + "where": "revoked_at is null" + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_token_hash_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_version_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_name_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"lockfile_path\"", + "\"ecosystem\"", + "\"package_name\"", + "\"advisory_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scan_matches", + "name": "dependency_scan_matches_org_scan_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scan_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_commit_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"commit_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_jkt_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"jkt\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_live_installation_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"installation_id\"" + ], + "where": "\"revoked_at\" IS NULL" + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_proof_replays", + "name": "device_proof_replays_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_fingerprint_candidates", + "name": "error_fingerprint_candidates_last_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_actor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"actor_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_fingerprints", + "name": "error_issue_fingerprints_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_pr_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"verify_after\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_open_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": "\"status\" in ('waiting', 'running')" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_lease_expiry_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"lease_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_archived_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"archived_at\"" + ], + "where": "\"archived_at\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_assignee_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"assigned_actor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_version_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_version\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_live_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"", + "\"id\"" + ], + "where": "\"archived_at\" is null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_merged_into_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"merged_into_issue_id\"" + ], + "where": "\"merged_into_issue_id\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_severity_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"severity\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_workflow_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"workflow_state\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"", + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_key_destination_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"destination_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_tick_states", + "name": "error_tick_states_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_org_scope_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scope_type\"", + "\"scope_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_incident_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_kind\"", + "\"incident_id\"" + ], + "where": "\"incident_id\" is not null" + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"dedupe_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_device_incident_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"device_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"authorization_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_family_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"family_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_platform_token_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"platform\"", + "\"token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_auth_states", + "name": "oauth_auth_states_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_provider_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_billing_links", + "name": "org_billing_links_billing_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"billing_org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_attribute_mappings", + "name": "org_ingest_attribute_mappings_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_private_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"private_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rule_stats", + "name": "org_ingest_rule_stats_org_bucket_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"bucket\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rules", + "name": "org_ingest_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"recommendation_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"event_type\"", + "\"external_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_db_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_issue_receipts", + "name": "planetscale_issue_receipts_processed_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"processed_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_dataset_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dataset\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_edits", + "name": "pr_review_edits_reply_seq_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"reply_id\"", + "\"seq\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_finding_embeddings", + "name": "pr_review_finding_embeddings_org_model_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"model\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_pr_handle_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"handle\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_pr_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_repo_comment_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"comment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_post_merge_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"post_merge_after\"" + ], + "where": "\"post_merge_status\" = 'waiting'" + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_head_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"head_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_connection_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"connection_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_org_env_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"environment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_checked_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"checked_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_events", + "name": "security_audit_events_run_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"run_id\"", + "\"at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_target_fingerprint_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"fingerprint\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_org_reservations", + "name": "security_audit_org_reservations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_one_running_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"" + ], + "where": "\"finished_at\" IS NULL" + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_target_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_repo_branch_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"repository\"", + "\"branch\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_name_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_default_kind_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": "\"kind\" = 'default'" + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_cleanups", + "name": "status_page_cleanups_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_monitor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"monitor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"position\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_components", + "name": "status_page_incident_components_component_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"component_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_overlay_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"overlay_entry_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_alert_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"alert_incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_slug_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"slug\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_unpublished_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"revision\"", + "\"published_revision\"" + ], + "where": "revision > published_revision" + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_project_insights", + "name": "supabase_project_insights_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_connection_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connection_id\"", + "\"project_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"log_drain_source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_enabled_kind_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"kind\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_heartbeat_token_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"heartbeat_token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_org_sha_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_repo_sha_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_provider_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"provider\"", + "\"external_installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_installation_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_repo_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"external_repo_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_repo_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "foreign_key", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_linear_issues", + "name": "alert_linear_issues_destination_id_alert_destinations_id_fk", + "columns": [ + "destination_id" + ], + "foreignTable": "alert_destinations", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "aws_connections", + "name": "aws_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "dashboard_shares", + "name": "dashboard_shares_dashboard_fk", + "columns": [ + "org_id", + "dashboard_id" + ], + "foreignTable": "dashboards", + "foreignColumns": [ + "org_id", + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_connectors", + "name": "gcp_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_resources", + "name": "gcp_resources_connector_id_gcp_connectors_id_fk", + "columns": [ + "connector_id" + ], + "foreignTable": "gcp_connectors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oauth_connections", + "name": "oauth_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "planetscale_connections", + "name": "planetscale_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "railway_connections", + "name": "railway_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_id_scrape_targets_id_fk", + "columns": [ + "target_id" + ], + "foreignTable": "scrape_targets", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_targets", + "name": "scrape_targets_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_Bnqlt6wwKJty_fk", + "columns": [ + "component_id" + ], + "foreignTable": "status_page_components", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_tBn6mEz4FenY_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_3LDDUAhznHPM_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incidents", + "name": "status_page_incidents_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_connections", + "name": "supabase_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_project_insights", + "name": "supabase_project_insights_project_id_supabase_projects_id_fk", + "columns": [ + "project_id" + ], + "foreignTable": "supabase_projects", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_connection_id_supabase_connections_id_fk", + "columns": [ + "connection_id" + ], + "foreignTable": "supabase_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_id_sources_id_fk", + "columns": [ + "log_drain_source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vcs_installations", + "name": "vcs_installations_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + } + ] +} diff --git a/packages/db/src/tables/alerts.ts b/packages/db/src/tables/alerts.ts index e0b2a069c6..15e8030abf 100644 --- a/packages/db/src/tables/alerts.ts +++ b/packages/db/src/tables/alerts.ts @@ -5,10 +5,12 @@ import { AlertIncidentEventId, AlertIncidentId, AlertRuleId, + AnomalyIncidentId, ErrorIssueId, OrgId, UserId, } from "@maple/domain/primitives" +import { AnomalySignalType } from "@maple/domain/http" import { Schema } from "effect" const StringArray = Schema.Array(Schema.String) @@ -73,6 +75,10 @@ export const AlertRules = PG.table("alert_rules", { * filters, so it is always empty for those. */ environmentsJson: PG.column(PG.nullable(PG.jsonb(StringArray)), { name: "environments_json" }), + /** Anomaly rules only: detector signals in scope. Null/empty means every signal. */ + anomalySignalsJson: PG.column(PG.nullable(PG.jsonb(Schema.Array(AnomalySignalType))), { + name: "anomaly_signals_json", + }), /** JSON-encoded `string[]` of free-form tags used to group and filter rules. */ tagsJson: PG.column(PG.nullable(PG.jsonb(StringArray)), { name: "tags_json" }), signalType: PG.column(PG.text, { name: "signal_type" }), @@ -226,6 +232,13 @@ const alertIncidentsColumns = { // Issue-hub link: the error_issues row (kind="alert") this incident // feeds, mirroring anomalyIncidents.errorIssueId. errorIssueId: PG.column(PG.nullable(PG.brand(PG.text, ErrorIssueId)), { name: "error_issue_id" }), + // Anomaly rules only: the anomaly incident this alert incident pages on. + anomalyIncidentId: PG.column(PG.nullable(PG.brand(PG.text, AnomalyIncidentId)), { + name: "anomaly_incident_id", + }), + // What people see as the group when the key is not readable, e.g. an anomaly's + // "checkout (production)". Null for rules whose group key is its own label. + groupLabel: PG.column(PG.nullable(PG.text), { name: "group_label" }), createdAt: PG.column(PG.timestamptzMillis, { name: "created_at" }), updatedAt: PG.column(PG.timestamptzMillis, { name: "updated_at" }), } diff --git a/packages/domain/src/http/alerts.ts b/packages/domain/src/http/alerts.ts index d34ef496d0..580eae0341 100644 --- a/packages/domain/src/http/alerts.ts +++ b/packages/domain/src/http/alerts.ts @@ -6,6 +6,7 @@ import { AlertIncidentEventId, AlertIncidentId, AlertRuleId, + AnomalyIncidentId, ChatConnectorId, ChatWorkspaceId, ErrorIssueId, @@ -16,6 +17,7 @@ import { } from "../primitives" import { QueryBuilderQueryDraftSchema } from "./query-engine" import { HttpTaggedError } from "./error-policy" +import { AnomalySignalType } from "./anomalies" export const AlertDestinationType = Schema.Literals([ "pagerduty", @@ -52,6 +54,8 @@ export const AlertSignalType = Schema.Literals([ "uptime", /** The hidden burn-rate rule behind an SLO; managed only through the SLO. */ "slo_burn_rate", + /** Pages on the anomaly detector's open incidents in the rule's scope; it has no query. */ + "anomaly", ]).annotate({ identifier: "@maple/AlertSignalType", title: "Alert Signal Type", @@ -816,6 +820,8 @@ export class AlertRuleDocument extends Schema.Class("AlertRul * their own filters. */ environments: Schema.Array(Schema.String), + /** Anomaly rules only: the detector signals the rule pages on. Absent or empty means every signal. */ + anomalySignals: Schema.optionalKey(Schema.Array(AnomalySignalType)), /** Free-form tags used to group and filter rules in the alerts list. */ tags: Schema.Array(Schema.String), groupBy: Schema.NullOr(AlertGroupBy), @@ -878,6 +884,8 @@ export class AlertRuleUpsertRequest extends Schema.Class serviceNames: Schema.optionalKey(Schema.Array(Schema.String)), excludeServiceNames: Schema.optionalKey(Schema.Array(Schema.String)), environments: Schema.optionalKey(Schema.Array(Schema.String)), + /** Anomaly rules only: the detector signals to page on. Absent or empty means every signal. */ + anomalySignals: Schema.optionalKey(Schema.Array(AnomalySignalType)), tags: Schema.optionalKey(RuleTags), groupBy: Schema.optionalKey(Schema.NullOr(AlertGroupBy)), signalType: AlertSignalType, @@ -1055,6 +1063,10 @@ export class AlertIncidentDocument extends Schema.Class(" acknowledgedByName: Schema.NullOr(Schema.String), acknowledgedVia: Schema.NullOr(AlertIncidentAckSource), errorIssueId: Schema.NullOr(ErrorIssueId), + /** Anomaly rules only: the anomaly incident this alert incident pages on. */ + anomalyIncidentId: Schema.optionalKey(Schema.NullOr(AnomalyIncidentId)), + /** What to show as the group when the key is not readable, e.g. an anomaly's "checkout (production)". */ + groupLabel: Schema.optionalKey(Schema.NullOr(Schema.String)), }) {} export class AlertIncidentEventDocument extends Schema.Class( diff --git a/packages/domain/src/http/v2/alert-incidents.ts b/packages/domain/src/http/v2/alert-incidents.ts index 7666b80273..24042d7837 100644 --- a/packages/domain/src/http/v2/alert-incidents.ts +++ b/packages/domain/src/http/v2/alert-incidents.ts @@ -20,6 +20,7 @@ import { AlertIncidentEventPublicId, AlertIncidentPublicId, AlertRulePublicId, + AnomalyIncidentPublicId, ErrorIssuePublicId, } from "./resource-ids" @@ -149,6 +150,18 @@ export const V2AlertIncident = Schema.Struct({ error_issue_id: Schema.NullOr(ErrorIssuePublicId).annotate({ description: "The linked error issue (`iss_…`) when the incident was correlated to one, or `null`.", }), + group_label: Schema.optionalKey( + Schema.NullOr(Schema.String).annotate({ + description: + "What to show as the incident's group when `group_key` is not readable, e.g. an anomaly's `checkout (production)`. Absent when the group key is its own label.", + }), + ), + anomaly_incident_id: Schema.optionalKey( + Schema.NullOr(AnomalyIncidentPublicId).annotate({ + description: + "`anomaly` rules only: the anomaly incident this alert incident pages on. Absent on other rules.", + }), + ), }).annotate({ identifier: "AlertIncident", title: "Alert Incident", diff --git a/packages/domain/src/http/v2/alert-rules.ts b/packages/domain/src/http/v2/alert-rules.ts index a475185bb8..68f3394942 100644 --- a/packages/domain/src/http/v2/alert-rules.ts +++ b/packages/domain/src/http/v2/alert-rules.ts @@ -29,6 +29,7 @@ import { AlertEvaluationErrorKind, type AlertThreshold, } from "../alerts" +import { AnomalySignalType } from "../anomalies" import { AlertDestinationPublicId } from "./alert-destinations" const EVALUATION_INTERVAL_DESCRIPTION = @@ -215,6 +216,13 @@ export const V2AlertRule = Schema.Struct({ "Deployment environments the rule is scoped to. Empty for all environments. Always empty for `builder_query` / `raw_query`, whose queries carry their own filters.", examples: [["production"]], }), + anomaly_signals: Schema.optionalKey( + Schema.Array(AnomalySignalType).annotate({ + description: + "`anomaly` rules only: the detector signals the rule pages on. Empty for every signal. Absent on other rules.", + examples: [["error_rate", "latency_p95"]], + }), + ), tags: Schema.Array(Schema.String).annotate({ description: "Free-form tags used to group and filter rules.", examples: [["payments"]], @@ -402,6 +410,13 @@ const createParamsFields = { examples: [["production"]], }), ), + anomaly_signals: Schema.optionalKey( + Schema.Array(AnomalySignalType).annotate({ + description: + "`anomaly` rules only: the detector signals to page on. Omit or pass `[]` for every signal.", + examples: [["error_rate"]], + }), + ), tags: Schema.optionalKey(RuleTags), group_by: Schema.optionalKey(Schema.NullOr(GroupBy)), signal_type: AlertSignalType.annotate({ @@ -491,6 +506,7 @@ export const V2AlertRuleUpdateParams = Schema.Struct({ service_names: createParamsFields.service_names, exclude_service_names: createParamsFields.exclude_service_names, environments: createParamsFields.environments, + anomaly_signals: createParamsFields.anomaly_signals, tags: createParamsFields.tags, group_by: createParamsFields.group_by, threshold_upper: createParamsFields.threshold_upper, @@ -989,7 +1005,7 @@ export class V2AlertRulesApiGroup extends HttpApiGroup.make("alertRules") HttpApiEndpoint.delete("delete", "/:id", { params: { id: AlertRulePublicId }, success: V2AlertRuleDeleteResponse, - error: [alertForbidden, alertPersistence, alertRuleNotFound], + error: [alertForbidden, alertValidation, alertPersistence, alertRuleNotFound], }).annotateMerge( OpenApi.annotations({ identifier: "deleteAlertRule", diff --git a/packages/domain/src/mcp-outputs/alerts.ts b/packages/domain/src/mcp-outputs/alerts.ts index 17e5776ed7..86e0d8cbf2 100644 --- a/packages/domain/src/mcp-outputs/alerts.ts +++ b/packages/domain/src/mcp-outputs/alerts.ts @@ -14,6 +14,8 @@ export const AlertRuleRow = Schema.Struct({ serviceNames: Schema.Array(Schema.String), /** Deployment environments the rule is scoped to. Empty means all. */ environments: Schema.Array(Schema.String), + /** Anomaly rules only: detector signals in scope. Empty means every signal. */ + anomalySignals: Schema.optionalKey(Schema.Array(Schema.String)), signalType: Schema.String, comparator: Schema.String, threshold: Schema.Number, @@ -224,6 +226,10 @@ export const AlertIncidentRow = Schema.Struct({ firstTriggeredAt: Schema.String, resolvedAt: NullableString, lastObservedValue: NullableNumber, + /** Anomaly rules only: the anomaly incident this alert incident pages on. */ + anomalyIncidentId: Schema.optionalKey(NullableString), + /** Shown in place of `groupKey` when set, e.g. an anomaly's "checkout (production)". */ + groupLabel: Schema.optionalKey(NullableString), }) /** Filters an incident list was narrowed by, echoed for the rendered scope. */ @@ -292,6 +298,8 @@ export const IncidentTimelineRow = Schema.Struct({ ruleId: Schema.String, ruleName: Schema.String, groupKey: NullableString, + /** Shown in place of `groupKey` when set, e.g. an anomaly's "checkout (production)". */ + groupLabel: Schema.optionalKey(NullableString), signalType: Schema.String, severity: Schema.String, status: Schema.String, diff --git a/packages/web-data/src/alerts/form-utils.ts b/packages/web-data/src/alerts/form-utils.ts index fd6683a2c3..aa4d84ec05 100644 --- a/packages/web-data/src/alerts/form-utils.ts +++ b/packages/web-data/src/alerts/form-utils.ts @@ -131,6 +131,7 @@ export const signalLabels: Record = { raw_query: "Raw SQL", uptime: "Uptime check", slo_burn_rate: "SLO burn rate", + anomaly: "Anomaly", } satisfies Record export const RAW_QUERY_REDUCER_LABELS: Record = { @@ -189,6 +190,9 @@ export function formatSignalValue(signalType: AlertSignalType, value: number | n // An SLO rule observes how many times faster than sustainable the budget burns. case "slo_burn_rate": return `${value.toFixed(1)}x` + // An anomaly rule observes the open anomaly's severity: 0 none, 1 warning, 2 critical. + case "anomaly": + return value >= 2 ? "Critical" : value >= 1 ? "Warning" : "Normal" } } diff --git a/packages/web-data/src/alerts/headroom.ts b/packages/web-data/src/alerts/headroom.ts index 506ebe8523..f26ff401df 100644 --- a/packages/web-data/src/alerts/headroom.ts +++ b/packages/web-data/src/alerts/headroom.ts @@ -8,6 +8,8 @@ export interface RuleHeadroom { readonly rule: AlertRuleDocument /** The group nearest its threshold; null for an ungrouped rule. */ readonly groupKey: string | null + /** What to show for the group: an incident's stored label, else the key; null hides it. */ + readonly groupLabel: string | null readonly value: number /** * 0 = far from firing, 1 = at the threshold. Null for comparators with no @@ -40,7 +42,20 @@ function closenessOf(rule: AlertRuleDocument, value: number): number | null { export function ruleHeadroom( rules: ReadonlyArray, statesByRule: ReadonlyMap>, + incidents: ReadonlyArray = [], ): RuleHeadroom[] { + const labels = new Map( + Arr.flatMap(incidents, (incident) => + incident.groupLabel == null + ? [] + : [[`${incident.ruleId}:${incident.groupKey}`, incident.groupLabel] as const], + ), + ) + // An anomaly rule's group key is an incident id, which is never shown. + const labelOf = (rule: AlertRuleDocument, groupKey: string | null): string | null => + groupKey === null + ? null + : (labels.get(`${rule.id}:${groupKey}`) ?? (rule.signalType === "anomaly" ? null : groupKey)) const nearestFirst = Order.flip( Order.mapInput(Order.Number, (entry: { readonly closeness: number | null }) => entry.closeness ?? -1), ) @@ -53,6 +68,10 @@ export function ruleHeadroom( : [ { groupKey: state.group_key === UNGROUPED_GROUP_KEY ? null : state.group_key, + groupLabel: labelOf( + rule, + state.group_key === UNGROUPED_GROUP_KEY ? null : state.group_key, + ), value: state.last_value, closeness: closenessOf(rule, state.last_value), checkedAt: diff --git a/packages/web-data/src/alerts/overview-derive.test.ts b/packages/web-data/src/alerts/overview-derive.test.ts index e6fa118954..66c420ba15 100644 --- a/packages/web-data/src/alerts/overview-derive.test.ts +++ b/packages/web-data/src/alerts/overview-derive.test.ts @@ -168,6 +168,25 @@ describe("incident lines", () => { expect(incidentSubline(incident)).toBe("High error rate · checkout") }) + it("shows an anomaly incident's stored label, never its incident-id group key", () => { + const anomaly = (groupLabel?: string) => + makeIncident({ + signalType: "anomaly", + ruleName: "Anomaly detection", + groupKey: "00000000-0000-4000-8000-00000000a001", + lastObservedValue: 2, + status: "open", + resolvedAt: null, + ...(groupLabel === undefined ? undefined : { groupLabel }), + }) + expect(incidentTitle(anomaly("checkout (production)"))).toBe( + "Anomaly Critical on checkout (production)", + ) + expect(incidentScope(anomaly("checkout (production)"))).toBe("checkout (production)") + expect(incidentTitle(anomaly())).toBe("Anomaly Critical") + expect(incidentScope(anomaly())).toBeNull() + }) + it("names a raw SQL incident by its rule, with the group under it", () => { const incident = makeIncident({ signalType: "raw_query", diff --git a/packages/web-data/src/alerts/overview-derive.ts b/packages/web-data/src/alerts/overview-derive.ts index 6d98eda686..0cdc56f594 100644 --- a/packages/web-data/src/alerts/overview-derive.ts +++ b/packages/web-data/src/alerts/overview-derive.ts @@ -46,9 +46,13 @@ export function ruleConditionSummary(rule: AlertRuleDocument): string { type IncidentLine = Pick< AlertIncidentDocument, - "signalType" | "ruleName" | "groupKey" | "status" | "lastObservedValue" | "threshold" + "signalType" | "ruleName" | "groupKey" | "groupLabel" | "status" | "lastObservedValue" | "threshold" > +/** The group as people read it: the stored label, else the key; an anomaly's key is never shown. */ +const shownGroup = (incident: IncidentLine): string | null => + incident.groupLabel ?? (incident.signalType === "anomaly" ? null : incident.groupKey) + /** Uptime incidents name the monitor ("checkout-api is down"), not a signal and a value. */ const isUptime = (incident: Pick) => incident.signalType === "uptime" @@ -71,12 +75,13 @@ export function incidentTitle(incident: IncidentLine): string { if (isQueryIncident(incident)) return incident.ruleName const value = formatSignalValue(incident.signalType, incident.lastObservedValue) const base = `${signalLabels[incident.signalType]} ${value}` - return incident.groupKey === null ? base : `${base} on ${incident.groupKey}` + const group = shownGroup(incident) + return group === null ? base : `${base} on ${group}` } /** The group shown next to the rule name: an uptime or SLO incident's only when it is a real one. */ export function incidentScope(incident: IncidentLine): string | null { - return isUptime(incident) || isSlo(incident) ? incidentGroup(incident) : incident.groupKey + return isUptime(incident) || isSlo(incident) ? incidentGroup(incident) : shownGroup(incident) } /** The line under the title: the rule and group, or a query rule's kind and group (its name is the title). */ diff --git a/packages/web-data/src/collections/alerts.ts b/packages/web-data/src/collections/alerts.ts index 68314ed8e7..1c8ae29b96 100644 --- a/packages/web-data/src/collections/alerts.ts +++ b/packages/web-data/src/collections/alerts.ts @@ -17,6 +17,8 @@ import { AlertSeverity, AlertSignalType, AlertThresholds, + AnomalyIncidentId, + AnomalySignalType, ChatConnectorId, ChatWorkspaceId, ErrorIssueId, @@ -58,6 +60,9 @@ const decodeNotificationTemplate = Schema.decodeUnknownOption(AlertNotificationT const decodeQueryBuilderDraft = Schema.decodeUnknownOption(QueryBuilderQueryDraftSchema) const decodeThresholds = Schema.decodeUnknownOption(AlertThresholds) const decodeStringArray = Schema.decodeUnknownOption(Schema.Array(Schema.String)) +// Lenient: a signal added by a newer server drops the list rather than breaking the alerts list. +const decodeAnomalySignals = Schema.decodeUnknownOption(Schema.Array(AnomalySignalType)) +const decodeAnomalyIncidentId = Schema.decodeUnknownOption(AnomalyIncidentId) // The stored `group_by` column is a JSON-encoded string array (mirrors the // server's `Schema.fromJsonString(AlertGroupBy)`). const decodeGroupByFromJson = Schema.decodeUnknownSync(Schema.fromJsonString(AlertGroupBy)) @@ -89,6 +94,7 @@ export const AlertRuleRowSchema = Schema.Struct({ service_names_json: Schema.NullOr(Schema.Unknown), exclude_service_names_json: Schema.NullOr(Schema.Unknown), environments_json: Schema.NullOr(Schema.Unknown), + anomaly_signals_json: Schema.optionalKey(Schema.NullOr(Schema.Unknown)), tags_json: Schema.NullOr(Schema.Unknown), signal_type: Schema.String, comparator: Schema.String, @@ -173,6 +179,8 @@ export const AlertIncidentRowSchema = Schema.Struct({ acknowledged_by_name: Schema.optionalKey(Schema.NullOr(Schema.String)), acknowledged_via: Schema.optionalKey(Schema.NullOr(Schema.String)), error_issue_id: Schema.NullOr(Schema.String), + anomaly_incident_id: Schema.optionalKey(Schema.NullOr(Schema.String)), + group_label: Schema.optionalKey(Schema.NullOr(Schema.String)), created_at: Schema.String, updated_at: Schema.String, }) @@ -238,6 +246,13 @@ export const rowToAlertRuleDocument = ( serviceNames: [...safeParseStringArray(row.service_names_json)], excludeServiceNames: [...safeParseStringArray(row.exclude_service_names_json)], environments: [...safeParseStringArray(row.environments_json)], + ...(row.signal_type === "anomaly" + ? { + anomalySignals: [ + ...Option.getOrElse(decodeAnomalySignals(row.anomaly_signals_json ?? []), () => []), + ], + } + : undefined), tags: [...safeParseStringArray(row.tags_json)], groupBy: row.group_by == null ? null : decodeGroupByFromJson(row.group_by), signalType: asSignalType(row.signal_type), @@ -310,6 +325,15 @@ export const rowToAlertIncidentDocument = (row: AlertIncidentRow): AlertIncident acknowledgedVia: row.acknowledged_via != null ? Option.getOrNull(decodeAckSource(row.acknowledged_via)) : null, errorIssueId: row.error_issue_id != null ? asErrorIssueId(row.error_issue_id) : null, + ...(row.signal_type === "anomaly" + ? { + anomalyIncidentId: + row.anomaly_incident_id == null + ? null + : Option.getOrNull(decodeAnomalyIncidentId(row.anomaly_incident_id)), + } + : undefined), + ...(row.group_label == null ? undefined : { groupLabel: row.group_label }), }) // alert_destinations From b518097e049bee9b8c9bce5bfffaf010a7289e1b Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 19:15:06 +0100 Subject: [PATCH 02/28] feat(alerts): silences match an anomaly incident's service and environment An anomaly rule's group key is the anomaly incident id and the default rule has no scope, so a silence on a service never muted anomalies in it. Incidents now record labels (service, environment); silence matching prefers them over the rule's scope and group key, at open, on reminders, on the owed page after a silence ends and on resolves. --- apps/ai/src/mcp/tool-descriptors.generated.ts | 4 +- .../ai/src/mcp/tools/get-incident-timeline.ts | 1 + apps/ai/src/mcp/tools/list-alert-incidents.ts | 1 + .../api/src/routes/v2/alert-incidents.http.ts | 1 + apps/api/src/routes/v2/alerts.http.test.ts | 36 + .../MapleAPI/Sources/MapleAPI/openapi.json | 21 + .../services/alerts/AlertReadModelsService.ts | 1 + .../AlertsService.anomaly-silences.test.ts | 214 + .../src/services/alerts/AlertsService.ts | 35 +- .../migration.sql | 2 + .../snapshot.json | 19345 ++++++++++++++++ packages/db/src/tables/alerts.ts | 4 +- .../domain/src/http/alert-silences.test.ts | 38 + packages/domain/src/http/alert-silences.ts | 8 +- packages/domain/src/http/alerts.ts | 9 + .../domain/src/http/v2/alert-incidents.ts | 21 + packages/domain/src/mcp-outputs/alerts.ts | 5 + .../web-data/src/collections/alerts.test.ts | 12 + packages/web-data/src/collections/alerts.ts | 8 + 19 files changed, 19760 insertions(+), 6 deletions(-) create mode 100644 packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts create mode 100644 packages/db/drizzle/20261106180930_alert_incident_labels/migration.sql create mode 100644 packages/db/drizzle/20261106180930_alert_incident_labels/snapshot.json diff --git a/apps/ai/src/mcp/tool-descriptors.generated.ts b/apps/ai/src/mcp/tool-descriptors.generated.ts index 094659b483..8c43b51a34 100644 --- a/apps/ai/src/mcp/tool-descriptors.generated.ts +++ b/apps/ai/src/mcp/tool-descriptors.generated.ts @@ -30,10 +30,10 @@ export const MCP_TOOL_DESCRIPTORS: ReadonlyArray = [ {"name":"list_alert_rules","title":"List Alert Rules","description":"List configured alert rules with severity, signal type and condition. Use list_alert_incidents for what has fired.","inputSchema":{"type":"object","properties":{"services":{"type":"array","items":{"type":"string"},"description":"Only rules covering any of these services: scoped to them, scoped to every service, or raw_query rules whose SQL or name mentions them"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"Only rules on this signal type"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only rules with this severity"},"enabled_only":{"type":"boolean","description":"Only enabled rules"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"services":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"severity":{"type":"string"},"enabledOnly":{"type":"boolean"}},"required":["rules","total"],"additionalProperties":true},"annotations":{"title":"List Alert Rules","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"list_alert_destinations","title":"List Alert Destinations","description":"List the notification destinations a rule can deliver to, with id, type and delivery health. Pass the ids as destination_ids to create_alert_rule / update_alert_rule.","inputSchema":{"type":"object","properties":{"type":{"type":"string","enum":["pagerduty","webhook","discord","telegram","google-chat","microsoft-teams","incident-io","email","chat","linear"],"description":"Only destinations of this type"},"enabled_only":{"type":"boolean","description":"Only enabled destinations"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"publicId":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"enabled":{"type":"boolean"},"summary":{"type":"string"},"channelLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastTestedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastTestError":{"anyOf":[{"type":"string"},{"type":"null"}]},"disabledReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","publicId","name","type","enabled","summary","channelLabel","lastTestedAt","lastTestError","disabledReason","createdAt","updatedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"type":{"type":"string"},"enabledOnly":{"type":"boolean"}},"required":["destinations","total"],"additionalProperties":true},"annotations":{"title":"List Alert Destinations","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"get_alert_rule","title":"Get Alert Rule","description":"Get full configuration details of a specific alert rule including thresholds, service filters, evaluation settings, and notification destinations. Use list_alert_rules to find rule IDs.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"}},"required":["rule_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"excludeServiceNames":{"type":"array","items":{"type":"string"}},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noDataBehavior":{"type":"string"},"consecutiveBreachesRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"consecutiveHealthyRequired":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"renotifyIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"apdexThresholdMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"queryBuilderDraft":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"anyOf":[{"type":"string","enum":["default","meter"]},{"type":"null"}]},"metricName":{"anyOf":[{"type":"string"},{"type":"null"}]},"metricType":{"anyOf":[{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},{"type":"null"}]},"isMonotonic":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"spaceAggregation":{"anyOf":[{"type":"string","enum":["sum","avg","min","max","count"]},{"type":"null"}]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}]},"whereClause":{"anyOf":[{"type":"string"},{"type":"null"}]},"aggregation":{"type":"string"},"stepInterval":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderByDirection":{"anyOf":[{"type":"string","enum":["desc","asc"]},{"type":"null"}]},"addOns":{"anyOf":[{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":true},{"type":"null"}]},"groupBy":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"having":{"anyOf":[{"type":"string"},{"type":"null"}]},"orderBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"limit":{"anyOf":[{"type":"string"},{"type":"null"}]},"seriesLimit":{"anyOf":[{"type":"string"},{"type":"null"}]},"legend":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":true},{"type":"null"}]},"rawQuerySql":{"anyOf":[{"type":"string"},{"type":"null"}]},"rawQueryReducer":{"anyOf":[{"type":"string"},{"type":"null"}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"notificationTitle":{"anyOf":[{"type":"string"},{"type":"null"}]},"notificationBody":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationError":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluationErrorKind":{"anyOf":[{"type":"string"},{"type":"null"}]},"slowedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastEvaluatedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"destinations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":{"anyOf":[{"type":"string"},{"type":"null"}]},"enabled":{"type":"boolean"}},"required":["id","name","type","enabled"],"additionalProperties":true}}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt","excludeServiceNames","groupBy","minimumSampleCount","noDataBehavior","consecutiveBreachesRequired","consecutiveHealthyRequired","renotifyIntervalMinutes","apdexThresholdMs","queryBuilderDraft","rawQuerySql","rawQueryReducer"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Get Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"list_alert_incidents","title":"List Alert Incidents","description":"Alert incidents: alert-rule breaches, open and resolved, with severity, group, condition and last observed value (flare-ups of an error issue are list_error_incidents). For one rule's trigger, notify and resolve timestamps use get_incident_timeline.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"acknowledged":{"type":"boolean","description":"true: open incidents someone acknowledged; false: open incidents nobody has. Implies status=open"},"limit":{"type":"number","description":"Max incidents to return (default 50, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"holdReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"heldSince":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"firstTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"anomalyIncidentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","holdReason","heldSince","threshold","comparator","firstTriggeredAt","resolvedAt","lastObservedValue"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"recentlyResolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"List Alert Incidents","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"list_alert_incidents","title":"List Alert Incidents","description":"Alert incidents: alert-rule breaches, open and resolved, with severity, group, condition and last observed value (flare-ups of an error issue are list_error_incidents). For one rule's trigger, notify and resolve timestamps use get_incident_timeline.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"acknowledged":{"type":"boolean","description":"true: open incidents someone acknowledged; false: open incidents nobody has. Implies status=open"},"limit":{"type":"number","description":"Max incidents to return (default 50, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"holdReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"heldSince":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"firstTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"anomalyIncidentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"labels":{"anyOf":[{"type":"object","properties":{"service":{"type":"string"},"environment":{"type":"string"}},"additionalProperties":true},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","holdReason","heldSince","threshold","comparator","firstTriggeredAt","resolvedAt","lastObservedValue"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"recentlyResolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"List Alert Incidents","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"list_alert_checks","title":"List Alert Checks","description":"List a rule's recent checks (one row per evaluation) with observed value, threshold, sample count and incident transition. Use it to tune thresholds, diagnose flapping, or see the near-misses before a breach.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"},"group_key":{"type":"string","description":"Only checks for this exact group key"},"status":{"type":"string","enum":["breached","healthy","skipped","error"],"description":"Only checks with this status"},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 7 days before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."},"limit":{"type":"number","description":"Max checks to return (default 100, max 2000)"}},"required":["rule_id"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ruleId":{"type":"string"},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"breached":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"healthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"skipped":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noData":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"belowMinSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"errored":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"transitions":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"checks":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string"},"groupKey":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"observedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowStart":{"type":"string"},"windowEnd":{"type":"string"},"consecutiveBreaches":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"consecutiveHealthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"incidentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"incidentTransition":{"type":"string"},"evaluationDurationMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorCategory":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["timestamp","groupKey","status","skipReason","observedValue","threshold","comparator","sampleCount","windowStart","windowEnd","consecutiveBreaches","consecutiveHealthy","incidentId","incidentTransition","evaluationDurationMs","errorMessage","errorCategory"],"additionalProperties":true}},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"groupKey":{"type":"string"},"status":{"type":"string"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["ruleId","total","breached","healthy","skipped","noData","belowMinSamples","errored","transitions","checks"],"additionalProperties":true},"annotations":{"title":"List Alert Checks","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"preview_alert_rule","title":"Preview Alert Rule","description":"Replay an alert rule over past data without saving it: per evaluation window, the value, sample count and verdict the scheduler would produce (breached, healthy, or skipped with the reason), plus when it would have fired. Pass rule_id to preview a saved rule, optionally with overrides, or a create_alert_rule definition to dry-run a new one. Run it before saving a raw_query rule.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Existing rule to preview (list_alert_rules). Definition params you also pass override its saved config, so a change can be tried before update_alert_rule. `template` is ignored with rule_id.: its UUID or its REST id (alrt_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents (see anomaly_signals). All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"start_time":{"type":"string","description":"Window start, UTC (YYYY-MM-DD HH:mm:ss or ISO 8601). Default: 1 day before end_time. Covers at most 7 days; a wider window is narrowed to the 7 days before end_time."},"end_time":{"type":"string","description":"Window end, UTC, same format. Default: now."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"ruleId":{"type":"string"},"timeRange":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"],"additionalProperties":true},"truncatedToStart":{"anyOf":[{"type":"string"},{"type":"null"}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"thresholdUpper":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"minimumSampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"groups":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"windows":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"breached":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"healthy":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noData":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"belowMinSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"noValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"minValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"maxValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"totalSamples":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","windows","breached","healthy","noData","belowMinSamples","noValue","minValue","maxValue","totalSamples"],"additionalProperties":true}},"points":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"bucket":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"provisional":{"type":"boolean"}},"required":["groupKey","bucket","status","skipReason","value","sampleCount","provisional"],"additionalProperties":true}},"wouldFire":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"}},"required":["groupKey","start","end"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"seriesFailure":{"type":"string","enum":["query_too_slow","query_too_much_memory","not_time_grouped"]},"queryTiming":{"type":"object","properties":{"elapsedMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"failure":{"anyOf":[{"type":"string"},{"type":"null"}]},"probeLimitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"intervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"limitMs":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"fitsIntervalMinutes":{"type":"array","items":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}}},"required":["elapsedMs","failure","probeLimitMs","intervalMinutes","limitMs","fitsIntervalMinutes"],"additionalProperties":true},"performanceHints":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"line":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["code","message","line"],"additionalProperties":true}}},"required":["timeRange","truncatedToStart","windowMinutes","comparator","threshold","thresholdUpper","minimumSampleCount","groups","points","wouldFire","warnings"],"additionalProperties":true},"annotations":{"title":"Preview Alert Rule","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, - {"name":"get_incident_timeline","title":"Incident Timeline","description":"Alert-rule incidents (not error-issue incidents) for one rule or all, with first and last trigger, last notification and resolution timestamps: the per-incident detail behind list_alert_incidents. Pass rule_id to follow one rule over time.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Only incidents of this rule (ids from list_alert_rules): its UUID or its REST id (alrt_\u2026)"},"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"limit":{"type":"number","description":"Max incidents to return (default 20, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"firstTriggeredAt":{"type":"string"},"lastTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastNotifiedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","comparator","threshold","lastObservedValue","firstTriggeredAt","lastTriggeredAt","resolvedAt","lastNotifiedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"ruleId":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"Incident Timeline","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, + {"name":"get_incident_timeline","title":"Incident Timeline","description":"Alert-rule incidents (not error-issue incidents) for one rule or all, with first and last trigger, last notification and resolution timestamps: the per-incident detail behind list_alert_incidents. Pass rule_id to follow one rule over time.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Only incidents of this rule (ids from list_alert_rules): its UUID or its REST id (alrt_\u2026)"},"status":{"type":"string","enum":["open","resolved"],"description":"Only incidents in this status"},"severity":{"type":"string","enum":["warning","critical"],"description":"Only incidents with this severity"},"group_key":{"type":"string","description":"Only incidents with this exact group key"},"limit":{"type":"number","description":"Max incidents to return (default 20, max 200)"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"incidents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ruleId":{"type":"string"},"ruleName":{"type":"string"},"groupKey":{"anyOf":[{"type":"string"},{"type":"null"}]},"groupLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"labels":{"anyOf":[{"type":"object","properties":{"service":{"type":"string"},"environment":{"type":"string"}},"additionalProperties":true},{"type":"null"}]},"signalType":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"lastObservedValue":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"firstTriggeredAt":{"type":"string"},"lastTriggeredAt":{"type":"string"},"resolvedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"lastNotifiedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"acknowledgedBy":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","ruleId","ruleName","groupKey","signalType","severity","status","comparator","threshold","lastObservedValue","firstTriggeredAt","lastTriggeredAt","resolvedAt","lastNotifiedAt"],"additionalProperties":true}},"total":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"openCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"resolvedCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"ruleId":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"groupKey":{"type":"string"},"acknowledged":{"type":"boolean"},"limit":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"hasMore":{"type":"boolean"}},"required":["incidents","total","openCount","resolvedCount"],"additionalProperties":true},"annotations":{"title":"Incident Timeline","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"create_alert_rule","title":"Create Alert Rule","description":"Create an alert rule. Pick a `template` for the common cases; otherwise pass signal_type, comparator and threshold yourself. destination_ids is required (at least one, from list_alert_destinations).","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Rule name"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Required, at least one: destination IDs to notify, from list_alert_destinations. Each is its UUID or its REST id (dest_\u2026)"},"template":{"type":"string","enum":["high_error_rate","slow_p95","slow_p99","low_apdex","throughput_drop","custom"],"description":"Preset for signal_type, comparator and threshold; signal_type and comparator you pass are ignored, threshold overrides. high_error_rate: error_rate > 0.05, grouped by service.name unless services is set. slow_p95: p95_latency > 1000 ms. slow_p99: p99_latency > 2000 ms. low_apdex: apdex < 0.8 with apdex_threshold_ms 500. throughput_drop: throughput < 100 rpm. Omit (or pass custom) to set the three yourself."},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity (default: warning)"},"threshold":{"type":"number","description":"The value compared against. Units: error_rate and apdex are 0-1 ratios, latency is milliseconds, throughput is requests per minute. Overrides the template's."},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440 (default: 5)"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit (or pass \"auto\") to derive it: a fifth of the window, 1 to 5."},"services":{"type":"array","items":{"type":"string"},"description":"Service names to scope the alert to"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (e.g. 'production'). Omit for all environments. Ignored for builder_query / raw_query, which filter inside their own query."},"enabled":{"type":"boolean","description":"Whether the rule evaluates (default true)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents (see anomaly_signals). All other signals are computed over ROOT spans only: a service that fails on child spans but returns success from its entry point (cron jobs, workers; audit_setup STAT-04 lists them) reads healthy at any threshold. Use raw_query there or rely on error-issue notifications."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold (breach when `value threshold`). Ignored when a template is set."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does."},"group_by":{"type":"array","items":{"type":"string"},"description":"Evaluate one value per group. Built-in tokens: service.name, span.name, status.code, http.method, severity; attribute keys as attr. (e.g. attr.http.route)."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window (default: 0). For raw_query it sums the `samples` column; without one each returned row counts as 1, so it gates on buckets, not events."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach, so a rule whose query stops matching opens an incident instead of going quiet (default false: such windows are skipped). Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting (default: 2)"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving (default: 2)"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes (default: 30)"},"apdex_threshold_ms":{"type":"number","description":"Response time counted as satisfactory, in ms. Required for signal_type=apdex."},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"The query to evaluate, in the query-builder draft shape dashboard custom-query widgets use ({ id, name, dataSource, aggregation, whereClause, groupBy, ... }). Required for signal_type=builder_query."},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column and optional `group` / `samples` columns (`samples` is the event count behind each row; it feeds minimum_sample_count). A query returning no rows is a no-data check, not a healthy one. Must reference $__orgFilter and $__timeFilter(col); $__startTime, $__endTime and $__interval_s are also available. At most 100 distinct groups. Required for signal_type=raw_query."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How several raw_query rows collapse into one value (default identity: the single row's value)."},"notification_title":{"type":"string","description":"Custom notification title template. Supports {{ variable }} substitution, e.g. \"{{ event.emoji }} {{ rule.name }}: {{ event.label }}\". Omit for the built-in format. Variables: rule.name, severity, signal.label, comparator.label, threshold, value, observed.summary, group, window, links.app, links.chat."},"notification_body":{"type":"string","description":"Custom notification body template (Markdown). Supports {{ variable }} substitution and {{#if key}}\u2026{{/if}} blocks, e.g. \"*Severity:* {{ severity }}\\n*Observed:* {{ observed.summary }}\". Omit for the built-in format."}},"required":["name","destination_ids"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Create Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}}, {"name":"update_alert_rule","title":"Update Alert Rule","description":"Update an alert rule. Pass only the fields to change; the rest keep their current value (get_alert_rule shows it). rule_ids applies one change to many rules (e.g. add_destination_ids on every rule with rule_ids=[\"*\"]). Ids from list_alert_rules and list_alert_destinations.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID to update (use list_alert_rules to find IDs): its UUID or its REST id (alrt_\u2026)"},"rule_ids":{"type":"array","items":{"type":"string"},"description":"Apply the same change to several rules in one call instead of rule_id; [\"*\"] means every rule. name cannot be set in bulk. Each is its UUID or its REST id (alrt_\u2026)"},"name":{"type":"string","description":"New rule name"},"severity":{"type":"string","enum":["warning","critical"],"description":"Alert severity"},"threshold":{"type":"number","description":"Threshold value. E.g. 0.05 for 5% error rate, 1000 for 1s latency"},"window_minutes":{"type":"number","description":"Evaluation window in minutes, at most 1440"},"evaluation_interval_minutes":{"anyOf":[{"type":"number","enum":[1,5,10,15,30,60]},{"type":"string"},{"type":"string","enum":["auto"]}],"description":"Minutes between checks, at most window_minutes. A raw_query rule checked less often gets a longer query time limit: 5s under 5 min, 15s at 5, 25s at 10 or more. Omit to keep the current one; pass \"auto\" to derive it from the window (a fifth, 1 to 5). Any edit also clears a slowdown the scheduler applied."},"services":{"type":"array","items":{"type":"string"},"description":"Service scope (replaces the current one; an empty list means all services)"},"environments":{"type":"array","items":{"type":"string"},"description":"Deployment environments to scope the alert to (replaces the current scope; pass an empty list for all environments). Ignored for builder_query / raw_query."},"enabled":{"type":"boolean","description":"Whether the rule is enabled"},"destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to notify, from list_alert_destinations; replaces the current ones. A rule needs at least one. Each is its UUID or its REST id (dest_\u2026)"},"add_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to add, keeping the current ones. Each is its UUID or its REST id (dest_\u2026)"},"remove_destination_ids":{"type":"array","items":{"type":"string"},"description":"Destination IDs to remove, keeping the rest. Each is its UUID or its REST id (dest_\u2026)"},"signal_type":{"type":"string","enum":["error_rate","p95_latency","p99_latency","apdex","throughput","builder_query","raw_query","anomaly"],"description":"What the rule measures. builder_query takes query_builder_draft, raw_query takes raw_query_sql, anomaly pages on the anomaly detector's incidents."},"anomaly_signals":{"type":"array","items":{"type":"string","enum":["error_rate","latency_p95","throughput","error_spike","log_volume"]},"description":"signal_type=anomaly only: detector signals the rule pages on (empty or omitted: every signal). An anomaly rule takes no comparator, threshold or window: it opens when the anomaly detector opens an anomaly in its scope (services, environments, anomaly_signals) at or above its severity, and resolves when that anomaly does. Replaces the current list; an empty list means every signal."},"comparator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"],"description":"How the observed value is compared with threshold"},"group_by":{"type":"array","items":{"type":"string"},"description":"Dimensions to evaluate the alert per-group (replaces the current grouping; an empty list removes it). Built-in tokens: service.name, span.name, status.code, http.method, severity. Attribute keys: attr.."},"minimum_sample_count":{"type":"number","description":"Skip evaluation below this many samples in the window. For raw_query it sums the `samples` column; without one each returned row counts as 1."},"alert_on_no_data":{"type":"boolean","description":"Count a window with no data as a breach instead of skipping it. Not supported with group_by."},"consecutive_breaches":{"type":"number","description":"Consecutive breaches before alerting"},"consecutive_healthy":{"type":"number","description":"Consecutive healthy evaluations before resolving"},"renotify_interval_minutes":{"type":"number","description":"Re-notification interval in minutes"},"apdex_threshold_ms":{"type":"number","description":"Apdex threshold in milliseconds (for signal_type=apdex)"},"query_builder_draft":{"type":"string","contentMediaType":"application/json","contentSchema":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["traces"]},"valueField":{"type":"string"}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["logs"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["metrics"]},"signalSource":{"type":"string","enum":["default","meter"]},"metricName":{"type":"string"},"metricType":{"type":"string","enum":["sum","gauge","histogram","exponential_histogram"]},"isMonotonic":{"type":"boolean"},"spaceAggregation":{"type":"string","enum":["sum","avg","min","max","count"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"hidden":{"type":"boolean"},"whereClause":{"type":"string"},"aggregation":{"type":"string"},"stepInterval":{"type":"string"},"orderByDirection":{"type":"string","enum":["desc","asc"]},"addOns":{"type":"object","properties":{"groupBy":{"type":"boolean"},"having":{"type":"boolean"},"orderBy":{"type":"boolean"},"limit":{"type":"boolean"},"legend":{"type":"boolean"}},"required":["groupBy","having","orderBy","limit","legend"],"additionalProperties":false},"groupBy":{"type":"array","items":{"type":"string"}},"having":{"type":"string"},"orderBy":{"type":"string"},"limit":{"type":"string"},"seriesLimit":{"type":"string"},"legend":{"type":"string"},"dataSource":{"type":"string","enum":["product_events"]}},"required":["id","name","aggregation","dataSource"],"additionalProperties":false}]},"description":"Query-builder draft, same shape as create_alert_rule (for signal_type=builder_query)"},"raw_query_sql":{"type":"string","description":"ClickHouse SQL returning a numeric `value` column (for signal_type=raw_query), at most 100 groups. Must reference $__orgFilter and $__timeFilter(col)."},"raw_query_reducer":{"type":"string","enum":["identity","sum","avg","max","min"],"description":"How to collapse raw_query result rows into one value."},"notification_title":{"type":"string","description":"Notification title template with {{ variable }} substitution; variables as in create_alert_rule"},"notification_body":{"type":"string","description":"Notification body template (Markdown) with {{ variable }} and {{#if key}}...{{/if}}"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"rule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true},"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"severity":{"type":"string"},"serviceNames":{"type":"array","items":{"type":"string"}},"environments":{"type":"array","items":{"type":"string"}},"anomalySignals":{"type":"array","items":{"type":"string"}},"signalType":{"type":"string"},"comparator":{"type":"string"},"threshold":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"windowMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"evaluationIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"slowedIntervalMinutes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"destinationIds":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","name","enabled","severity","serviceNames","environments","signalType","comparator","threshold","windowMinutes","evaluationIntervalMinutes","slowedIntervalMinutes","destinationIds","createdAt","updatedAt"],"additionalProperties":true}},"warnings":{"type":"array","items":{"type":"string"}},"evaluation":{"type":"object","properties":{"nextEvaluationAt":{"anyOf":[{"type":"string"},{"type":"null"}]},"current":{"type":"array","items":{"type":"object","properties":{"groupKey":{"type":"string"},"window":{"type":"string"},"status":{"type":"string"},"skipReason":{"anyOf":[{"type":"string"},{"type":"null"}]},"value":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]},{"type":"null"}]},"sampleCount":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]}},"required":["groupKey","window","status","skipReason","value","sampleCount"],"additionalProperties":true}},"previewError":{"type":"string"}},"required":["nextEvaluationAt","current"],"additionalProperties":true}},"required":["rule"],"additionalProperties":true},"annotations":{"title":"Update Alert Rule","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}}, {"name":"delete_alert_rule","title":"Delete Alert Rule","description":"Permanently delete an alert rule together with its incident history, delivery events and evaluation state. Irreversible; requires confirm=true. Ids from list_alert_rules.","inputSchema":{"type":"object","properties":{"rule_id":{"type":"string","description":"Alert rule ID: its UUID or its REST id (alrt_\u2026)"},"confirm":{"type":"boolean","description":"Must be true. There is no undo."}},"required":["rule_id","confirm"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":true},"annotations":{"title":"Delete Alert Rule","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}}, diff --git a/apps/ai/src/mcp/tools/get-incident-timeline.ts b/apps/ai/src/mcp/tools/get-incident-timeline.ts index 10898d6cd8..7f46ea9fdd 100644 --- a/apps/ai/src/mcp/tools/get-incident-timeline.ts +++ b/apps/ai/src/mcp/tools/get-incident-timeline.ts @@ -80,6 +80,7 @@ export function registerGetIncidentTimelineTool(server: McpToolRegistrar) { ruleName: i.ruleName, groupKey: i.groupKey, ...(i.groupLabel === undefined ? undefined : { groupLabel: i.groupLabel }), + ...(i.labels === undefined ? undefined : { labels: i.labels }), signalType: i.signalType, severity: i.severity, status: i.status, diff --git a/apps/ai/src/mcp/tools/list-alert-incidents.ts b/apps/ai/src/mcp/tools/list-alert-incidents.ts index a39a372b60..a116de605c 100644 --- a/apps/ai/src/mcp/tools/list-alert-incidents.ts +++ b/apps/ai/src/mcp/tools/list-alert-incidents.ts @@ -123,6 +123,7 @@ export function registerListAlertIncidentsTool(server: McpToolRegistrar) { ? undefined : { anomalyIncidentId: i.anomalyIncidentId }), ...(i.groupLabel === undefined ? undefined : { groupLabel: i.groupLabel }), + ...(i.labels === undefined ? undefined : { labels: i.labels }), })), total: incidents.length, openCount, diff --git a/apps/api/src/routes/v2/alert-incidents.http.ts b/apps/api/src/routes/v2/alert-incidents.http.ts index 98b2785986..97033234ec 100644 --- a/apps/api/src/routes/v2/alert-incidents.http.ts +++ b/apps/api/src/routes/v2/alert-incidents.http.ts @@ -48,6 +48,7 @@ const toV2Incident = (doc: AlertIncidentDocument): V2AlertIncident => ({ error_issue_id: doc.errorIssueId, ...(doc.anomalyIncidentId === undefined ? undefined : { anomaly_incident_id: doc.anomalyIncidentId }), ...(doc.groupLabel === undefined ? undefined : { group_label: doc.groupLabel }), + ...(doc.labels === undefined ? undefined : { labels: doc.labels }), silence_id: doc.silenceId, silenced_at: timestampOrNull(doc.silencedAt), }) diff --git a/apps/api/src/routes/v2/alerts.http.test.ts b/apps/api/src/routes/v2/alerts.http.test.ts index 284be35253..07942c9c9a 100644 --- a/apps/api/src/routes/v2/alerts.http.test.ts +++ b/apps/api/src/routes/v2/alerts.http.test.ts @@ -1209,6 +1209,42 @@ describe("v2 alerts over HTTP", () => { await harness.dispose() }) + it("lists an incident's labels, and omits them when none are recorded", async () => { + const harness = makeHarness() + const key = await harness.bootstrapKey(["alerts:write"]) + await createWebhookAndRule(harness, key.secret) + await executeSql( + harness.testDb, + `insert into alert_incidents ( + id, org_id, rule_id, incident_key, rule_name, group_key, signal_type, severity, status, + comparator, threshold, first_triggered_at, last_triggered_at, resolved_at, dedupe_key, + labels_json, created_at, updated_at + ) + select v.id, rules.org_id, rules.id, v.id, 'Checkout error rate', '__total__', 'error_rate', + 'critical', v.status, 'gt', 0.05, now(), now(), v.resolved_at, v.id, v.labels, now(), now() + from (values + ('00000000-0000-4000-8000-0000000000b1', 'open', null::timestamptz, + '{"service":"checkout","environment":"production"}'::jsonb), + ('00000000-0000-4000-8000-0000000000b2', 'resolved', now(), null::jsonb) + ) as v(id, status, resolved_at, labels) + cross join lateral ( + select id, org_id from alert_rules where org_id = 'org_alerts_e2e' limit 1 + ) as rules`, + ) + const list = await harness.request("GET", "/v2/alerts/incidents", key.secret) + expect(list.status).toBe(200) + const labelsByKey = Arr.map(list.body.data, (incident: { dedupe_key: string; labels?: unknown }) => [ + incident.dedupe_key, + incident.labels, + ]) + expect(labelsByKey).toEqual( + expect.arrayContaining([ + ["00000000-0000-4000-8000-0000000000b1", { service: "checkout", environment: "production" }], + ["00000000-0000-4000-8000-0000000000b2", undefined], + ]), + ) + }) + it("acknowledges and unacknowledges an open incident and lists its events", async () => { const harness = makeHarness() const key = await harness.bootstrapKey(["alerts:write"]) diff --git a/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json b/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json index 1e3ae55dd7..f8c90655f8 100644 --- a/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json +++ b/apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json @@ -390,6 +390,27 @@ "id": { "$ref": "#/components/schemas/_maple_AlertIncidentId" }, + "labels": { + "additionalProperties": false, + "description": "The service and environment the incident is about, for rules whose `group_key` does not say (today `anomaly` rules). Alert silences match `services` and `environments` against these first. Absent when not recorded.", + "properties": { + "environment": { + "description": "The deployment environment the incident is about.", + "examples": [ + "production" + ], + "type": "string" + }, + "service": { + "description": "The service the incident is about.", + "examples": [ + "checkout" + ], + "type": "string" + } + }, + "type": "object" + }, "last_delivered_event_type": { "$ref": "#/components/schemas/_maple_AlertEventType", "description": "The most recent notification event delivered for this incident (`trigger`, `resolve`, `renotify`, `test`), or `null`." diff --git a/packages/backend/src/services/alerts/AlertReadModelsService.ts b/packages/backend/src/services/alerts/AlertReadModelsService.ts index cf946c5975..4ff22a040b 100644 --- a/packages/backend/src/services/alerts/AlertReadModelsService.ts +++ b/packages/backend/src/services/alerts/AlertReadModelsService.ts @@ -194,6 +194,7 @@ export const rowToIncidentDocument = (row: AlertIncidentRow) => errorIssueId: row.errorIssueId != null ? decodeErrorIssueIdSync(row.errorIssueId) : null, ...(row.signalType === "anomaly" ? { anomalyIncidentId: row.anomalyIncidentId } : undefined), ...(row.groupLabel === null ? undefined : { groupLabel: row.groupLabel }), + ...(row.labelsJson === null ? undefined : { labels: row.labelsJson }), silenceId: row.silenceId, silencedAt: toIso(row.silencedAt), }) diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts new file mode 100644 index 0000000000..c88533f117 --- /dev/null +++ b/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts @@ -0,0 +1,214 @@ +import { afterEach, assert, describe, expect, it } from "@effect/vitest" +import * as PG from "@maple-dev/effect-orm/postgres" +import { AnomalyIncidents } from "@maple/db/tables" +import { + AlertRuleUpsertRequest, + AlertSilenceCreateRequest, + AnomalyIncidentId, + IsoDateTimeString, + type AlertSilenceMatchers, +} from "@maple/domain/http" +import { Array as Arr, Clock, Duration, Effect, Layer, Schema } from "effect" +import { TestClock } from "effect/testing" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { cleanupTestDbs, createTestDb, type TestDb } from "@maple/backend/platform/test-pglite" +import { AlertsService, type AlertsServiceApi } from "./AlertsService" +import { AlertSilencesService } from "./AlertSilencesService" +import { AnomalyAlertSource } from "./anomaly/AnomalyAlertSource" +import { + DEFAULT_CLOCK_EPOCH_MS, + adminRoles, + asOrgId, + asUserId, + createWebhookDestination, + makeLayer, + makeWarehouseStub, +} from "./alerts-service-test-support" + +const trackedDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(trackedDbs)) + +const orgId = asOrgId("org_anomaly_silences") +const userId = asUserId("user_anomaly_silences") +const anomalyId = Schema.decodeSync(AnomalyIncidentId)("00000000-0000-4000-8000-00000000b001") +const iso = Schema.decodeUnknownSync(IsoDateTimeString) + +const harness = () => { + const testDb = createTestDb(trackedDbs) + const sent: Array = [] + const fetchImpl: typeof fetch = async (_input, init) => { + sent.push(new Headers(init?.headers).get("x-maple-event-type") ?? "") + return new Response("ok", { status: 200 }) + } + const layer = Layer.mergeAll( + makeLayer(testDb, makeWarehouseStub({}), { fetch: fetchImpl }), + AnomalyAlertSource.layer.pipe(Layer.provide(testDb.layer)), + testDb.layer, + ) + return { sent, layer } +} + +/** An open critical error-rate anomaly in checkout (production). */ +const insertCheckoutAnomaly = Effect.gen(function* () { + const database = yield* Database + const now = yield* Clock.currentTimeMillis + yield* database.execute((db) => + db.run( + PG.insertInto(AnomalyIncidents).values({ + id: anomalyId, + orgId, + detectorKey: "error_rate:production:checkout", + signalType: "error_rate", + serviceName: "checkout", + deploymentEnv: "production", + fingerprintHash: null, + errorIssueId: null, + status: "open", + severity: "critical", + openedValue: 0.042, + baselineMedian: 0.003, + baselineSigma: 0.001, + thresholdValue: 0.006, + lastObservedValue: 0.042, + firstTriggeredAt: now, + lastTriggeredAt: now, + resolvedAt: null, + resolveReason: null, + dedupeKey: `dedupe-${anomalyId}`, + createdAt: now, + updatedAt: now, + }), + ), + ) +}).pipe(Effect.orDie) + +/** An anomaly rule over every service, like the default one, whose critical tier pages a webhook. */ +const createUnscopedAnomalyRule = (alerts: AlertsServiceApi) => + Effect.gen(function* () { + const destination = yield* createWebhookDestination(alerts, orgId, userId) + return yield* alerts.createRule( + orgId, + userId, + adminRoles, + new AlertRuleUpsertRequest({ + name: "Anomalies", + enabled: true, + signalType: "anomaly", + comparator: "gte", + windowMinutes: 5, + thresholds: [ + { severity: "warning", value: 1, upper: null, recoveryValue: null, destinationIds: [] }, + { + severity: "critical", + value: 2, + upper: null, + recoveryValue: null, + destinationIds: [destination.id], + }, + ], + }), + ) + }) + +const silence = (matchers: AlertSilenceMatchers) => + Effect.gen(function* () { + const silences = yield* AlertSilencesService + const now = yield* Clock.currentTimeMillis + return yield* silences.create( + orgId, + userId, + adminRoles, + new AlertSilenceCreateRequest({ + reason: "checkout maintenance", + matchers, + endsAt: iso(new Date(now + 60 * 60_000).toISOString()), + }), + ) + }) + +const ticks = (alerts: AlertsServiceApi, count: number) => + Effect.forEach( + Arr.range(1, count), + () => + Effect.gen(function* () { + yield* TestClock.adjust(Duration.minutes(1)) + yield* alerts.runSchedulerTick() + }), + { discard: true }, + ) + +const deliveries = (alerts: AlertsServiceApi) => + alerts + .listDeliveryEvents(orgId) + .pipe( + Effect.map((page) => + Arr.map(Arr.reverse(page.events), (event) => `${event.eventType}:${event.status}`), + ), + ) + +describe("alert silences on anomaly rules", () => { + it.effect("a service silence holds back the page for an anomaly in that service", () => { + const { sent, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* createUnscopedAnomalyRule(alerts) + yield* insertCheckoutAnomaly + const created = yield* silence({ services: ["checkout"] }) + + yield* ticks(alerts, 1) + + const [incident] = (yield* alerts.listIncidents(orgId)).incidents + expect(incident).toMatchObject({ + status: "open", + severity: "critical", + labels: { service: "checkout", environment: "production" }, + silenceId: created.id, + }) + assert.isNotNull(incident?.silencedAt) + assert.deepStrictEqual(yield* deliveries(alerts), ["trigger:silenced"]) + assert.deepStrictEqual(sent, []) + }).pipe(Effect.provide(layer)) + }) + + it.effect("a silence on another service leaves the anomaly paging", () => { + const { sent, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* createUnscopedAnomalyRule(alerts) + yield* insertCheckoutAnomaly + yield* silence({ services: ["payments"] }) + + yield* ticks(alerts, 1) + + const [incident] = (yield* alerts.listIncidents(orgId)).incidents + assert.isNull(incident?.silenceId) + assert.deepStrictEqual(yield* deliveries(alerts), ["trigger:success"]) + assert.deepStrictEqual(sent, ["trigger"]) + }).pipe(Effect.provide(layer)) + }) + + it.effect("sends the held-back page once when the silence ends while the anomaly is open", () => { + const { sent, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const silences = yield* AlertSilencesService + yield* createUnscopedAnomalyRule(alerts) + yield* insertCheckoutAnomaly + const created = yield* silence({ services: ["checkout"] }) + yield* ticks(alerts, 2) + assert.deepStrictEqual(sent, []) + + yield* silences.end(orgId, userId, adminRoles, created.id) + yield* ticks(alerts, 3) + + const [incident] = (yield* alerts.listIncidents(orgId)).incidents + assert.deepStrictEqual(yield* deliveries(alerts), ["trigger:silenced", "trigger:success"]) + assert.deepStrictEqual(sent, ["trigger"]) + assert.isNull(incident?.silencedAt) + assert.strictEqual(incident?.silenceId, created.id) + }).pipe(Effect.provide(layer)) + }) +}) diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index f2108ae32e..77da813608 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -63,6 +63,7 @@ import { type AlertGroupBy, UNGROUPED_GROUP_KEY, alertSilenceGroupBy, + type AlertIncidentLabels, OrgId, AlertRuleId, type AlertDestinationId, @@ -205,6 +206,23 @@ type EvaluatedRule = import("@maple/alerting-core").AlertEvaluation & { readonly anomaly?: AnomalyAlertObservation } +/** The service and environment an anomaly is about; null when it names neither. */ +const anomalyIncidentLabels = (anomaly: AnomalyAlertObservation): AlertIncidentLabels | null => { + const labels: AlertIncidentLabels = { + ...(anomaly.serviceName === "" ? undefined : { service: anomaly.serviceName }), + ...(anomaly.deploymentEnv === "" ? undefined : { environment: anomaly.deploymentEnv }), + } + return labels.service === undefined && labels.environment === undefined ? null : labels +} + +/** An open incident's labels: this tick's anomaly when it names one, else what the incident stored. */ +const incidentLabels = ( + evaluation: EvaluatedRule, + incident: { readonly labelsJson: AlertIncidentLabels | null }, +): AlertIncidentLabels | null => + (evaluation.anomaly === undefined ? null : anomalyIncidentLabels(evaluation.anomaly)) ?? + incident.labelsJson + /** * A tick's claim on a rule expired and another tick re-claimed it before this * one finished writing. The rule's remaining writes and notifications belong to @@ -1522,6 +1540,7 @@ export class AlertsService extends Context.Service selectActiveSilences(db, orgId, atMs)) @@ -1532,6 +1551,7 @@ export class AlertsService extends Context.Service published_revision" + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_project_insights", + "name": "supabase_project_insights_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_connection_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connection_id\"", + "\"project_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"log_drain_source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_enabled_kind_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"kind\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_heartbeat_token_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"heartbeat_token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_org_sha_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_repo_sha_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_provider_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"provider\"", + "\"external_installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_installation_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_repo_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"external_repo_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_repo_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "foreign_key", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_linear_issues", + "name": "alert_linear_issues_destination_id_alert_destinations_id_fk", + "columns": [ + "destination_id" + ], + "foreignTable": "alert_destinations", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "aws_connections", + "name": "aws_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "dashboard_shares", + "name": "dashboard_shares_dashboard_fk", + "columns": [ + "org_id", + "dashboard_id" + ], + "foreignTable": "dashboards", + "foreignColumns": [ + "org_id", + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_connectors", + "name": "gcp_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_resources", + "name": "gcp_resources_connector_id_gcp_connectors_id_fk", + "columns": [ + "connector_id" + ], + "foreignTable": "gcp_connectors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oauth_connections", + "name": "oauth_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "planetscale_connections", + "name": "planetscale_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "railway_connections", + "name": "railway_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_id_scrape_targets_id_fk", + "columns": [ + "target_id" + ], + "foreignTable": "scrape_targets", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_targets", + "name": "scrape_targets_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_Bnqlt6wwKJty_fk", + "columns": [ + "component_id" + ], + "foreignTable": "status_page_components", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_tBn6mEz4FenY_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_3LDDUAhznHPM_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incidents", + "name": "status_page_incidents_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_connections", + "name": "supabase_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_project_insights", + "name": "supabase_project_insights_project_id_supabase_projects_id_fk", + "columns": [ + "project_id" + ], + "foreignTable": "supabase_projects", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_connection_id_supabase_connections_id_fk", + "columns": [ + "connection_id" + ], + "foreignTable": "supabase_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_id_sources_id_fk", + "columns": [ + "log_drain_source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vcs_installations", + "name": "vcs_installations_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + } + ] +} diff --git a/packages/db/src/tables/alerts.ts b/packages/db/src/tables/alerts.ts index c259969378..28d92cdbe6 100644 --- a/packages/db/src/tables/alerts.ts +++ b/packages/db/src/tables/alerts.ts @@ -11,7 +11,7 @@ import { OrgId, UserId, } from "@maple/domain/primitives" -import { AnomalySignalType } from "@maple/domain/http" +import { AlertIncidentLabels, AnomalySignalType } from "@maple/domain/http" import { Schema } from "effect" const StringArray = Schema.Array(Schema.String) @@ -240,6 +240,8 @@ const alertIncidentsColumns = { // What people see as the group when the key is not readable, e.g. an anomaly's // "checkout (production)". Null for rules whose group key is its own label. groupLabel: PG.column(PG.nullable(PG.text), { name: "group_label" }), + // The service and environment the incident is about, when the group key does not say. + labelsJson: PG.column(PG.nullable(PG.jsonb(AlertIncidentLabels)), { name: "labels_json" }), /** The silence that last held back one of this incident's notifications. */ silenceId: PG.column(PG.nullable(PG.brand(PG.text, AlertSilenceId)), { name: "silence_id" }), /** diff --git a/packages/domain/src/http/alert-silences.test.ts b/packages/domain/src/http/alert-silences.test.ts index 71c0282cf5..79be246f5e 100644 --- a/packages/domain/src/http/alert-silences.test.ts +++ b/packages/domain/src/http/alert-silences.test.ts @@ -86,6 +86,44 @@ describe("alertSilenceMatches", () => { }) }) +describe("alertSilenceMatches with incident labels", () => { + // The default anomaly rule: no scope, grouped by the anomaly incident id. + const anomaly = (labels: AlertSilenceTarget["labels"]) => + rule({ groupKey: "6f1c2d3e-4b5a-4c6d-8e7f-001122334455", labels }) + const checkoutProduction = { service: "checkout", environment: "production" } + + it.each([ + ["the labelled service", { services: ["checkout"] }, true], + ["the labelled environment", { environments: ["production"] }, true], + [ + "the labelled service AND environment", + { services: ["checkout"], environments: ["production"] }, + true, + ], + ["another service", { services: ["payments"] }, false], + ["the service in another environment", { services: ["checkout"], environments: ["staging"] }, false], + ] as const)("an anomaly labelled checkout/production against %s → %s", (_label, matchers, expected) => { + expect(alertSilenceMatches(matchers, anomaly(checkoutProduction))).toBe(expected) + }) + + it("keeps the scope and group key results when the incident has no labels", () => { + expect(alertSilenceMatches({ services: ["checkout"] }, anomaly(null))).toBe(false) + expect(alertSilenceMatches({ services: ["checkout"] }, anomaly(undefined))).toBe(false) + expect( + alertSilenceMatches( + { services: ["checkout"] }, + rule({ serviceNames: ["checkout"], labels: null }), + ), + ).toBe(true) + }) + + it("lets a label win over the rule's scope", () => { + const scoped = rule({ serviceNames: ["payments"], labels: { service: "checkout" } }) + expect(alertSilenceMatches({ services: ["checkout"] }, scoped)).toBe(true) + expect(alertSilenceMatches({ services: ["payments"] }, scoped)).toBe(false) + }) +}) + describe("AlertSilenceMatchers", () => { it("rejects a silence that matches nothing in particular", () => { expect(Schema.decodeOption(AlertSilenceMatchers)({})._tag).toBe("None") diff --git a/packages/domain/src/http/alert-silences.ts b/packages/domain/src/http/alert-silences.ts index fe3b6338a0..b698b77a4a 100644 --- a/packages/domain/src/http/alert-silences.ts +++ b/packages/domain/src/http/alert-silences.ts @@ -142,6 +142,8 @@ export interface AlertSilenceTarget { readonly groupBy: AlertGroupBy | null /** The incident's group; null or `__total__` for an ungrouped rule. */ readonly groupKey: string | null + /** Labels recorded on the incident itself; they win over scope and group key. */ + readonly labels?: { readonly service?: string; readonly environment?: string } | null } const groupedBy = (target: AlertSilenceTarget, dimension: string): string | null => @@ -154,11 +156,13 @@ const groupedBy = (target: AlertSilenceTarget, dimension: string): string | null : null /** - * The services an incident covers: its group when the rule groups by service + * The services an incident covers: its service label, else its group when the rule groups by service * (or keys a multi-service rule by service), else the rule's scope. Empty means * every service, which no service list covers. */ const scopedServices = (target: AlertSilenceTarget): ReadonlyArray => { + const label = target.labels?.service + if (label !== undefined) return [label] const group = groupedBy(target, "service.name") if (group !== null) return [group] if ( @@ -171,6 +175,8 @@ const scopedServices = (target: AlertSilenceTarget): ReadonlyArray => { } const scopedEnvironments = (target: AlertSilenceTarget): ReadonlyArray => { + const label = target.labels?.environment + if (label !== undefined) return [label] const group = groupedBy(target, "deployment.environment") return group !== null ? [group] : target.environments } diff --git a/packages/domain/src/http/alerts.ts b/packages/domain/src/http/alerts.ts index ef9c6d4f4f..5811fae7aa 100644 --- a/packages/domain/src/http/alerts.ts +++ b/packages/domain/src/http/alerts.ts @@ -135,6 +135,13 @@ export type AlertIncidentStatus = Schema.Schema.Type * `status` stays `"open"` while held — a hold is a sub-state of open, not a * third status, so every consumer of `open`/`resolved` keeps working. */ +/** Facts about what an incident is about, for rules whose group key does not say (anomaly rules). */ +export const AlertIncidentLabels = Schema.Struct({ + service: Schema.optionalKey(Schema.String), + environment: Schema.optionalKey(Schema.String), +}) +export type AlertIncidentLabels = Schema.Schema.Type + export const AlertIncidentHoldReason = Schema.Literals([ "no_data", "volume_collapsed", @@ -1075,6 +1082,8 @@ export class AlertIncidentDocument extends Schema.Class(" anomalyIncidentId: Schema.optionalKey(Schema.NullOr(AnomalyIncidentId)), /** What to show as the group when the key is not readable, e.g. an anomaly's "checkout (production)". */ groupLabel: Schema.optionalKey(Schema.NullOr(Schema.String)), + /** The service and environment the incident is about; silences match on these first. */ + labels: Schema.optionalKey(Schema.NullOr(AlertIncidentLabels)), /** The silence that last held back one of this incident's notifications. */ silenceId: Schema.NullOr(AlertSilenceId), /** Set while a silenced notification is still owed; cleared once one is sent after the silence. */ diff --git a/packages/domain/src/http/v2/alert-incidents.ts b/packages/domain/src/http/v2/alert-incidents.ts index 23cb392b82..8b2ea2b686 100644 --- a/packages/domain/src/http/v2/alert-incidents.ts +++ b/packages/domain/src/http/v2/alert-incidents.ts @@ -159,6 +159,27 @@ export const V2AlertIncident = Schema.Struct({ "What to show as the incident's group when `group_key` is not readable, e.g. an anomaly's `checkout (production)`. Absent when the group key is its own label.", }), ), + labels: Schema.optionalKey( + Schema.NullOr( + Schema.Struct({ + service: Schema.optionalKey( + Schema.String.annotate({ + description: "The service the incident is about.", + examples: ["checkout"], + }), + ), + environment: Schema.optionalKey( + Schema.String.annotate({ + description: "The deployment environment the incident is about.", + examples: ["production"], + }), + ), + }), + ).annotate({ + description: + "The service and environment the incident is about, for rules whose `group_key` does not say (today `anomaly` rules). Alert silences match `services` and `environments` against these first. Absent when not recorded.", + }), + ), anomaly_incident_id: Schema.optionalKey( Schema.NullOr(AnomalyIncidentPublicId).annotate({ description: diff --git a/packages/domain/src/mcp-outputs/alerts.ts b/packages/domain/src/mcp-outputs/alerts.ts index 86e0d8cbf2..23fe205456 100644 --- a/packages/domain/src/mcp-outputs/alerts.ts +++ b/packages/domain/src/mcp-outputs/alerts.ts @@ -1,6 +1,7 @@ /** Output schemas for the alerts MCP tools. */ import { Schema } from "effect" import { QueryBuilderQueryDraftSchema } from "@maple/query-model" +import { AlertIncidentLabels } from "../http/alerts" import { OutputTimeRange } from "./shared" const NullableString = Schema.NullOr(Schema.String) @@ -230,6 +231,8 @@ export const AlertIncidentRow = Schema.Struct({ anomalyIncidentId: Schema.optionalKey(NullableString), /** Shown in place of `groupKey` when set, e.g. an anomaly's "checkout (production)". */ groupLabel: Schema.optionalKey(NullableString), + /** The service and environment the incident is about; silences match on these first. */ + labels: Schema.optionalKey(Schema.NullOr(AlertIncidentLabels)), }) /** Filters an incident list was narrowed by, echoed for the rendered scope. */ @@ -300,6 +303,8 @@ export const IncidentTimelineRow = Schema.Struct({ groupKey: NullableString, /** Shown in place of `groupKey` when set, e.g. an anomaly's "checkout (production)". */ groupLabel: Schema.optionalKey(NullableString), + /** The service and environment the incident is about; silences match on these first. */ + labels: Schema.optionalKey(Schema.NullOr(AlertIncidentLabels)), signalType: Schema.String, severity: Schema.String, status: Schema.String, diff --git a/packages/web-data/src/collections/alerts.test.ts b/packages/web-data/src/collections/alerts.test.ts index 95766a24da..e2599d175a 100644 --- a/packages/web-data/src/collections/alerts.test.ts +++ b/packages/web-data/src/collections/alerts.test.ts @@ -227,6 +227,18 @@ describe("rowToAlertIncidentDocument", () => { assert.strictEqual(doc.heldSince, null) }) + it("carries an incident's labels and drops ones it cannot read", () => { + const labelled = rowToAlertIncidentDocument({ + ...incidentRow, + labels_json: { service: "checkout", environment: "production" }, + }) + assert.deepStrictEqual(labelled.labels, { service: "checkout", environment: "production" }) + const unreadable = rowToAlertIncidentDocument({ ...incidentRow, labels_json: { service: 42 } }) + assert.strictEqual(unreadable.status, "open") + assert.isUndefined(unreadable.labels) + assert.isUndefined(rowToAlertIncidentDocument(incidentRow).labels) + }) + it("maps a held row's hold reason and timestamp", () => { const doc = rowToAlertIncidentDocument({ ...incidentRow, diff --git a/packages/web-data/src/collections/alerts.ts b/packages/web-data/src/collections/alerts.ts index 3047dfdb33..1a6d242992 100644 --- a/packages/web-data/src/collections/alerts.ts +++ b/packages/web-data/src/collections/alerts.ts @@ -10,6 +10,7 @@ import { AlertIncidentDocument, AlertIncidentAckSource, AlertIncidentHoldReason, + AlertIncidentLabels, AlertIncidentStatus, AlertNotificationTemplate, AlertRuleDocument, @@ -65,6 +66,7 @@ const decodeStringArray = Schema.decodeUnknownOption(Schema.Array(Schema.String) // Lenient: a signal added by a newer server drops the list rather than breaking the alerts list. const decodeAnomalySignals = Schema.decodeUnknownOption(Schema.Array(AnomalySignalType)) const decodeAnomalyIncidentId = Schema.decodeUnknownOption(AnomalyIncidentId) +const decodeIncidentLabels = Schema.decodeUnknownOption(AlertIncidentLabels) // The stored `group_by` column is a JSON-encoded string array (mirrors the // server's `Schema.fromJsonString(AlertGroupBy)`). const decodeGroupByFromJson = Schema.decodeUnknownSync(Schema.fromJsonString(AlertGroupBy)) @@ -183,6 +185,7 @@ export const AlertIncidentRowSchema = Schema.Struct({ error_issue_id: Schema.NullOr(Schema.String), anomaly_incident_id: Schema.optionalKey(Schema.NullOr(Schema.String)), group_label: Schema.optionalKey(Schema.NullOr(Schema.String)), + labels_json: Schema.optionalKey(Schema.NullOr(Schema.Unknown)), silence_id: Schema.optionalKey(Schema.NullOr(Schema.String)), silenced_at: Schema.optionalKey(Schema.NullOr(Schema.String)), created_at: Schema.String, @@ -338,6 +341,11 @@ export const rowToAlertIncidentDocument = (row: AlertIncidentRow): AlertIncident } : undefined), ...(row.group_label == null ? undefined : { groupLabel: row.group_label }), + // Lenient: an unreadable labels value drops the labels, not the incident. + ...Option.match(row.labels_json == null ? Option.none() : decodeIncidentLabels(row.labels_json), { + onNone: () => undefined, + onSome: (labels) => ({ labels }), + }), silenceId: row.silence_id == null ? null : asAlertSilenceId(row.silence_id), silencedAt: row.silenced_at == null ? null : decodeIso(row.silenced_at), }) From dd253622292fda3656eaefa5e1fac2c0fdb4bd26 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 19:47:41 +0100 Subject: [PATCH 03/28] feat(web): anomalies live in the alerts hub - The rule editor has an Anomaly mode: scope by service, environment and signal, and route the warning and critical tiers. The default anomaly rule keeps its scope and may stay unrouted. - Anomaly incidents show the detector's evidence (observed vs baseline chart, signal, deviation, error-spike fingerprints, issue and triage) on the incident page, with no threshold numbers. - The inbox has an Anomalies view (open, resolved, all); the rules list marks the default anomaly rule. /anomalies redirects there; /anomalies/:id stays. --- apps/web/src/lab/anomaly-alerts-lab.ts | 343 ++++++++++++++++++ apps/web/src/lab/entries/anomaly-alerts.ts | 17 + apps/web/src/lab/entries/empty-states.ts | 34 +- apps/web/src/lab/registry.ts | 8 + apps/web/src/page/alerts/editor/derive.ts | 27 ++ apps/web/src/page/alerts/editor/draft.ts | 3 + .../editor/editor-anomaly.scene.test.ts | 158 ++++++++ apps/web/src/page/alerts/editor/form-steps.ts | 19 +- apps/web/src/page/alerts/editor/form.ts | 9 +- apps/web/src/page/alerts/editor/message.ts | 11 +- apps/web/src/page/alerts/editor/sub-steps.ts | 45 ++- apps/web/src/page/alerts/editor/update.ts | 8 +- .../page/alerts/editor/view-notifications.ts | 10 +- apps/web/src/page/alerts/editor/view-query.ts | 131 ++++++- apps/web/src/page/alerts/editor/view.ts | 66 ++-- apps/web/src/page/alerts/hub/derive.ts | 68 +++- .../alerts/hub/hub-anomalies.scene.test.ts | 217 +++++++++++ apps/web/src/page/alerts/hub/message.ts | 11 + apps/web/src/page/alerts/hub/update.ts | 43 ++- .../web/src/page/alerts/hub/view-anomalies.ts | 217 +++++++++++ apps/web/src/page/alerts/hub/view-inbox.ts | 11 +- apps/web/src/page/alerts/hub/view-rules.ts | 125 ++++--- apps/web/src/page/alerts/hub/view.ts | 62 +++- apps/web/src/page/alerts/incident/command.ts | 30 +- apps/web/src/page/alerts/incident/derive.ts | 3 +- .../incident/incident-anomaly.scene.test.ts | 142 ++++++++ apps/web/src/page/alerts/incident/message.ts | 8 + apps/web/src/page/alerts/incident/model.ts | 13 +- apps/web/src/page/alerts/incident/update.ts | 48 +++ .../src/page/alerts/incident/view-anomaly.ts | 164 +++++++++ .../web/src/page/alerts/incident/view-rail.ts | 7 +- apps/web/src/page/alerts/incident/view.ts | 169 +++++---- .../alerts/shared/anomaly-test-fixtures.ts | 197 ++++++++++ apps/web/src/page/alerts/shared/preview.ts | 2 + .../src/page/anomalies/detail/view-body.ts | 184 +--------- .../src/page/anomalies/detail/view-sidebar.ts | 61 +--- apps/web/src/page/anomalies/detail/view.ts | 12 +- apps/web/src/page/anomalies/kit/evidence.ts | 250 +++++++++++++ .../anomalies/list/anomalies.scene.test.ts | 271 -------------- apps/web/src/page/anomalies/list/command.ts | 70 ---- apps/web/src/page/anomalies/list/derive.ts | 160 -------- apps/web/src/page/anomalies/list/index.ts | 82 ----- apps/web/src/page/anomalies/list/message.ts | 51 --- apps/web/src/page/anomalies/list/model.ts | 47 --- apps/web/src/page/anomalies/list/update.ts | 336 ----------------- .../src/page/anomalies/list/view-filters.ts | 83 ----- apps/web/src/page/anomalies/list/view-list.ts | 227 ------------ apps/web/src/page/anomalies/list/view.ts | 175 --------- apps/web/src/page/kit/grid-list-row.ts | 20 + apps/web/src/page/registry.ts | 2 - apps/web/src/route/redirect.ts | 18 +- apps/web/src/route/route.test.ts | 22 ++ apps/web/src/route/search.ts | 4 + apps/web/src/view.lazy.test.ts | 1 - packages/views/src/ui/INDEX.md | 1 + .../web-data/src/alerts/anomaly-rule.test.ts | 56 +++ packages/web-data/src/alerts/anomaly-rule.ts | 61 ++++ packages/web-data/src/alerts/form-utils.ts | 41 ++- .../web-data/src/alerts/incident-timeline.ts | 12 +- .../src/alerts/overview-derive.test.ts | 26 +- .../web-data/src/alerts/overview-derive.ts | 12 +- .../web-data/src/alerts/preview-insights.ts | 6 + packages/web-data/src/alerts/rule-modes.ts | 51 ++- packages/web-data/src/alerts/rule-status.ts | 4 +- 64 files changed, 2789 insertions(+), 1983 deletions(-) create mode 100644 apps/web/src/lab/anomaly-alerts-lab.ts create mode 100644 apps/web/src/lab/entries/anomaly-alerts.ts create mode 100644 apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts create mode 100644 apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts create mode 100644 apps/web/src/page/alerts/hub/view-anomalies.ts create mode 100644 apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts create mode 100644 apps/web/src/page/alerts/incident/view-anomaly.ts create mode 100644 apps/web/src/page/alerts/shared/anomaly-test-fixtures.ts create mode 100644 apps/web/src/page/anomalies/kit/evidence.ts delete mode 100644 apps/web/src/page/anomalies/list/anomalies.scene.test.ts delete mode 100644 apps/web/src/page/anomalies/list/command.ts delete mode 100644 apps/web/src/page/anomalies/list/derive.ts delete mode 100644 apps/web/src/page/anomalies/list/index.ts delete mode 100644 apps/web/src/page/anomalies/list/message.ts delete mode 100644 apps/web/src/page/anomalies/list/model.ts delete mode 100644 apps/web/src/page/anomalies/list/update.ts delete mode 100644 apps/web/src/page/anomalies/list/view-filters.ts delete mode 100644 apps/web/src/page/anomalies/list/view-list.ts delete mode 100644 apps/web/src/page/anomalies/list/view.ts create mode 100644 apps/web/src/page/kit/grid-list-row.ts create mode 100644 packages/web-data/src/alerts/anomaly-rule.test.ts create mode 100644 packages/web-data/src/alerts/anomaly-rule.ts diff --git a/apps/web/src/lab/anomaly-alerts-lab.ts b/apps/web/src/lab/anomaly-alerts-lab.ts new file mode 100644 index 0000000000..a88ff3a3bc --- /dev/null +++ b/apps/web/src/lab/anomaly-alerts-lab.ts @@ -0,0 +1,343 @@ +import * as Policy from "@maple/domain/policy" +import { OrgId } from "@maple/domain" +import { Effect, Option, Result, Schema, Array as Arr, String as Str } from "effect" +import { Command, type Update } from "foldkit" +import type { Html, HtmlBuilder } from "foldkit/html" +import { defineMessageUnion } from "foldkit/message" +import * as EditorMessage from "../page/alerts/editor/message" +import * as EditorModel from "../page/alerts/editor/model" +import { init as initEditor } from "../page/alerts/editor/init" +import { update as updateEditor } from "../page/alerts/editor/update" +import { breadcrumbsOf as editorCrumbs, view as editorView } from "../page/alerts/editor/view" +import * as HubMessage from "../page/alerts/hub/message" +import * as HubModel from "../page/alerts/hub/model" +import * as HubUpdate from "../page/alerts/hub/update" +import { view as hubView } from "../page/alerts/hub/view" +import * as IncidentMessage from "../page/alerts/incident/message" +import * as IncidentModel from "../page/alerts/incident/model" +import * as IncidentUpdate from "../page/alerts/incident/update" +import { breadcrumbsOf as incidentCrumbs, view as incidentView } from "../page/alerts/incident/view" +import { + anomalyAlertIncidentId, + anomalyIncidentRow, + anomalyTimeseriesWire, + anomalyWire, + defaultAnomalyRuleRow, + scopedAnomalyRuleRow, +} from "../page/alerts/shared/anomaly-test-fixtures" +import { + destinationRow, + incidentRow, + NOW, + ORG, + ruleId, + ruleRow, + stateRow, +} from "../page/alerts/shared/test-rows" +import type { PageContext } from "../page/contract" +import type { Resources } from "../platform/resources" + +// `/lab/anomaly-alerts`: anomalies paging through alert rules, over fixtures. `?state=` picks one: +// editor-new, editor-scoped, editor-default, incident-warning, incident-critical, incident-spike, +// hub-anomalies, hub-anomalies-resolved, hub-anomalies-empty, hub-rules. + +export const LabState = Schema.Literals([ + "editor-new", + "editor-scoped", + "editor-default", + "incident-warning", + "incident-critical", + "incident-spike", + "hub-anomalies", + "hub-anomalies-resolved", + "hub-anomalies-empty", + "hub-rules", +]) +export type LabState = typeof LabState.Type + +export const Flags = Schema.Struct({ state: LabState }) +export type Flags = typeof Flags.Type + +export const flags: Effect.Effect = Effect.sync(() => ({ + state: Option.getOrElse( + Schema.decodeUnknownOption(LabState)(new URLSearchParams(window.location.search).get("state")), + (): LabState => "hub-anomalies", + ), +})) + +export const Model = Schema.Struct({ + state: LabState, + hub: HubModel.Model, + editor: EditorModel.Model, + incident: IncidentModel.Model, +}) +export type Model = typeof Model.Type + +export const Message = defineMessageUnion({ + GotHubMessage: { message: HubMessage.Message }, + GotEditorMessage: { message: EditorMessage.Message }, + GotIncidentMessage: { message: IncidentMessage.Message }, +}) +export type Message = typeof Message.Type + +type Return = Update.Return + +const CONTEXT: PageContext = { orgId: Schema.decodeSync(OrgId)(ORG), pinnedNamespace: null, timeZone: "UTC" } + +/** The pages' reads and writes are dropped: the fixture is all the data there is. */ +const keepLocal = (commands: ReadonlyArray> | undefined) => + Arr.filter( + commands ?? [], + (command) => !/^(Fetch|Toggle|Delete|Save|Read|Generate|Run|Set|Create)/.test(command.name), + ) + +const fold = ( + model: PM, + messages: ReadonlyArray, + step: (model: PM, message: PS) => { readonly model: PM }, +) => Arr.reduce(messages, model, (current, message) => step(current, message).model) + +const RULES = [ + ruleRow(2, { name: "Payment p95 latency", tags_json: ["team:payments"] }), + defaultAnomalyRuleRow(), + scopedAnomalyRuleRow(), +] + +const OPEN = [ + anomalyIncidentRow({ + n: 1, + ruleN: 8, + severity: "critical", + service: "checkout", + environment: "production", + }), + anomalyIncidentRow({ n: 2, ruleN: 7, severity: "warning", service: "search", environment: "production" }), + anomalyIncidentRow( + { n: 5, ruleN: 7, severity: "critical", service: "payments-worker", environment: "production" }, + { acknowledged_at: new Date(NOW - 20 * 60_000).toISOString(), acknowledged_by_name: "Ada Lovelace" }, + ), +] +const RESOLVED = [ + anomalyIncidentRow({ + n: 3, + ruleN: 8, + severity: "warning", + service: "checkout", + environment: "staging", + status: "resolved", + }), + anomalyIncidentRow({ + n: 6, + ruleN: 7, + severity: "critical", + service: "search", + environment: "production", + status: "resolved", + }), +] + +const hubFor = (state: LabState) => { + const search: HubModel.Model["search"] = + state === "hub-rules" + ? { tab: "rules" } + : state === "hub-anomalies-resolved" + ? { signal: "anomaly", anomalyStatus: "all" } + : { signal: "anomaly" } + const incidents = + state === "hub-anomalies-empty" + ? [incidentRow({ rule_name: "Payment p95 latency" })] + : [incidentRow({ rule_name: "Payment p95 latency" }), ...OPEN, ...RESOLVED] + return fold( + HubUpdate.init({ _tag: "Alerts", search }, CONTEXT).model, + [ + HubMessage.Message.ReadClock({ nowMs: NOW }), + HubMessage.Message.UpdatedRules({ orgId: ORG, rows: RULES }), + HubMessage.Message.UpdatedRuleStates({ + orgId: ORG, + rows: [ + stateRow(2, { last_status: "breached", last_value: 1840 }), + stateRow(7, { last_value: 2 }), + stateRow(8, { last_value: 0 }), + ], + }), + HubMessage.Message.UpdatedIncidents({ orgId: ORG, rows: incidents }), + HubMessage.Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + HubMessage.Message.CompletedFetchSession({ orgId: ORG, grants: Policy.allPermissions }), + ], + (model, message) => HubUpdate.update(model, message, CONTEXT), + ) +} + +const editorSearch = (state: LabState): EditorModel.Model["search"] => + state === "editor-scoped" + ? { ruleId: ruleId(8) } + : state === "editor-default" + ? { ruleId: ruleId(7) } + : { template: "anomaly" } + +const editorFor = (state: LabState) => + fold( + initEditor({ _tag: "AlertsCreate", search: editorSearch(state) }, CONTEXT).model, + [ + EditorMessage.Message.GeneratedDraftId({ id: "6f1c2e3a-0000-4000-8000-000000000001" }), + EditorMessage.Message.CompletedReadClock({ nowMs: NOW }), + EditorMessage.Message.CompletedReadGrants({ grants: Policy.allPermissions }), + EditorMessage.Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + EditorMessage.Message.UpdatedRules({ orgId: ORG, rows: RULES }), + ...(state === "editor-new" + ? [ + EditorMessage.Message.ChangedField({ field: "name", value: "Search anomalies" }), + EditorMessage.Message.ToggledAnomalySignal({ signal: "latency_p95" }), + EditorMessage.Message.ToggledAnomalySignal({ signal: "throughput" }), + ] + : []), + ], + (model, message) => updateEditor(model, message, CONTEXT), + ) + +const INCIDENTS = { + "incident-warning": { n: 4, signal: "latency_p95", severity: "warning", service: "search" }, + "incident-critical": { n: 1, signal: "error_rate", severity: "critical", service: "checkout" }, + "incident-spike": { n: 3, signal: "error_spike", severity: "critical", service: "payments-worker" }, +} as const + +const incidentFor = (state: LabState) => { + const scenario = + state === "incident-warning" || state === "incident-critical" || state === "incident-spike" + ? INCIDENTS[state] + : INCIDENTS["incident-critical"] + const row = anomalyIncidentRow({ + n: scenario.n, + ruleN: scenario.service === "checkout" ? 8 : 7, + severity: scenario.severity, + service: scenario.service, + environment: "production", + }) + const entered = IncidentUpdate.init( + { _tag: "AlertIncident", incidentId: anomalyAlertIncidentId(scenario.n), search: {} }, + CONTEXT, + ).model + const synced = fold( + entered, + [ + IncidentMessage.Message.ReadClock({ nowMs: NOW }), + IncidentMessage.Message.UpdatedIncidents({ orgId: ORG, rows: [row] }), + IncidentMessage.Message.UpdatedRules({ orgId: ORG, rows: RULES }), + ], + (model, message) => IncidentUpdate.update(model, message, CONTEXT), + ) + return fold( + synced, + [ + IncidentMessage.Message.CompletedFetchAnomaly({ + key: synced.anomaly.key ?? "", + result: Result.succeed( + anomalyWire(scenario.n, scenario.signal, scenario.severity, scenario.service), + ), + }), + IncidentMessage.Message.CompletedFetchAnomalyTimeseries({ + key: synced.anomalyTimeseries.key ?? "", + result: Result.succeed(anomalyTimeseriesWire(scenario.signal)), + }), + IncidentMessage.Message.CompletedFetchDeliveries({ + key: synced.deliveries.key ?? "", + result: Result.succeed([]), + }), + IncidentMessage.Message.CompletedFetchActivity({ + key: synced.activity.key ?? "", + result: Result.succeed([]), + }), + IncidentMessage.Message.CompletedFetchInvestigation({ + key: synced.investigation.key ?? "", + result: Result.succeed(null), + }), + ], + (model, message) => IncidentUpdate.update(model, message, CONTEXT), + ) +} + +const isEditor = Str.startsWith("editor") +const isIncident = Str.startsWith("incident") + +export const init = (flags: Flags): Return => ({ + model: { + state: flags.state, + hub: hubFor(flags.state), + editor: editorFor(flags.state), + incident: incidentFor(flags.state), + }, + // The incident page's chart runtime still loads, so its anomaly chart draws. + commands: Command.mapMessages( + keepLocal( + IncidentUpdate.init( + { _tag: "AlertIncident", incidentId: anomalyAlertIncidentId(1), search: {} }, + CONTEXT, + ).commands, + ), + (message) => Message.GotIncidentMessage({ message }), + ), +}) + +export const update = (model: Model, message: Message): Return => + Message.match(message, { + GotHubMessage: ({ message: child }) => { + const next = HubUpdate.update(model.hub, child, CONTEXT) + return { + model: { ...model, hub: next.model }, + commands: Command.mapMessages(keepLocal(next.commands), (inner) => + Message.GotHubMessage({ message: inner }), + ), + } + }, + GotEditorMessage: ({ message: child }) => { + const next = updateEditor(model.editor, child, CONTEXT) + return { + model: { ...model, editor: next.model }, + commands: Command.mapMessages(keepLocal(next.commands), (inner) => + Message.GotEditorMessage({ message: inner }), + ), + } + }, + GotIncidentMessage: ({ message: child }) => { + const next = IncidentUpdate.update(model.incident, child, CONTEXT) + return { + model: { ...model, incident: next.model }, + commands: Command.mapMessages(keepLocal(next.commands), (inner) => + Message.GotIncidentMessage({ message: inner }), + ), + } + }, + }) + +export const breadcrumbs = (model: Model) => + isEditor(model.state) + ? editorCrumbs(model.editor) + : isIncident(model.state) + ? incidentCrumbs(model.incident) + : [{ label: "Alerts" }] + +export const view = (model: Model, h: HtmlBuilder): Html => + isEditor(model.state) + ? h.submodel({ + slotId: "lab-anomaly-editor", + model: model.editor, + view: editorView, + viewInputs: { context: CONTEXT }, + toParentMessage: (message: EditorMessage.Message) => Message.GotEditorMessage({ message }), + }) + : isIncident(model.state) + ? h.submodel({ + slotId: "lab-anomaly-incident", + model: model.incident, + view: incidentView, + viewInputs: { context: CONTEXT }, + toParentMessage: (message: IncidentMessage.Message) => + Message.GotIncidentMessage({ message }), + }) + : h.submodel({ + slotId: "lab-anomaly-hub", + model: model.hub, + view: hubView, + viewInputs: { context: CONTEXT }, + toParentMessage: (message: HubMessage.Message) => Message.GotHubMessage({ message }), + }) diff --git a/apps/web/src/lab/entries/anomaly-alerts.ts b/apps/web/src/lab/entries/anomaly-alerts.ts new file mode 100644 index 0000000000..90d8392e6e --- /dev/null +++ b/apps/web/src/lab/entries/anomaly-alerts.ts @@ -0,0 +1,17 @@ +import { defineShellLab } from "../shell-lab" +import { breadcrumbs, Flags, flags, init, Message, Model, update, view } from "../anomaly-alerts-lab" + +// `/lab/anomaly-alerts`: anomaly rules in the editor, an anomaly rule's incident and the Inbox's +// Anomalies view over fixtures (`?state=`). + +export const lab = defineShellLab("Anomaly alerts", { + Model, + Message, + Flags, + flags, + init, + update, + view, + currentPath: "/lab/anomaly-alerts", + breadcrumbs, +}) diff --git a/apps/web/src/lab/entries/empty-states.ts b/apps/web/src/lab/entries/empty-states.ts index 4925f85654..358a37d24c 100644 --- a/apps/web/src/lab/entries/empty-states.ts +++ b/apps/web/src/lab/entries/empty-states.ts @@ -1,5 +1,5 @@ import { OrgId } from "@maple/domain" -import { Clock, Effect, Result, Schema, Array as Arr } from "effect" +import { Clock, Effect, Result, Schema } from "effect" import { AsyncData, Submodel } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" import { defineMessageUnion } from "foldkit/message" @@ -8,10 +8,6 @@ import * as AgentSessionMessage from "../../page/agent-sessions/session/message" import { dividerBlock } from "../../page/agent-sessions/session/transcript-lanes" import { init as initSession } from "../../page/agent-sessions/session/update" import { loadIndicator } from "../../page/agent-sessions/session/view-load" -import * as AnomaliesMessage from "../../page/anomalies/list/message" -import * as Anomalies from "../../page/anomalies/list/model" -import { init as initAnomalies, update as updateAnomalies } from "../../page/anomalies/list/update" -import { view as anomaliesView } from "../../page/anomalies/list/view" import { notFoundContent } from "../../page/chrome" import type { PageContext } from "../../page/contract" import * as ErrorsMessage from "../../page/errors/hub/message" @@ -40,8 +36,6 @@ export const ITEMS = [ "releases-first-run", "releases-quiet", "releases-filtered", - "anomalies-first-run", - "anomalies-loading", "session-rest-failed", "recommendations-loading", "not-found", @@ -124,23 +118,6 @@ const releasesModel = ( return { ...loaded, presence: presenceOf(presence) } } -const anomaliesModel = (nowMs: number, presence: Presence | null): Anomalies.Model => { - const entered = initAnomalies({ search: {} }, context).model - if (presence === null) return entered - const loaded = Arr.reduce( - [ - AnomaliesMessage.Message.ReadClock({ nowMs }), - AnomaliesMessage.Message.CompletedFetchFirstPage({ - key: entered.firstPage.key ?? "", - result: Result.succeed({ data: [], nextCursor: null }), - }), - ], - entered, - (model, message) => updateAnomalies(model, message, context).model, - ) - return { ...loaded, presence: presenceOf(presence) } -} - const FAILED_PROGRESS = { phase: "failed", agentSpansComplete: false, @@ -198,15 +175,6 @@ const itemView = (model: Model, h: HtmlBuilder): Html => { toParentMessage: ignore, viewInputs: { context }, }) - case "anomalies-first-run": - case "anomalies-loading": - return h.submodel({ - slotId: "lab-anomalies", - model: anomaliesModel(nowMs, model.item === "anomalies-first-run" ? "absent" : null), - view: anomaliesView, - toParentMessage: ignore, - viewInputs: { context }, - }) case "session-rest-failed": return h.submodel({ slotId: "lab-session", diff --git a/apps/web/src/lab/registry.ts b/apps/web/src/lab/registry.ts index fdb9ebac27..48917ad274 100644 --- a/apps/web/src/lab/registry.ts +++ b/apps/web/src/lab/registry.ts @@ -61,6 +61,14 @@ export const LAB_ENTRIES: ReadonlyArray = [ kind: "lab", session: "none", }, + { + path: "/lab/anomaly-alerts", + title: "Anomaly alerts", + description: + "Anomaly rules in the rule editor (scoped and the protected default), an anomaly rule's incident with the detector's evidence, and the Inbox's Anomalies view (`?state=`).", + kind: "lab", + session: "none", + }, { path: "/lab/billing-orgs", title: "Billing orgs", diff --git a/apps/web/src/page/alerts/editor/derive.ts b/apps/web/src/page/alerts/editor/derive.ts index 60f4b78901..52dacb186e 100644 --- a/apps/web/src/page/alerts/editor/derive.ts +++ b/apps/web/src/page/alerts/editor/derive.ts @@ -10,6 +10,7 @@ import { signalLabels, thresholdUnitLabel, } from "@maple/web-data/alerts/form-utils" +import { anomalyScopeSummary, isDefaultAnomalyRule } from "@maple/web-data/alerts/anomaly-rule" import { destinationProvider } from "../shared/providers" import type { RuleForm } from "./form" import { intervalWindowIssue, queryTimingIssue } from "./interval" @@ -19,6 +20,12 @@ const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)) // The rule editor's pure helpers: form surface, condition bar, live chart hero, notifications. +/** Editing the org's default anomaly rule: its scope, kind and enabled state are the server's. */ +export const isProtectedDefault = ( + model: Readonly<{ editingRuleId: string | null; savedForm: RuleForm | null }>, +): boolean => + model.editingRuleId !== null && model.savedForm !== null && isDefaultAnomalyRule(model.savedForm) + export const COMPARATOR_WORDS = new Map([ ["gt", "is above"], ["gte", "is at or above"], @@ -33,6 +40,7 @@ export const comparatorWords = (comparator: AlertComparator): string => COMPARAT /** What the threshold is compared against, in words: the aggregation for a query, the signal otherwise. */ export const conditionSubject = (form: RuleForm): string => { + if (form.signalType === "anomaly") return "anomalies" if (form.signalType === "raw_query") return "the SQL value" if (form.signalType === "builder_query") { const aggregation = Str.trim(Str.replaceAll("_", " ")(form.queryBuilderDraft.aggregation)) @@ -43,6 +51,10 @@ export const conditionSubject = (form: RuleForm): string => { /** The rule in one plain sentence, shown in the action bar. */ export const describeRuleCondition = (form: RuleForm): string => { + if (form.signalType === "anomaly") + return isDefaultAnomalyRule(form) + ? "Pages on every anomaly no narrower rule handles" + : `Pages on anomalies in ${anomalyScopeSummary(form)}` const unit = thresholdUnitLabel(form) ?? "" const value = (raw: string) => `${Str.trim(raw) || "?"}${unit === "%" ? "%" : unit ? ` ${unit}` : ""}` const threshold = isRangeComparator(form.comparator) @@ -81,6 +93,7 @@ export const suggestCriticalThreshold = (form: RuleForm): string => { /** `deriveValidationIssues`: what the action bar lists as missing. */ export const validationIssues = (form: RuleForm): ReadonlyArray => { + if (form.signalType === "anomaly") return anomalyIssues(form) const issues: Array = [] if (Str.trim(form.name).length === 0) issues.push("Rule name") if (!isFilledNumber(form.threshold)) issues.push("Threshold") @@ -110,6 +123,19 @@ export const validationIssues = (form: RuleForm): ReadonlyArray => { return issues } +/** + * An anomaly rule has no query or timing to get wrong: a name, and someone to page on a scoped + * rule. The default rule may stay unrouted (anomaly paging is opt-in), as the server allows. + */ +const anomalyIssues = (form: RuleForm): ReadonlyArray => [ + ...(Str.trim(form.name).length === 0 ? ["Rule name"] : []), + ...(!isDefaultAnomalyRule(form) && + form.destinationIds.length === 0 && + (form.escalation?.destinationIds.length ?? 0) === 0 + ? ["Who gets notified"] + : []), +] + /** What blocks saving: the form's own issues and a query too slow for its interval. */ export const saveIssues = (form: RuleForm, timing: AlertQueryTiming | null): ReadonlyArray => { const timingIssue = queryTimingIssue(form, timing) @@ -138,6 +164,7 @@ export const previewIdentityKey = (form: RuleForm): string => { /** `makeSuggestedName`: signal and scope, offered while the name is empty. */ export const suggestedName = (form: RuleForm): string | null => { if (Str.trim(form.name).length > 0) return null + if (form.signalType === "anomaly") return `Anomalies · ${anomalyScopeSummary(form)}` const subject = conditionSubject(form) const base = Str.toUpperCase(Str.slice(0, 1)(subject)) + Str.slice(1)(subject) const queryGroupBy = diff --git a/apps/web/src/page/alerts/editor/draft.ts b/apps/web/src/page/alerts/editor/draft.ts index 5258c72700..b5e3ea59a2 100644 --- a/apps/web/src/page/alerts/editor/draft.ts +++ b/apps/web/src/page/alerts/editor/draft.ts @@ -133,6 +133,9 @@ export const initialization = (model: Model, draftId: string): Initialization => { severity: "warning", message: "Couldn't load dashboards. Starting from a blank alert." }, ]) } + // "New rule → Anomaly rule" in the hub: the editor opens on the anomaly kind. + if (search.template === "anomaly") + return ready("new:anomaly", fromFormState(switchSignalKind(base, "anomaly").form)) const template = Arr.findFirst(ALERT_TEMPLATES, (candidate) => candidate.id === search.template) if (Option.isSome(template)) return ready(`new:template:${template.value.id}`, fromFormState(template.value.apply(base))) diff --git a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts new file mode 100644 index 0000000000..c1de80d1bb --- /dev/null +++ b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts @@ -0,0 +1,158 @@ +import { OrgId } from "@maple/domain" +import { AlertDestinationId } from "@maple/domain/http" +import { Result, Schema, Array as Arr } from "effect" +import { click, expect as expectView, given, Mount, role, scene, text, withViewInputs } from "foldkit/scene" +import { describe, expect, test } from "vitest" +import { ResolveThemeColors, ThemeMessage } from "@maple/views/charts/theme-colors" +import type { PageContext } from "../../contract" +import { defaultAnomalyRuleRow, scopedAnomalyRuleRow } from "../shared/anomaly-test-fixtures" +import { DESTINATION_ID, destinationRow, NOW, ORG, ruleId, ruleRow } from "../shared/test-rows" +import { init } from "./init" +import { Message } from "./message" +import type { Model } from "./model" +import { update } from "./update" +import { view as pageView } from "./view" + +const orgId = Schema.decodeSync(OrgId)(ORG) +const context: PageContext = { orgId, pinnedNamespace: null, timeZone: "UTC" } +const pageUpdate = (model: Model, message: Message) => update(model, message, context) +const view = withViewInputs(pageView, { context })() +const DESTINATION = Schema.decodeSync(AlertDestinationId)(DESTINATION_ID) +const DRAFT_ID = "6f1c2e3a-0000-4000-8000-000000000001" +const themeResolved = () => + Mount.resolve(ResolveThemeColors, ThemeMessage.ResolvedThemeColors({ colors: {} })) +const run = (model: Model, messages: ReadonlyArray) => + Arr.reduce(messages, model, (current, message) => pageUpdate(current, message).model) +const opened = ( + search: Model["search"], + rules = [ruleRow(2), defaultAnomalyRuleRow(), scopedAnomalyRuleRow()], +) => + run(init({ _tag: "AlertsCreate", search }, context).model, [ + Message.GeneratedDraftId({ id: DRAFT_ID }), + Message.CompletedReadClock({ nowMs: NOW }), + Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + Message.UpdatedRules({ orgId: ORG, rows: rules }), + ]) + +const DESCRIPTION = /^Pages when Maple detects unusual error rate, latency, throughput/ +const DEFAULT_NOTE = /^This is the default anomaly rule/ + +describe("alert rule editor: anomaly rules", () => { + test("the Anomaly kind keeps scope and per-tier destinations and drops the condition and chart", () => { + const blank = opened({ template: "slow_p95" }) + scene( + { update: pageUpdate, view }, + given(blank), + themeResolved(), + expectView(text("When to alert")).toExist(), + click(role("button", { name: "Anomaly" })), + expectView(text(DESCRIPTION)).toExist(), + expectView(text("When to alert")).not.toExist(), + expectView(role("button", { name: "Dry run" })).not.toExist(), + expectView(role("group", { name: "Anomaly signals" })).toExist(), + expectView(text("None picked: every signal pages.")).toExist(), + expectView(text("Critical, on top of the warning destinations")).toExist(), + ) + }) + + test("picking signals narrows the rule, and saving sends the scope with both pinned tiers", () => { + const anomaly = run(opened({ template: "anomaly" }), [ + Message.ChangedField({ field: "name", value: "Checkout anomalies" }), + Message.ToggledAnomalySignal({ signal: "error_spike" }), + Message.ToggledAnomalySignal({ signal: "error_rate" }), + ]) + // Kept in the editor's order, whatever the click order. + expect(anomaly.form?.anomalySignals).toEqual(["error_rate", "error_spike"]) + const saved = pageUpdate(anomaly, Message.ClickedSave()) + const payload = saved.commands?.[0]?.args + expect(saved.commands?.[0]?.name).toBe("SaveAlertRule") + expect(payload).toMatchObject({ + payload: { + signal_type: "anomaly", + anomaly_signals: ["error_rate", "error_spike"], + thresholds: [ + expect.objectContaining({ severity: "warning", value: 1 }), + expect.objectContaining({ severity: "critical", value: 2 }), + ], + }, + }) + }) + + test("a scoped anomaly rule with nobody to page cannot be saved", () => { + const unrouted = run(opened({ template: "anomaly", serviceName: "search" }), [ + Message.ChangedField({ field: "name", value: "Search anomalies" }), + Message.ClickedRemoveDestination({ tier: "warning", id: DESTINATION }), + ]) + scene( + { update: pageUpdate, view }, + given(unrouted), + themeResolved(), + expectView(text("Who gets notified")).toExist(), + expectView(role("button", { name: "Create rule" })).toBeDisabled(), + ) + }) + + test("editing a scoped anomaly rule opens on its scope and keeps the save unchanged", () => { + const scoped = opened({ ruleId: ruleId(8) }) + expect(scoped.form?.signalType).toBe("anomaly") + expect(scoped.form?.serviceNames).toEqual(["checkout"]) + expect(scoped.form?.anomalySignals).toEqual(["error_rate", "latency_p95"]) + scene( + { update: pageUpdate, view }, + given(scoped), + themeResolved(), + expectView(text("No changes")).toExist(), + expectView(text(DEFAULT_NOTE)).not.toExist(), + expectView(role("button", { name: "Error rate" })).not.toBeDisabled(), + ) + }) + + test("the default anomaly rule locks its kind and scope and says why", () => { + const fallback = opened({ ruleId: ruleId(7) }) + scene( + { update: pageUpdate, view }, + given(fallback), + themeResolved(), + expectView(text(DEFAULT_NOTE)).toExist(), + expectView(role("button", { name: "Error rate" })).toBeDisabled(), + expectView(role("button", { name: "Built-in" })).toBeDisabled(), + ) + // A stale click still cannot scope it or turn it into a query rule. + const poked = run(fallback, [ + Message.ToggledAnomalySignal({ signal: "throughput" }), + Message.ClickedSignalKind({ kind: "builtin" }), + ]) + expect(poked.form).toEqual(fallback.form) + }) + + test("the default anomaly rule saves unrouted, since anomaly paging is opt-in", () => { + const renamed = run(opened({ ruleId: ruleId(7) }), [ + Message.ChangedField({ field: "name", value: "Anomalies (everything else)" }), + ]) + scene( + { update: pageUpdate, view }, + given(renamed), + themeResolved(), + expectView(text("Ready to save")).toExist(), + expectView(role("button", { name: "Save changes" })).not.toBeDisabled(), + ) + const saved = pageUpdate(renamed, Message.ClickedSave()) + expect(saved.commands?.[0]?.name).toBe("SaveAlertRule") + }) + + test("the server's refusal is shown as the save error", () => { + const fallback = run(opened({ ruleId: ruleId(7) }), [ + Message.ClickedAddNotes(), + Message.ChangedField({ field: "notes", value: "Pages the platform team" }), + ]) + const saving = pageUpdate(fallback, Message.ClickedSave()).model + const message = + "The default anomaly rule cannot be deleted or disabled; turn anomaly detection off in anomaly settings instead" + const failed = pageUpdate( + saving, + Message.CompletedSave({ result: Result.fail({ title: "Couldn't save rule", message }) }), + ) + expect(failed.model.isSaving).toBe(false) + expect(failed.outMessage).toMatchObject({ title: "Couldn't save rule", description: message }) + }) +}) diff --git a/apps/web/src/page/alerts/editor/form-steps.ts b/apps/web/src/page/alerts/editor/form-steps.ts index b15b2f6a87..5be9030d8d 100644 --- a/apps/web/src/page/alerts/editor/form-steps.ts +++ b/apps/web/src/page/alerts/editor/form-steps.ts @@ -1,4 +1,10 @@ -import type { AlertDestinationId, AlertSeverity, AlertSignalType } from "@maple/domain/http" +import type { + AlertDestinationId, + AlertSeverity, + AlertSignalType, + AnomalySignalType, +} from "@maple/domain/http" +import { ANOMALY_SIGNALS } from "@maple/web-data/alerts/anomaly-rule" import { switchSignalKind } from "@maple/web-data/alerts/rule-modes" import { ALERT_TEMPLATES } from "../shared/templates" import { suggestCriticalThreshold } from "./derive" @@ -39,6 +45,17 @@ export const addCriticalTier = (form: RuleForm): RuleForm => ({ escalation: { threshold: suggestCriticalThreshold(form), recoveryValue: "", destinationIds: [] }, }) +/** An anomaly rule's signal toggled in or out of its scope, kept in the editor's order. */ +export const toggleAnomalySignal = (form: RuleForm, signal: AnomalySignalType): RuleForm => ({ + ...form, + anomalySignals: form.anomalySignals.includes(signal) + ? Arr.filter(form.anomalySignals, (existing) => existing !== signal) + : Arr.filter( + ANOMALY_SIGNALS, + (candidate) => candidate === signal || form.anomalySignals.includes(candidate), + ), +}) + /** Services: each named service is already its own group, and exclude only narrows "all services". */ export const setServices = (form: RuleForm, serviceNames: ReadonlyArray): RuleForm => ({ ...form, diff --git a/apps/web/src/page/alerts/editor/form.ts b/apps/web/src/page/alerts/editor/form.ts index 381801e6db..26cf9fa759 100644 --- a/apps/web/src/page/alerts/editor/form.ts +++ b/apps/web/src/page/alerts/editor/form.ts @@ -1,4 +1,10 @@ -import { AlertComparator, AlertDestinationId, AlertSeverity, AlertSignalType } from "@maple/domain/http" +import { + AlertComparator, + AlertDestinationId, + AlertSeverity, + AlertSignalType, + AnomalySignalType, +} from "@maple/domain/http" import { QueryEngineAlertReducer } from "@maple/domain/query-engine" import type { RuleFormState } from "@maple/web-data/alerts/form-utils" import { Schema } from "effect" @@ -24,6 +30,7 @@ export const RuleForm = Schema.Struct({ serviceNames: Strings, excludeServiceNames: Strings, environments: Strings, + anomalySignals: Schema.mutable(Schema.Array(AnomalySignalType)), tags: Strings, groupBy: Strings, signalType: AlertSignalType, diff --git a/apps/web/src/page/alerts/editor/message.ts b/apps/web/src/page/alerts/editor/message.ts index 8e89016904..cb15fc2c8c 100644 --- a/apps/web/src/page/alerts/editor/message.ts +++ b/apps/web/src/page/alerts/editor/message.ts @@ -1,5 +1,10 @@ import * as Policy from "@maple/domain/policy" -import { AlertDestinationId, AlertRulePreviewResponse, DashboardDocument } from "@maple/domain/http" +import { + AlertDestinationId, + AlertRulePreviewResponse, + AnomalySignalType, + DashboardDocument, +} from "@maple/domain/http" import { V2ChatConnector } from "@maple/domain/http/v2" import { AlertDestinationRowSchema, @@ -46,7 +51,7 @@ export const TextField = Schema.Literals([ ]) export type TextField = typeof TextField.Type -export const SignalKind = Schema.Literals(["builtin", "builder_query", "raw_query"]) +export const SignalKind = Schema.Literals(["builtin", "builder_query", "raw_query", "anomaly"]) export type SignalKind = typeof SignalKind.Type /** The warning tier's destinations, or the critical tier's. */ @@ -93,6 +98,8 @@ export const Message = defineMessageUnion({ message: RadioGroup.Message, }, ClickedOpenInBuilder: {}, + /** An anomaly rule's signal toggle: on adds the signal to its scope, off takes it out. */ + ToggledAnomalySignal: { signal: AnomalySignalType }, GotQueryPanelMessage: { message: QueryPanel.Message }, GotSelectMessage: { id: Schema.String, message: Select.Message }, GotComboMessage: { id: Schema.String, message: Combobox.Message }, diff --git a/apps/web/src/page/alerts/editor/sub-steps.ts b/apps/web/src/page/alerts/editor/sub-steps.ts index 9b0f9c7e16..8eaf0e687a 100644 --- a/apps/web/src/page/alerts/editor/sub-steps.ts +++ b/apps/web/src/page/alerts/editor/sub-steps.ts @@ -10,6 +10,7 @@ import type { PageContext } from "../../contract" import { applyQueryChange } from "../../kit/query-builder/change" import * as QueryPanel from "../../kit/query-builder/panel" import * as TimeRangePicker from "../../kit/time-range-picker" +import { isProtectedDefault } from "./derive" import { setBuiltinSignal, setChips, setSeverity } from "./form-steps" import { AUTO_INTERVAL, intervalRadioValue } from "./interval" import { Message } from "./message" @@ -42,7 +43,9 @@ export const radioStep = ( ? model.form.severity : intervalRadioValue(model.form.evaluationIntervalMinutes) const next = RadioGroup.update(RadioGroup.init(current ?? undefined), message) - const commands = Command.mapMessages(next.commands ?? [], (inner) => Message.GotRadioMessage({ group, message: inner })) + const commands = Command.mapMessages(next.commands ?? [], (inner) => + Message.GotRadioMessage({ group, message: inner }), + ) const value = next.model.maybeValue if (value._tag === "None" || value.value === current) return { model, commands } const edited = editForm(model, context, (form) => { @@ -64,11 +67,18 @@ const withCommands = (step: Return, commands: Return["commands"]): Return => ({ commands: [...(commands ?? []), ...(step.commands ?? [])], }) -export const selectStep = (model: Model, context: PageContext, id: string, message: Select.Message): Return => { +export const selectStep = ( + model: Model, + context: PageContext, + id: string, + message: Select.Message, +): Return => { const current = model.selects[id] ?? Select.init(id, { value: "", selectedIndex: null }) const next = Select.update(current, message) const lifted: Model = { ...model, selects: { ...model.selects, [id]: next.model } } - const commands = Command.mapMessages(next.commands ?? [], (inner) => Message.GotSelectMessage({ id, message: inner })) + const commands = Command.mapMessages(next.commands ?? [], (inner) => + Message.GotSelectMessage({ id, message: inner }), + ) if (next.outMessage === undefined) return { model: lifted, commands } const value = next.outMessage.value const edited = editForm(lifted, context, (form) => { @@ -82,14 +92,27 @@ export const selectStep = (model: Model, context: PageContext, id: string, messa return withCommands(edited, commands) } -export const comboStep = (model: Model, context: PageContext, id: string, message: Combobox.Message): Return => { +export const comboStep = ( + model: Model, + context: PageContext, + id: string, + message: Combobox.Message, +): Return => { const current = model.combos[id] ?? Combobox.init(id, "multiple") const next = Combobox.update(current, message) const lifted: Model = { ...model, combos: { ...model.combos, [id]: next.model } } - const commands = Command.mapMessages(next.commands ?? [], (inner) => Message.GotComboMessage({ id, message: inner })) - if (next.outMessage === undefined || next.outMessage._tag !== "ChangedSelection") return { model: lifted, commands } + const commands = Command.mapMessages(next.commands ?? [], (inner) => + Message.GotComboMessage({ id, message: inner }), + ) + if (next.outMessage === undefined || next.outMessage._tag !== "ChangedSelection") + return { model: lifted, commands } const selected = next.outMessage.selected - return withCommands(editForm(lifted, context, (form) => setChips(form, id, selected)), commands) + // The default anomaly rule's scope stays empty: the server refuses any other. + if (isProtectedDefault(model)) return { model: lifted, commands } + return withCommands( + editForm(lifted, context, (form) => setChips(form, id, selected)), + commands, + ) } /** The rule's one query panel; its edits land on `queryBuilderDraft`. */ @@ -112,14 +135,18 @@ export const chartStep = (model: Model, message: Chart.Message): Return => { const next = Chart.update(model.chart, message) return { model: { ...model, chart: next.model }, - commands: Command.mapMessages(next.commands ?? [], (inner) => Message.GotChartMessage({ message: inner })), + commands: Command.mapMessages(next.commands ?? [], (inner) => + Message.GotChartMessage({ message: inner }), + ), } } /** The preview-only lookback: it re-runs the preview, never the rule being saved. */ export const timePickerStep = (model: Model, message: TimeRangePicker.Message): Return => { const next = TimeRangePicker.update(model.timePicker, message) - const commands = Command.mapMessages(next.commands ?? [], (inner) => Message.GotTimePickerMessage({ message: inner })) + const commands = Command.mapMessages(next.commands ?? [], (inner) => + Message.GotTimePickerMessage({ message: inner }), + ) const lifted = { ...model, timePicker: next.model } // TODO(alert-preview): keep a custom preview window too; only presets are stored here. if (next.outMessage?._tag !== "SelectedPreset") return { model: lifted, commands } diff --git a/apps/web/src/page/alerts/editor/update.ts b/apps/web/src/page/alerts/editor/update.ts index 050d03cffa..2066af1bdd 100644 --- a/apps/web/src/page/alerts/editor/update.ts +++ b/apps/web/src/page/alerts/editor/update.ts @@ -9,7 +9,7 @@ import { completeSave, completeTest, runTest, save } from "./actions" import { FocusSqlCaret, InsertSqlToken, ReloadPage } from "./command" import { SQL_TEXTAREA_ID } from "./view-sql" import { chatConnectStep, connectorsAnswered, liftDialog, menuStep, removeDestination } from "./dest-steps" -import { suggestedName } from "./derive" +import { isProtectedDefault, suggestedName } from "./derive" import { tagSuggestionsOf } from "./derive-rows" import * as F from "./form-steps" import { Message } from "./message" @@ -119,7 +119,7 @@ export const update = (model: Model, message: Message, context: PageContext): Re GotChartMessage: ({ message: inner }) => chartStep(model, inner), GotTimePickerMessage: ({ message: inner }) => timePickerStep(model, inner), ClickedSignalKind: ({ kind }) => { - if (model.form === null) return { model } + if (model.form === null || isProtectedDefault(model)) return { model } const moved = F.switchKind(model.form, kind) return editForm({ ...model, kindNotice: moved.notice }, context, () => moved.form) }, @@ -129,6 +129,10 @@ export const update = (model: Model, message: Message, context: PageContext): Re const moved = F.switchKind(model.form, "builder_query") return editForm({ ...model, kindNotice: moved.notice }, context, () => moved.form) }, + ToggledAnomalySignal: ({ signal }) => + isProtectedDefault(model) + ? { model } + : editForm(model, context, (form) => F.toggleAnomalySignal(form, signal)), GotQueryPanelMessage: ({ message: inner }) => queryPanelStep(model, context, inner), GotSelectMessage: ({ id, message: inner }) => selectStep(model, context, id, inner), GotComboMessage: ({ id, message: inner }) => comboStep(model, context, id, inner), diff --git a/apps/web/src/page/alerts/editor/view-notifications.ts b/apps/web/src/page/alerts/editor/view-notifications.ts index f3f8463d83..6633e92a72 100644 --- a/apps/web/src/page/alerts/editor/view-notifications.ts +++ b/apps/web/src/page/alerts/editor/view-notifications.ts @@ -31,7 +31,7 @@ type NotificationsView = NotifyView & Pick type NotificationsForm = Pick< RuleForm, - "comparator" | "escalation" | "destinationIds" | "notificationTitle" | "notificationBody" + "signalType" | "comparator" | "escalation" | "destinationIds" | "notificationTitle" | "notificationBody" > const TITLE_PLACEHOLDER = "{{ event.emoji }} {{ rule.name }} — {{ event.label }}" @@ -176,6 +176,13 @@ const notifications = (h: H, model: NotificationsView, form: NotificationsForm): h.div( [h.Class("mt-1 space-y-4")], [ + ...(form.signalType === "anomaly" + ? [ + typography(h, { size: "xs", tone: "muted", as: "p" }, [ + "Maple rates each anomaly warning or critical by how far it strays from its baseline.", + ]), + ] + : []), h.div( [h.Class("space-y-1.5")], [ @@ -259,6 +266,7 @@ const viewOf = stableSlice((model: Model) => ) const formOf = stableSlice((form: RuleForm) => Struct.pick(form, [ + "signalType", "comparator", "escalation", "destinationIds", diff --git a/apps/web/src/page/alerts/editor/view-query.ts b/apps/web/src/page/alerts/editor/view-query.ts index 3e19082666..a7fdd8d8e9 100644 --- a/apps/web/src/page/alerts/editor/view-query.ts +++ b/apps/web/src/page/alerts/editor/view-query.ts @@ -1,4 +1,5 @@ -import type { AlertSignalType } from "@maple/domain/http" +import type { AlertSignalType, AnomalySignalType } from "@maple/domain/http" +import { ANOMALY_SIGNAL_LABELS, ANOMALY_SIGNALS } from "@maple/web-data/alerts/anomaly-rule" import { GROUP_BY_OPTIONS } from "@maple/query-engine/query-builder" import { signalTypeToKind } from "@maple/web-data/alerts/rule-modes" import { cn } from "@maple/ui/lib/utils" @@ -10,9 +11,10 @@ import { chipsView } from "@maple/views/ui/combobox-view" import { icon, type IconName } from "@maple/views/ui/icon" import { label } from "@maple/views/ui/label" import * as RadioGroup from "@maple/views/ui/radio-group" -import { segmentedSelect } from "@maple/views/ui/segmented-select" +import { multiSegmentedSelect, segmentedSelect } from "@maple/views/ui/segmented-select" import { stableSlice } from "../../kit/stable-slice" import { queryPanelView } from "../../kit/query-builder/view" +import { isProtectedDefault } from "./derive" import { spanAttributeKeysOf, traceFacetsOf } from "./derive-rows" import { baseValuesOf } from "./where-inputs" import type { RuleForm } from "./form" @@ -33,20 +35,31 @@ type H = HtmlBuilder /** What the section reads off the Model. */ type QueryView = SqlView & - Pick + Pick & { + /** Editing the org's default anomaly rule: its kind and scope are fixed. */ + readonly isDefaultAnomaly: boolean + } const KIND_OPTIONS: ReadonlyArray<{ value: SignalKind; label: string }> = [ { value: "builtin", label: "Built-in" }, { value: "builder_query", label: "Query builder" }, { value: "raw_query", label: "SQL" }, + { value: "anomaly", label: "Anomaly" }, ] const KIND_HINT = new Map([ ["builtin", "Five common signals on root spans. Switching modes carries the filters over."], ["builder_query", "Same builder as dashboards. Switching to Built-in keeps what it can express."], ["raw_query", "ClickHouse SQL, replayed per evaluation window."], + ["anomaly", "No query to write: Maple's anomaly detector decides what is unusual."], ]) +const ANOMALY_DESCRIPTION = + "Pages when Maple detects unusual error rate, latency, throughput, error spikes or log volume. The most specific anomaly rule handles each anomaly." + +const DEFAULT_ANOMALY_NOTE = + "This is the default anomaly rule. It handles every anomaly no narrower rule covers, so its scope stays empty and it stays on." + const BUILTIN_SIGNALS: ReadonlyArray<{ value: AlertSignalType label: string @@ -298,6 +311,98 @@ const builtinForm = (h: H, model: QueryView, form: RuleForm): Html => { ) } +/** An anomaly rule's scope: services, environments and detector signals, each empty for all. */ +const anomalyForm = (h: H, model: QueryView, form: RuleForm): Html => { + const facets = traceFacetsOf(model) + const isLocked = model.isDefaultAnomaly + return card( + h, + [ + h.div( + [h.Class("space-y-1 border-b px-3 py-2.5")], + [ + typography(h, { size: "sm", tone: "foreground", as: "p" }, [ANOMALY_DESCRIPTION]), + ...(isLocked + ? [ + hstack( + h, + { + gap: "1.5", + align: "start", + className: typographyClass({ size: "xs", tone: "muted" }), + }, + [ + icon(h, "LockIcon", { size: 12, className: "mt-0.5 shrink-0" }), + h.span([], [DEFAULT_ANOMALY_NOTE]), + ], + ), + ] + : []), + ], + ), + h.div( + [h.Class("space-y-2 p-3")], + [ + builtinRow( + h, + "Services", + inputOf("rule-services"), + "Empty covers every service.", + chipsPicker(h, model, "rule-services", form.serviceNames, { + items: facets.services, + placeholder: form.serviceNames.length > 0 ? "Add service…" : "All services", + emptyText: "No services found", + isDisabled: isLocked, + }), + ), + builtinRow( + h, + "Environment", + inputOf("rule-environments"), + "Empty covers every environment.", + chipsPicker(h, model, "rule-environments", form.environments, { + items: facets.environments, + placeholder: + form.environments.length === 0 ? "All environments" : "Add environment…", + emptyText: "No environments found", + isDisabled: isLocked, + }), + ), + builtinRow( + h, + "Signals", + ANOMALY_SIGNALS_ID, + form.anomalySignals.length === 0 + ? "None picked: every signal pages." + : "Only the picked signals page.", + h.div( + [h.Id(ANOMALY_SIGNALS_ID)], + [ + multiSegmentedSelect(h, { + options: Arr.map(ANOMALY_SIGNALS, (signal) => ({ + value: signal, + label: ANOMALY_SIGNAL_LABELS[signal], + ariaLabel: ANOMALY_SIGNAL_LABELS[signal], + })), + value: form.anomalySignals, + onToggle: (signal) => Message.ToggledAnomalySignal({ signal }), + size: "sm", + ariaLabel: "Anomaly signals", + isDisabled: isLocked, + className: "flex-wrap", + }), + ], + ), + ), + ], + ), + ], + "gap-0 overflow-hidden p-0", + ) +} + +const ANOMALY_SIGNALS_ID = "rule-anomaly-signals" + /** `AlertQueryPanel`: the dashboard `QueryPanel` without Clone, Remove or the visibility box. */ const builderPanel = (h: H, model: QueryView, form: RuleForm): Html => h.div( @@ -331,7 +436,12 @@ const query = (h: H, model: QueryView, form: RuleForm, timing: AlertQueryTiming [ hstack(h, { gap: "3", wrap: true }, [ segmentedSelect(h, { - options: KIND_OPTIONS, + options: model.isDefaultAnomaly + ? Arr.map(KIND_OPTIONS, (option) => ({ + ...option, + isDisabled: option.value !== "anomaly", + })) + : KIND_OPTIONS, value: kind, onChange: (next) => Message.ClickedSignalKind({ kind: next }), ariaLabel: "Query mode", @@ -341,11 +451,13 @@ const query = (h: H, model: QueryView, form: RuleForm, timing: AlertQueryTiming model.kindNotice ?? KIND_HINT.get(kind) ?? "", ]), ]), - kind === "builtin" - ? builtinForm(h, model, form) - : kind === "builder_query" - ? builderPanel(h, model, form) - : rawQueryEditor(h, model, form, timing), + kind === "anomaly" + ? anomalyForm(h, model, form) + : kind === "builtin" + ? builtinForm(h, model, form) + : kind === "builder_query" + ? builderPanel(h, model, form) + : rawQueryEditor(h, model, form, timing), ], ) } @@ -359,6 +471,7 @@ const viewOf = stableSlice((model: Model): QueryView => ({ autocomplete: model.autocomplete, isSqlCollapsed: model.isSqlCollapsed, reducerSelect: model.selects[REDUCER_SELECT_ID], + isDefaultAnomaly: isProtectedDefault(model), })) /** Step 1, "What to watch"; `form` is the evaluated draft (`evaluatedFormOf`). */ diff --git a/apps/web/src/page/alerts/editor/view.ts b/apps/web/src/page/alerts/editor/view.ts index 0d84dc5904..504d4ae9e6 100644 --- a/apps/web/src/page/alerts/editor/view.ts +++ b/apps/web/src/page/alerts/editor/view.ts @@ -1,3 +1,4 @@ +import { isDefaultAnomalyRule } from "@maple/web-data/alerts/anomaly-rule" import { deriveTierIssues, isFilledNumber } from "@maple/web-data/alerts/form-utils" import { cn } from "@maple/ui/lib/utils" import { Submodel } from "foldkit" @@ -107,10 +108,19 @@ const evaluatedFormOf = stableSlice((form: RuleForm): RuleForm => ({ notificationBody: "", })) +/** Who gets paged is missing: no destination on a tier that needs one. */ +const isNotifyMissingOf = (form: RuleForm): boolean => + form.signalType === "anomaly" + ? !isDefaultAnomalyRule(form) && + form.destinationIds.length === 0 && + (form.escalation?.destinationIds.length ?? 0) === 0 + : form.destinationIds.length === 0 || deriveTierIssues(form).includes("Critical destinations") + const surface = (h: H, model: Model, form: RuleForm, timeZone: string): ReadonlyArray => { const evaluated = evaluatedFormOf(form) - const isNotifyMissing = - form.destinationIds.length === 0 || deriveTierIssues(form).includes("Critical destinations") + // An anomaly rule has no condition to tune: the detector decides, so "When to alert" goes. + const isAnomaly = form.signalType === "anomaly" + const stepAfterQuery = isAnomaly ? 2 : 3 return [ h.div( [h.Class(cn("mx-auto w-full space-y-10", RULE_FORM_MAX_WIDTH))], @@ -122,47 +132,53 @@ const surface = (h: H, model: Model, form: RuleForm, timeZone: string): Readonly number: 1, hasDivider: false, title: "What to watch", - hint: "Pick a built-in signal, or build the query like a dashboard widget.", + hint: isAnomaly + ? "Pick which anomalies this rule pages on." + : "Pick a built-in signal, or build the query like a dashboard widget.", status: "done", }, [querySection(h, model, evaluated)], ), - ruleStep( - h, - { - number: 2, - title: "When to alert", - hint: "The dashed line is your threshold. Shaded bands show when the rule would have fired.", - status: isFilledNumber(form.threshold) ? "done" : "missing", - }, - [ - h.div( - [h.Class("space-y-4")], - [ - liveChartHero(h, model, form, evaluated, timeZone), - conditionFields(h, model, evaluated), - noiseSection(h, model, evaluated), - ], - ), - ], - ), + ...(isAnomaly + ? [] + : [ + ruleStep( + h, + { + number: 2, + title: "When to alert", + hint: "The dashed line is your threshold. Shaded bands show when the rule would have fired.", + status: isFilledNumber(form.threshold) ? "done" : "missing", + }, + [ + h.div( + [h.Class("space-y-4")], + [ + liveChartHero(h, model, form, evaluated, timeZone), + conditionFields(h, model, evaluated), + noiseSection(h, model, evaluated), + ], + ), + ], + ), + ]), h.div( [h.Class("grid gap-6 border-t pt-8 lg:grid-cols-2 lg:items-start")], [ ruleStep( h, { - number: 3, + number: stepAfterQuery, hasDivider: false, title: "Who gets notified", - status: isNotifyMissing ? "missing" : "done", + status: isNotifyMissingOf(form) ? "missing" : "done", }, [notificationsSection(h, model, form)], ), ruleStep( h, { - number: 4, + number: stepAfterQuery + 1, hasDivider: false, title: "Name it", status: Str.trim(form.name).length === 0 ? "missing" : "done", diff --git a/apps/web/src/page/alerts/hub/derive.ts b/apps/web/src/page/alerts/hub/derive.ts index 200125c77b..64a562fb8c 100644 --- a/apps/web/src/page/alerts/hub/derive.ts +++ b/apps/web/src/page/alerts/hub/derive.ts @@ -1,4 +1,4 @@ -import type { AlertDestinationDocument } from "@maple/domain/http" +import type { AlertDestinationDocument, AlertIncidentDocument } from "@maple/domain/http" import { type AlertsOverviewReady, deriveOverview } from "@maple/web-data/alerts/overview" import { buildRuleStatesByRuleId, @@ -6,10 +6,10 @@ import { rowToAlertIncidentDocument, rowToAlertRuleDocument, } from "@maple/web-data/collections/alerts" -import { Option, Array as Arr } from "effect" +import { Option, Array as Arr, String as Str } from "effect" import { AsyncData } from "foldkit" import { lastBy } from "../../kit/last-by" -import type { RuleBucket } from "./message" +import type { InboxSignal, IncidentStatusFilter, RuleBucket } from "./message" import type { Model } from "./model" type OverviewKey = readonly [ @@ -44,6 +44,68 @@ const overviewFor = lastBy( export const overviewOf = (model: Model): Option.Option => overviewFor([model.rules, model.states, model.incidents, model.deliveries.data, model.nowMs]) +/** An incident is about a service: its labels say so, else its stored "service (env)" label. */ +const isAboutService = (incident: AlertIncidentDocument, serviceName: string): boolean => + incident.labels?.service !== undefined + ? incident.labels.service === serviceName + : incident.groupLabel === serviceName || + (incident.groupLabel != null && Str.startsWith(`${serviceName} (`)(incident.groupLabel)) + +type SignalKey = readonly [...OverviewKey, string | undefined] + +const anomalyOverviewFor = lastBy( + ([ + rules, + states, + incidents, + deliveries, + nowMs, + serviceName, + ]: SignalKey): Option.Option => { + if (rules === null || incidents === null || nowMs === null) return Option.none() + const stateRows = states ?? [] + const statesByRule = buildRuleStatesByRuleId(stateRows) + const anomalyIncidents = Arr.filter( + Arr.map( + Arr.filter(incidents, (row) => row.signal_type === "anomaly"), + rowToAlertIncidentDocument, + ), + (incident) => serviceName === undefined || isAboutService(incident, serviceName), + ) + return Option.some( + deriveOverview({ + rules: Arr.map( + Arr.filter(rules, (row) => row.signal_type === "anomaly"), + (row) => rowToAlertRuleDocument(row, statesByRule), + ), + incidents: anomalyIncidents, + states: stateRows, + deliveryEvents: Option.getOrElse(AsyncData.getData(deliveries), () => []), + now: nowMs, + }), + ) + }, +) + +/** The overview narrowed to anomaly rules and incidents, and to one service when the view names one. */ +export const anomalyOverviewOf = (model: Model): Option.Option => + anomalyOverviewFor([ + model.rules, + model.states, + model.incidents, + model.deliveries.data, + model.nowMs, + model.search.serviceName, + ]) + +/** The Inbox's signal view; old anomaly links say "anomalies". */ +export const resolveSignal = (signal: string | undefined): InboxSignal => + signal === "anomaly" || signal === "anomalies" ? "anomaly" : null + +/** A signal view lists open incidents unless the link asks for resolved or all of them. */ +export const resolveIncidentStatus = (status: string | undefined): IncidentStatusFilter => + status === "resolved" || status === "all" ? status : "open" + const destinationsFor = lastBy( ([rows]: readonly [Model["destinations"]]): ReadonlyArray => rowsToAlertDestinationDocuments(rows ?? []), diff --git a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts new file mode 100644 index 0000000000..2a03c45004 --- /dev/null +++ b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts @@ -0,0 +1,217 @@ +import * as Policy from "@maple/domain/policy" +import { OrgId } from "@maple/domain" +import { AlertRuleId } from "@maple/domain/http" +import { UNTAGGED_KEY } from "@maple/web-data/alerts/tag-grouping" +import { Schema, Array as Arr } from "effect" +import { + click, + expect as expectView, + expectOutMessage, + given, + role, + scene, + text, + withViewInputs, +} from "foldkit/scene" +import { describe, expect, test } from "vitest" +import * as Menu from "@maple/views/ui/menu" +import { PageOutMessage, type PageContext } from "../../contract" +import { + anomalyIncidentRow, + defaultAnomalyRuleRow, + scopedAnomalyRuleRow, +} from "../shared/anomaly-test-fixtures" +import { destinationRow, incidentRow, NOW, ORG, ruleId, ruleRow, stateRow } from "../shared/test-rows" +import { Message } from "./message" +import type { Model } from "./model" +import { init, ruleMenuId, update } from "./update" +import { view as pageView } from "./view" + +const ENGINEER = Policy.grantsOf(["org:engineer"]) +const orgId = Schema.decodeSync(OrgId)(ORG) +const context: PageContext = { orgId, pinnedNamespace: null, timeZone: "UTC" } +const pageUpdate = (model: Model, message: Message) => update(model, message, context) +const view = withViewInputs(pageView, { context })() + +const OPEN_CHECKOUT = anomalyIncidentRow({ + n: 1, + ruleN: 8, + severity: "critical", + service: "checkout", + environment: "production", +}) +const OPEN_SEARCH = anomalyIncidentRow({ + n: 2, + ruleN: 7, + severity: "warning", + service: "search", + environment: "production", +}) +const RESOLVED_CHECKOUT = anomalyIncidentRow({ + n: 3, + ruleN: 8, + severity: "warning", + service: "checkout", + environment: "staging", + status: "resolved", +}) + +const loaded = ( + search: Model["search"], + incidents = [ + incidentRow({ rule_name: "Payment p95 latency" }), + OPEN_CHECKOUT, + OPEN_SEARCH, + RESOLVED_CHECKOUT, + ], +): Model => + Arr.reduce( + [ + Message.ReadClock({ nowMs: NOW }), + Message.UpdatedRules({ + orgId: ORG, + rows: [ + ruleRow(2, { name: "Payment p95 latency" }), + defaultAnomalyRuleRow(), + scopedAnomalyRuleRow(), + ], + }), + Message.UpdatedRuleStates({ + orgId: ORG, + rows: [ + stateRow(2, { last_status: "breached", last_value: 1840 }), + stateRow(7, { last_value: 1 }), + ], + }), + Message.UpdatedIncidents({ orgId: ORG, rows: incidents }), + Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + Message.CompletedFetchSession({ orgId: ORG, grants: ENGINEER }), + ], + init({ _tag: "Alerts", search }, context).model, + (model, message) => pageUpdate(model, message).model, + ) + +describe("alerts hub: anomalies", () => { + test("the Anomalies filter lists anomaly incidents alone, and turning it off drops its status and service", () => { + scene( + { update: pageUpdate, view }, + given(loaded({})), + expectView(text("Payment p95 latency")).toExist(), + click(role("button", { name: /^Anomalies/ })), + expectOutMessage( + PageOutMessage.RequestedNavigation({ + url: "/alerts?tab=inbox&signal=anomaly", + isReplace: false, + }), + ), + ) + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", serviceName: "checkout", anomalyStatus: "all" })), + click(role("button", { name: /^Anomalies/ })), + expectOutMessage(PageOutMessage.RequestedNavigation({ url: "/alerts", isReplace: false })), + ) + }) + + test("the anomaly view shows open anomalies by their group, without other rules' incidents", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly" })), + expectView(text("2 firing")).toExist(), + expectView(role("link", { name: "Anomaly on checkout (production)" })).toExist(), + expectView(role("link", { name: "Anomaly on search (production)" })).toExist(), + expectView(role("link", { name: "Payment p95 latency" })).not.toExist(), + // The severity is the whole reading: no threshold, no dash placeholder. + expectView(text("Critical")).toExist(), + expectView(text(/^ \/ /)).not.toExist(), + // Resolved anomalies wait behind the status switch. + expectView(role("link", { name: "Anomaly on checkout (staging)" })).not.toExist(), + ) + }) + + test("a service and the resolved status narrow the view, as the old anomalies links did", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", serviceName: "checkout", anomalyStatus: "resolved" })), + expectView(role("link", { name: "Anomaly on checkout (staging)" })).toExist(), + expectView(role("link", { name: "Anomaly on checkout (production)" })).not.toExist(), + expectView(role("link", { name: "Anomaly on search (production)" })).not.toExist(), + click(role("button", { name: "Remove checkout" })), + expectOutMessage( + PageOutMessage.RequestedNavigation({ + url: "/alerts?signal=anomaly&anomalyStatus=resolved", + isReplace: false, + }), + ), + ) + }) + + test("with no anomalies the view says what the detector watches", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly" }, [incidentRow({ rule_name: "Payment p95 latency" })])), + expectView(text("No anomalies")).toExist(), + expectView(text(/^Maple watches error rate, latency, throughput/)).toExist(), + ) + }) + + test("the rules list marks the default anomaly rule, reads scopes, and offers no way to turn it off", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ tab: "rules" })), + expectView(text("Default")).toExist(), + expectView(text("Anomaly · all services · all environments · all signals")).toExist(), + expectView(text("Anomaly · checkout · production · Error rate, p95 latency")).toExist(), + expectView(text("Warning anomaly open")).toExist(), + ) + const model = loaded({ tab: "rules" }) + const toggle = (n: number) => + pageUpdate( + model, + Message.GotRowMenuMessage({ + menuId: ruleMenuId(Schema.decodeSync(AlertRuleId)(ruleId(n)), UNTAGGED_KEY), + message: Menu.Message.ClickedItem({ index: 1, item: { kind: "Item", value: "toggle" } }), + }), + ) + // A scoped anomaly rule turns off like any rule; the default one does not. + expect(toggle(8).model.togglingRuleId).toBe(ruleId(8)) + expect(toggle(7).model.togglingRuleId).toBeNull() + }) + + test("an unrouted default anomaly rule is normal; an unrouted scoped one is a blind spot", () => { + const withScoped = (rows: Parameters[0]["rows"]) => + Arr.reduce( + [ + Message.UpdatedRules({ orgId: ORG, rows }), + Message.UpdatedRuleStates({ + orgId: ORG, + rows: [stateRow(7, { last_value: 0 }), stateRow(9, { last_value: 0 })], + }), + ], + loaded({}), + (model, message) => pageUpdate(model, message).model, + ) + scene( + { update: pageUpdate, view }, + given(withScoped([defaultAnomalyRuleRow()])), + expectView(text("Unrouted")).not.toExist(), + ) + scene( + { update: pageUpdate, view }, + given( + withScoped([ + defaultAnomalyRuleRow(), + scopedAnomalyRuleRow(8), + { + ...scopedAnomalyRuleRow(9), + name: "Search anomalies", + thresholds_json: null, + destination_ids_json: [], + }, + ]), + ), + expectView(text("Unrouted")).toExist(), + expectView(text("Search anomalies")).toExist(), + ) + }) +}) diff --git a/apps/web/src/page/alerts/hub/message.ts b/apps/web/src/page/alerts/hub/message.ts index d5b87c712f..f2cdd1658b 100644 --- a/apps/web/src/page/alerts/hub/message.ts +++ b/apps/web/src/page/alerts/hub/message.ts @@ -25,6 +25,14 @@ import { ServiceNamespaces } from "./uptime-groups" export const RuleBucket = Schema.Literals(["firing", "blind", "healthy", "disabled"]) export type RuleBucket = typeof RuleBucket.Type +/** The Inbox's signal view: every incident, or anomalies alone. */ +export const InboxSignal = Schema.NullOr(Schema.Literal("anomaly")) +export type InboxSignal = typeof InboxSignal.Type + +/** Which incidents a signal view lists. */ +export const IncidentStatusFilter = Schema.Literals(["open", "resolved", "all"]) +export type IncidentStatusFilter = typeof IncidentStatusFilter.Type + const rows = (row: S) => ({ orgId: Schema.String, rows: Schema.Array(row) }) const outcome = (success: S) => Schema.Result(success, LoadFailure) @@ -48,6 +56,9 @@ export const Message = defineMessageUnion({ ChangedRulesQuery: { value: Schema.String }, ClickedStatusFilter: { bucket: RuleBucket }, ClickedClearRuleFilters: {}, + ClickedInboxSignal: { signal: InboxSignal }, + ClickedIncidentStatus: { status: IncidentStatusFilter }, + ClickedClearInboxService: {}, ClickedShowAllRules: {}, ClickedToggleRule: { ruleId: AlertRuleId, isEnabled: Schema.Boolean }, CompletedToggleRule: { ruleId: AlertRuleId, result: outcome(Schema.Void) }, diff --git a/apps/web/src/page/alerts/hub/update.ts b/apps/web/src/page/alerts/hub/update.ts index 9e1c3c6f3a..9a5de772c2 100644 --- a/apps/web/src/page/alerts/hub/update.ts +++ b/apps/web/src/page/alerts/hub/update.ts @@ -1,5 +1,6 @@ import type { UptimeMonitorId } from "@maple/domain" import type { AlertDestinationId, AlertRuleId } from "@maple/domain/http" +import { isDefaultAnomalyRule } from "@maple/web-data/alerts/anomaly-rule" import { buildDestinationUpdateParamsV2, destinationToFormState } from "@maple/web-data/alerts/form-utils" import { rowToAlertDestinationDocument, rowToAlertRuleDocument } from "@maple/web-data/collections/alerts" import { Array, Option, Result, String as Str } from "effect" @@ -362,7 +363,9 @@ const updateNewRuleMenu = (model: Model, message: Menu.Message): Return => { ? newMonitorHref({ serviceName }) : result.outMessage.value === "slo" ? newSloHref({ serviceName }) - : createHref({ serviceName }) + : result.outMessage.value === "anomaly" + ? createHref({ serviceName, template: "anomaly" }) + : createHref({ serviceName }) return { model: next, commands, @@ -383,15 +386,18 @@ const pickedFromMenu = (model: Model, menuId: string, value: string): Return => return Option.match(rule, { onNone: () => ({ model }), onSome: (row) => - value === "open" - ? { - model, - outMessage: PageOutMessage.RequestedNavigation({ - url: ruleHref(row.id), - isReplace: false, - }), - } - : toggleRule(model, row.id, !row.enabled), + // The default anomaly rule stays on; its menu offers no toggle, and a stale pick does nothing. + value === "toggle" && isDefaultAnomalyRule(row) + ? { model } + : value === "open" + ? { + model, + outMessage: PageOutMessage.RequestedNavigation({ + url: ruleHref(row.id), + isReplace: false, + }), + } + : toggleRule(model, row.id, !row.enabled), }) } return Option.match( @@ -472,6 +478,23 @@ export const update = (model: Model, message: Message, context: PageContext): Re model: { ...model, rulesQuery: "" }, outMessage: navigate({ ...model.search, status: undefined }), }), + ClickedInboxSignal: ({ signal }) => ({ + model, + // The status and service belong to the signal view: plain Inbox drops them. + outMessage: navigate( + signal === null + ? { ...model.search, signal: undefined, anomalyStatus: undefined, serviceName: undefined } + : { ...model.search, tab: "inbox", signal, anomalyStatus: undefined }, + ), + }), + ClickedIncidentStatus: ({ status }) => ({ + model, + outMessage: navigate({ ...model.search, anomalyStatus: status === "open" ? undefined : status }), + }), + ClickedClearInboxService: () => ({ + model, + outMessage: navigate({ ...model.search, serviceName: undefined }), + }), ClickedShowAllRules: () => ({ model: { ...model, isShowingAllRules: true } }), ClickedToggleRule: ({ ruleId, isEnabled }) => toggleRule(model, ruleId, isEnabled), CompletedToggleRule: ({ result }) => ({ diff --git a/apps/web/src/page/alerts/hub/view-anomalies.ts b/apps/web/src/page/alerts/hub/view-anomalies.ts new file mode 100644 index 0000000000..6ba0b5cdde --- /dev/null +++ b/apps/web/src/page/alerts/hub/view-anomalies.ts @@ -0,0 +1,217 @@ +import type { AlertIncidentDocument } from "@maple/domain/http" +import { formatRelativeShortFrom } from "@maple/ui/lib/time-format" +import { cn } from "@maple/ui/lib/utils" +import { formatAlertDuration } from "@maple/web-data/alerts/form-utils" +import type { AlertsOverviewReady } from "@maple/web-data/alerts/overview" +import { incidentSubline, incidentTitle } from "@maple/web-data/alerts/overview-derive" +import { Array as Arr, Order } from "effect" +import type { Html, HtmlBuilder } from "foldkit/html" +import { borderedBox } from "@maple/views/ui/bordered-box" +import { empty, emptyDescription, emptyHeader, emptyMedia, emptyTitle } from "@maple/views/ui/empty" +import { eyebrow } from "@maple/views/ui/eyebrow" +import { filterChip } from "@maple/views/ui/filter-chip" +import { icon } from "@maple/views/ui/icon" +import { removableChip } from "@maple/views/ui/removable-chip" +import { segmentedSelect } from "@maple/views/ui/segmented-select" +import { stripClass } from "@maple/views/ui/rule" +import { hstack } from "@maple/views/ui/stack" +import { rowLink } from "@maple/views/ui/table" +import { gridListRow } from "../../kit/grid-list-row" +import { typography, typographyClass } from "@maple/views/ui/typography" +import { listToolbar } from "../../kit/list-toolbar" +import { severityBadge } from "../shared/badges" +import { incidentHref } from "../shared/links" +import { isoMs } from "../shared/time" +import { inboxTab, type InboxAcknowledge } from "./view-inbox" +import { Message, type InboxSignal, type IncidentStatusFilter } from "./message" + +// The Inbox's signal view: anomaly incidents alone, open, resolved or both (what `/anomalies` was). + +type H = HtmlBuilder + +const RESOLVED_LIMIT = 50 +const RESOLVED_GRID = "grid grid-cols-[minmax(0,1fr)_110px_90px] items-center gap-4" + +const STATUS_OPTIONS: ReadonlyArray<{ value: IncidentStatusFilter; label: string }> = [ + { value: "open", label: "Open" }, + { value: "resolved", label: "Resolved" }, + { value: "all", label: "All" }, +] + +/** The Inbox's filter row: the Anomalies view toggle and, inside it, the status and service. */ +export const inboxFilters = ( + h: H, + options: Readonly<{ + signal: InboxSignal + status: IncidentStatusFilter + serviceName: string | undefined + openAnomalies: number + }>, +): Html => + listToolbar(h, { + className: "pb-0", + leading: hstack(h, { gap: "2", wrap: true }, [ + filterChip( + h, + { + isPressed: options.signal === "anomaly", + onClick: Message.ClickedInboxSignal({ + signal: options.signal === "anomaly" ? null : "anomaly", + }), + count: String(options.openAnomalies), + }, + ["Anomalies"], + ), + ...(options.signal === "anomaly" + ? [ + segmentedSelect(h, { + options: STATUS_OPTIONS, + value: options.status, + onChange: (status) => Message.ClickedIncidentStatus({ status }), + ariaLabel: "Anomaly status", + size: "xs", + }), + ...(options.serviceName === undefined + ? [] + : [ + removableChip(h, { + label: options.serviceName, + onRemove: Message.ClickedClearInboxService(), + }), + ]), + ] + : []), + ]), + }) + +/** Nothing to list: the detector watches every service and opens an anomaly here when it finds one. */ +const anomaliesEmpty = (h: H, status: IncidentStatusFilter, serviceName: string | undefined): Html => + empty( + h, + [ + emptyHeader(h, [ + emptyMedia(h, { variant: "icon" }, [icon(h, "PulseIcon", { size: 16 })]), + emptyTitle(h, [ + status === "resolved" ? "No resolved anomalies" : "No anomalies", + ...(serviceName === undefined ? [] : [` on ${serviceName}`]), + ]), + emptyDescription(h, [ + "Maple watches error rate, latency, throughput, error spikes and log volume on every service, and opens an anomaly here when one looks unusual against its baseline.", + ]), + ]), + ], + "rounded-md border border-dashed py-12", + ) + +const byResolvedDesc = Order.flip( + Order.mapInput(Order.Number, (incident: AlertIncidentDocument) => + isoMs(incident.resolvedAt ?? incident.lastTriggeredAt), + ), +) + +const resolvedRow = (h: H, incident: AlertIncidentDocument, now: number): Html => + gridListRow(h, { key: incident.id, grid: RESOLVED_GRID }, [ + h.div( + [h.Class("min-w-0 space-y-1")], + [ + hstack(h, { gap: "2", shrink: true }, [ + severityBadge(h, incident.peakSeverity ?? incident.severity), + rowLink( + h, + { + href: incidentHref(incident.id), + className: "min-w-0 truncate font-medium", + attributes: [h.Title(incidentTitle(incident))], + }, + [incidentTitle(incident)], + ), + ]), + typography(h, { size: "xs", tone: "muted", truncate: true, as: "p" }, [ + incidentSubline(incident), + ]), + ], + ), + typography(h, { size: "xs", tone: "muted", mono: true, numeric: true }, [ + incident.resolvedAt === null + ? "" + : `${formatRelativeShortFrom(isoMs(incident.resolvedAt), now)} ago`, + ]), + typography(h, { size: "xs", mono: true, numeric: true }, [ + formatAlertDuration(incident.firstTriggeredAt, incident.resolvedAt), + ]), + ]) + +const resolvedList = (h: H, incidents: ReadonlyArray, now: number): Html => { + const shown = Arr.take(Arr.sort(incidents, byResolvedDesc), RESOLVED_LIMIT) + return h.section( + [h.Class("space-y-3")], + [ + eyebrow(h, { as: "h3" }, ["Resolved"]), + borderedBox(h, { overflow: "x-auto" }, [ + h.div( + [h.Class("min-w-[520px]")], + [ + h.div( + [ + h.Class( + cn( + RESOLVED_GRID, + typographyClass({ + size: "xs", + tone: "muted", + className: stripClass("head"), + }), + ), + ), + ], + [ + h.span( + [], + [ + "Resolved · ", + String(incidents.length), + ...(incidents.length > shown.length + ? [`, newest ${shown.length}`] + : []), + ], + ), + h.span([], ["Resolved"]), + h.span([], ["Lasted"]), + ], + ), + h.ul( + [h.Class("divide-y")], + Arr.map(shown, (incident) => resolvedRow(h, incident, now)), + ), + ], + ), + ]), + ], + ) +} + +/** The Anomalies view of the Inbox, over the overview narrowed to anomalies. */ +export const anomalyInbox = ( + h: H, + data: AlertsOverviewReady, + options: Readonly<{ + status: IncidentStatusFilter + serviceName: string | undefined + acknowledge: InboxAcknowledge + }>, +): Html => { + const resolved = Arr.filter(data.incidents, (incident) => incident.status === "resolved") + const now = data.timelineRange.max + const hasOpenView = options.status !== "resolved" + const hasResolvedView = options.status !== "open" + const isEmpty = + (hasOpenView ? data.incidents.length === 0 : true) && (hasResolvedView ? resolved.length === 0 : true) + if (isEmpty) return anomaliesEmpty(h, options.status, options.serviceName) + return h.div( + [h.Class("space-y-8")], + [ + ...(hasOpenView ? [inboxTab(h, data, options.acknowledge)] : []), + ...(hasResolvedView && resolved.length > 0 ? [resolvedList(h, resolved, now)] : []), + ], + ) +} diff --git a/apps/web/src/page/alerts/hub/view-inbox.ts b/apps/web/src/page/alerts/hub/view-inbox.ts index 53a907d3a6..16d36e87bd 100644 --- a/apps/web/src/page/alerts/hub/view-inbox.ts +++ b/apps/web/src/page/alerts/hub/view-inbox.ts @@ -33,6 +33,7 @@ import { severityBadge } from "../shared/badges" import { incidentHref, ruleHref } from "../shared/links" import { sortIncidents } from "../shared/sort" import { isoMs, msIso } from "../shared/time" +import { gridListRow } from "../../kit/grid-list-row" import { typography, typographyClass } from "@maple/views/ui/typography" import { stripClass } from "@maple/views/ui/rule" import { hstack } from "@maple/views/ui/stack" @@ -207,11 +208,7 @@ const openIncidentRow = ( const isHeld = incident.holdReason !== null const acked = acknowledgedLine(incident) const tone = incident.severity === "critical" ? "text-severity-error" : "text-severity-warn" - return h.li( - [ - h.Key(incident.id), - h.Class(cn(INCIDENT_GRID, "relative px-4 py-3 hover:bg-surface-sunken active:bg-surface-sunken")), - ], + return gridListRow(h, { key: incident.id, grid: INCIDENT_GRID }, [ h.span([ h.AriaHidden(true), @@ -254,7 +251,9 @@ const openIncidentRow = ( isHeld ? h.span([h.Class("text-muted-foreground")], ["no data"]) : h.span([h.Class(tone)], [incidentValues(incident).now]), - h.span([h.Class("text-muted-foreground")], [" / ", incidentValues(incident).threshold]), + ...(incidentValues(incident).threshold === null + ? [] + : [h.span([h.Class("text-muted-foreground")], [" / ", incidentValues(incident).threshold])]), ]), typography(h, { size: "xs", mono: true, numeric: true }, [ formatAlertDuration(incident.firstTriggeredAt, msIso(now)), diff --git a/apps/web/src/page/alerts/hub/view-rules.ts b/apps/web/src/page/alerts/hub/view-rules.ts index d4294e063a..97d1e3825f 100644 --- a/apps/web/src/page/alerts/hub/view-rules.ts +++ b/apps/web/src/page/alerts/hub/view-rules.ts @@ -2,6 +2,7 @@ import type { AlertDestinationDocument, AlertIncidentDocument, AlertRuleDocument import { countLabel } from "@maple/ui/lib/format" import type { Tone } from "@maple/ui/lib/tone" import { cn } from "@maple/ui/lib/utils" +import { anomalySeverityLabel, isDefaultAnomalyRule } from "@maple/web-data/alerts/anomaly-rule" import { formatSignalValue } from "@maple/web-data/alerts/form-utils" import type { AlertsOverviewReady } from "@maple/web-data/alerts/overview" import { @@ -15,6 +16,7 @@ import { type DerivedRuleStatus, worstState } from "@maple/web-data/alerts/rule- import { groupByTag, type TagGroup, UNTAGGED_KEY } from "@maple/web-data/alerts/tag-grouping" import type { Html, HtmlBuilder } from "foldkit/html" import { alert, alertDescription } from "@maple/views/ui/alert" +import { badge } from "@maple/views/ui/display" import { button } from "@maple/views/ui/button" import { filterChip } from "@maple/views/ui/filter-chip" import * as Menu from "@maple/views/ui/menu" @@ -30,6 +32,7 @@ import { Message, type RuleBucket } from "./message" import { canManage, type Model } from "./model" import { ruleMenuId } from "./update" import { searchInput } from "@maple/views/ui/search-input" +import { gridListRow } from "../../kit/grid-list-row" import { typography, typographyClass } from "@maple/views/ui/typography" import { stripClass } from "@maple/views/ui/rule" import { hstack } from "@maple/views/ui/stack" @@ -293,25 +296,27 @@ type RowInputs = Readonly<{ const ruleRow = (h: H, model: Model, row: RowInputs): Html => { const { rule } = row const menuId = ruleMenuId(rule.id, row.groupKey) + const isDefault = isDefaultAnomalyRule(rule) + // The default anomaly rule stays on: anomaly detection itself is what turns it off. const entries: ReadonlyArray = [ { _tag: "Item", value: "open", label: "Open rule" }, - { - _tag: "Item", - value: "toggle", - label: rule.enabled ? "Disable rule" : "Enable rule", - isDisabled: model.togglingRuleId !== null, - }, + ...(isDefault + ? [] + : [ + { + _tag: "Item" as const, + value: "toggle", + label: rule.enabled ? "Disable rule" : "Enable rule", + isDisabled: model.togglingRuleId !== null, + }, + ]), ] - return h.li( - [ - h.Key(rule.id), - h.Class(cn(ROW_GRID, "relative px-4 py-3 hover:bg-surface-sunken active:bg-surface-sunken")), - ], - [ - statusDot(h, { tone: BUCKET_TONE.get(row.bucket) ?? "neutral", size: "lg" }), - h.div( - [h.Class("min-w-0 space-y-0.5")], - [ + return gridListRow(h, { key: rule.id, grid: ROW_GRID }, [ + statusDot(h, { tone: BUCKET_TONE.get(row.bucket) ?? "neutral", size: "lg" }), + h.div( + [h.Class("min-w-0 space-y-0.5")], + [ + hstack(h, { gap: "2", shrink: true }, [ rowLink( h, { @@ -324,46 +329,49 @@ const ruleRow = (h: H, model: Model, row: RowInputs): Html => { }, [rule.name], ), - typography(h, { size: "xs", tone: "muted", mono: true, truncate: true, as: "p" }, [ - ruleConditionSummary(rule), - ]), - ], - ), - headroomMeter(h, rule, row.derived, row.lastValue), - stateRibbon(h, ruleRibbon(row.incidents, row.ribbonRange, RIBBON_CELLS)), - routesTo(h, row.destinations, rule.enabled), - h.span( - [ - h.Class( - cn( - typographyClass({ - size: "xs", - mono: true, - numeric: true, - className: "text-right", - }), - row.fired === 0 && "text-muted-foreground", - ), + ...(isDefault + ? [badge(h, { variant: "outline", size: "sm", className: "shrink-0" }, ["Default"])] + : []), + ]), + typography(h, { size: "xs", tone: "muted", mono: true, truncate: true, as: "p" }, [ + ruleConditionSummary(rule), + ]), + ], + ), + headroomMeter(h, rule, row.derived, row.lastValue), + stateRibbon(h, ruleRibbon(row.incidents, row.ribbonRange, RIBBON_CELLS)), + routesTo(h, row.destinations, rule.enabled), + h.span( + [ + h.Class( + cn( + typographyClass({ + size: "xs", + mono: true, + numeric: true, + className: "text-right", + }), + row.fired === 0 && "text-muted-foreground", ), - ], - [String(row.fired)], - ), - h.span( - [h.Class(ROW_LINK_LIFT)], - canManage(model) - ? [ - rowActionsMenu(h, { - slotId: menuId, - model: model.menus[menuId] ?? Menu.init(menuId), - entries, - label: `Actions for ${rule.name}`, - toParentMessage: (message) => Message.GotRowMenuMessage({ menuId, message }), - }), - ] - : [], - ), - ], - ) + ), + ], + [String(row.fired)], + ), + h.span( + [h.Class(ROW_LINK_LIFT)], + canManage(model) + ? [ + rowActionsMenu(h, { + slotId: menuId, + model: model.menus[menuId] ?? Menu.init(menuId), + entries, + label: `Actions for ${rule.name}`, + toParentMessage: (message) => Message.GotRowMenuMessage({ menuId, message }), + }), + ] + : [], + ), + ]) } const headroomMeter = ( @@ -375,6 +383,13 @@ const headroomMeter = ( if (!rule.enabled) return typography(h, { size: "xs", tone: "muted" }, ["Not evaluating"]) if (derived?.status === "error") return typography(h, { size: "xs", tone: "error" }, ["Query fails to run"]) + // An anomaly rule's reading is the worst open anomaly's severity, not a value against a threshold. + if (rule.signalType === "anomaly") + return value === null || value < 1 + ? typography(h, { size: "xs", tone: "muted" }, ["No open anomaly"]) + : typography(h, { size: "xs", tone: value >= 2 ? "error" : "warning" }, [ + `${anomalySeverityLabel(value)} anomaly open`, + ]) if (value === null) return typography(h, { size: "xs", tone: "muted" }, ["Waiting on data"]) const meter = ruleHeadroom(rule, value) const tone = meter.breached diff --git a/apps/web/src/page/alerts/hub/view.ts b/apps/web/src/page/alerts/hub/view.ts index 21dee8d739..13456fa958 100644 --- a/apps/web/src/page/alerts/hub/view.ts +++ b/apps/web/src/page/alerts/hub/view.ts @@ -1,4 +1,4 @@ -import { isManagedSignalType } from "@maple/domain/http" +import { type AlertIncidentId, isManagedSignalType } from "@maple/domain/http" import type { AlertsOverviewReady } from "@maple/web-data/alerts/overview" import { makeClientErrorBody } from "@maple/web-data/lib/error-messages" import { Option, Array as Arr } from "effect" @@ -16,10 +16,20 @@ import { dashboardPage } from "../../kit/dashboard-page" import { linkTabStrip } from "../../kit/underline-tabs" import { alertsHref, createHref } from "../shared/links" import { newSloHref } from "../shared/slo-commands" -import { type AlertsTab, destinationsOf, overviewOf, resolveStatus, resolveTab } from "./derive" +import { + type AlertsTab, + anomalyOverviewOf, + destinationsOf, + overviewOf, + resolveIncidentStatus, + resolveSignal, + resolveStatus, + resolveTab, +} from "./derive" import { Message } from "./message" import { canManage, type Model } from "./model" import { alertsEmptyState } from "./view-empty" +import { anomalyInbox, inboxFilters } from "./view-anomalies" import { inboxTab, unacknowledgedCount } from "./view-inbox" import { routingTab } from "./view-routing" import * as DestinationDialogView from "../destination/view" @@ -128,6 +138,13 @@ const newRuleButton = (h: H, model: Model): Html => "ChartLineIcon", "Error rate, latency or any query on traces, logs and metrics.", ), + newRuleItem( + h, + "anomaly", + "Anomaly rule", + "PulseIcon", + "Route what Maple's anomaly detector finds for some services, environments or signals.", + ), // Uptime monitors are admin-only to create; the API refuses members too. ...(canManage(model) ? [ @@ -253,14 +270,39 @@ const hubBody = ( const hasManagedIncidents = Arr.some(data.openIncidents, (incident) => isManagedSignalType(incident.signalType), ) - if (tab === "inbox") - return data.rules.length === 0 && !hasManagedIncidents - ? emptyState - : inboxTab(h, data, { - onAcknowledge: (incidentId, acknowledged) => - Message.ClickedAcknowledgeIncident({ incidentId, acknowledged }), - pendingIds: model.acknowledgingIncidentIds, - }) + const acknowledge = { + onAcknowledge: (incidentId: AlertIncidentId, acknowledged: boolean) => + Message.ClickedAcknowledgeIncident({ incidentId, acknowledged }), + pendingIds: model.acknowledgingIncidentIds, + } + if (tab === "inbox") { + const signal = resolveSignal(model.search.signal) + const hasAnomalies = Arr.some(data.incidents, (incident) => incident.signalType === "anomaly") + if (data.rules.length === 0 && !hasManagedIncidents && !hasAnomalies && signal === null) + return emptyState + const filters = inboxFilters(h, { + signal, + status: resolveIncidentStatus(model.search.anomalyStatus), + serviceName: model.search.serviceName, + openAnomalies: Arr.filter( + data.openIncidents, + (incident) => incident.signalType === "anomaly", + ).length, + }) + const body = + signal === "anomaly" + ? Option.match(anomalyOverviewOf(model), { + onNone: () => hubSkeleton(h), + onSome: (anomalies) => + anomalyInbox(h, anomalies, { + status: resolveIncidentStatus(model.search.anomalyStatus), + serviceName: model.search.serviceName, + acknowledge, + }), + }) + : inboxTab(h, data, acknowledge) + return h.div([h.Class("space-y-6")], [filters, body]) + } return rulesTab(h, model, { data, destinations, diff --git a/apps/web/src/page/alerts/incident/command.ts b/apps/web/src/page/alerts/incident/command.ts index 791c201b28..dab2d8007f 100644 --- a/apps/web/src/page/alerts/incident/command.ts +++ b/apps/web/src/page/alerts/incident/command.ts @@ -1,4 +1,4 @@ -import { AlertIncidentId, AlertRuleId, IsoDateTimeString } from "@maple/domain/http" +import { AlertIncidentId, AlertRuleId, AnomalyIncidentId, IsoDateTimeString } from "@maple/domain/http" import { V2AlertRulePreviewParams, V2InvestigationCreateParams } from "@maple/domain/http/v2" import { Clock, Effect, Schema } from "effect" import { Command } from "foldkit" @@ -121,3 +121,31 @@ export const FetchIncidentMonitors = Command.define("FetchAlertIncidentMonitors" Effect.map((result) => Message.CompletedFetchMonitors({ key, result })), ), }) + +/** An anomaly rule's incident: the anomaly the detector opened, its evidence. */ +export const FetchIncidentAnomaly = Command.define("FetchAlertIncidentAnomaly", { + args: { key: Schema.String, id: AnomalyIncidentId }, + messages: [Message.CompletedFetchAnomaly], + execute: ({ key, id }) => + Effect.gen(function* () { + const client = yield* MapleApiV2Client + const result = yield* client.anomalies + .getIncident({ params: { id } }) + .pipe(Effect.mapError(toLoadFailure), Effect.result) + return Message.CompletedFetchAnomaly({ key, result }) + }), +}) + +/** The anomaly's signal around it, with its baseline and threshold. */ +export const FetchIncidentAnomalyTimeseries = Command.define("FetchAlertIncidentAnomalyTimeseries", { + args: { key: Schema.String, id: AnomalyIncidentId }, + messages: [Message.CompletedFetchAnomalyTimeseries], + execute: ({ key, id }) => + Effect.gen(function* () { + const client = yield* MapleApiV2Client + const result = yield* client.anomalies + .getIncidentTimeseries({ params: { id }, query: {} }) + .pipe(Effect.mapError(toLoadFailure), Effect.result) + return Message.CompletedFetchAnomalyTimeseries({ key, result }) + }), +}) diff --git a/apps/web/src/page/alerts/incident/derive.ts b/apps/web/src/page/alerts/incident/derive.ts index 1fca929685..4d1a566dc4 100644 --- a/apps/web/src/page/alerts/incident/derive.ts +++ b/apps/web/src/page/alerts/incident/derive.ts @@ -88,8 +88,9 @@ const deriveIncidentView = (model: ViewKey): Option.Option => { rowToAlertIncidentDocument, ), range, + // An anomaly rule records only severity codes: the detector's own series is the evidence. checksQuery: - rule === null || isManagedSignalType(incident.signalType) + rule === null || isManagedSignalType(incident.signalType) || incident.signalType === "anomaly" ? Option.none() : Option.some({ ruleId: rule.id, diff --git a/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts new file mode 100644 index 0000000000..e7d7f72542 --- /dev/null +++ b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts @@ -0,0 +1,142 @@ +import { OrgId } from "@maple/domain" +import { Result, Schema, Array as Arr } from "effect" +import { expect as expectView, given, Mount, role, scene, text, withViewInputs } from "foldkit/scene" +import { describe, expect, test } from "vitest" +import { ResolveThemeColors, ThemeMessage } from "@maple/views/charts/theme-colors" +import { toLoadFailure } from "@maple/views/data/load-failure" +import type { PageContext } from "../../contract" +import { + anomalyAlertIncidentId, + anomalyId, + anomalyIncidentRow, + anomalyTimeseriesWire, + anomalyWire, + defaultAnomalyRuleRow, +} from "../shared/anomaly-test-fixtures" +import { NOW, ORG } from "../shared/test-rows" +import { Message } from "./message" +import type { Model } from "./model" +import { init, update } from "./update" +import { view as pageView } from "./view" + +const orgId = Schema.decodeSync(OrgId)(ORG) +const context: PageContext = { orgId, pinnedNamespace: null, timeZone: "UTC" } +const pageUpdate = (model: Model, message: Message) => update(model, message, context) +const view = withViewInputs(pageView, { context })() +const names = (commands: ReadonlyArray<{ readonly name: string }> | undefined) => + Arr.map(commands ?? [], ({ name }) => name) +const themeResolved = () => + Mount.resolve(ResolveThemeColors, ThemeMessage.ResolvedThemeColors({ colors: {} })) +const run = (model: Model, messages: ReadonlyArray) => + Arr.reduce(messages, model, (current, message) => pageUpdate(current, message).model) + +const SPIKE = { + n: 3, + ruleN: 7, + severity: "critical", + service: "checkout", + environment: "production", +} as const + +const synced = (scenario: Parameters[0]) => + run( + init({ _tag: "AlertIncident", incidentId: anomalyAlertIncidentId(scenario.n), search: {} }, context) + .model, + [ + Message.ReadClock({ nowMs: NOW }), + Message.UpdatedIncidents({ orgId: ORG, rows: [anomalyIncidentRow(scenario)] }), + Message.UpdatedRules({ orgId: ORG, rows: [defaultAnomalyRuleRow()] }), + ], + ) + +describe("alert incident: anomaly rules", () => { + test("an anomaly incident reads its anomaly and series, never a rule preview or checks", () => { + const entered = init( + { _tag: "AlertIncident", incidentId: anomalyAlertIncidentId(3), search: {} }, + context, + ) + const clocked = pageUpdate(entered.model, Message.ReadClock({ nowMs: NOW })).model + const withIncident = pageUpdate( + clocked, + Message.UpdatedIncidents({ orgId: ORG, rows: [anomalyIncidentRow(SPIKE)] }), + ) + expect(names(withIncident.commands)).toEqual( + expect.arrayContaining(["FetchAlertIncidentAnomaly", "FetchAlertIncidentAnomalyTimeseries"]), + ) + const withRule = pageUpdate( + withIncident.model, + Message.UpdatedRules({ orgId: ORG, rows: [defaultAnomalyRuleRow()] }), + ) + expect(names(withRule.commands)).not.toContain("FetchAlertIncidentPreview") + expect(names(withRule.commands)).not.toContain("FetchAlertIncidentChecks") + }) + + test("the evidence shows the detector's values, grouped errors and the way to the anomaly", () => { + const model = synced(SPIKE) + const loaded = run(model, [ + Message.CompletedFetchAnomaly({ + key: model.anomaly.key ?? "", + result: Result.succeed(anomalyWire(3, "error_spike", "critical", "checkout")), + }), + Message.CompletedFetchAnomalyTimeseries({ + key: model.anomalyTimeseries.key ?? "", + result: Result.succeed(anomalyTimeseriesWire("error_spike")), + }), + ]) + scene( + { update: pageUpdate, view }, + given(loaded), + themeResolved(), + expectView(text("Anomaly on checkout (production)")).toExist(), + expectView(text("Anomaly · checkout (production)")).toExist(), + expectView(text(/^Observed/)).toExist(), + expectView(text("Error frequency increase")).toExist(), + expectView(text("Grouped errors · 2")).toExist(), + expectView(text("triaging…")).toExist(), + expectView(role("link", { name: /Open anomaly/ })).toHaveAttr( + "href", + `/anomalies/${anomalyId(3)}`, + ), + expectView(role("link", { name: /Open issue/ })).toExist(), + // The rule's pinned severity code is never read out as a threshold. + expectView(text(/≥ 1|at or above 1/)).not.toExist(), + ) + }) + + test("a warning anomaly without grouped errors has no fingerprint list or issue link", () => { + const scenario = { ...SPIKE, n: 4, severity: "warning" } as const + const model = synced(scenario) + const loaded = run(model, [ + Message.CompletedFetchAnomaly({ + key: model.anomaly.key ?? "", + result: Result.succeed(anomalyWire(4, "latency_p95", "warning", "checkout")), + }), + ]) + scene( + { update: pageUpdate, view }, + given(loaded), + themeResolved(), + expectView(text("p95 latency")).toExist(), + expectView(text(/^Grouped errors/)).not.toExist(), + expectView(role("link", { name: /Open issue/ })).not.toExist(), + ) + }) + + test("an anomaly that cannot be read still links to its page", () => { + const model = synced(SPIKE) + const failed = pageUpdate( + model, + Message.CompletedFetchAnomaly({ + key: model.anomaly.key ?? "", + result: Result.fail(toLoadFailure(new Error("timed out"))), + }), + ).model + scene( + { update: pageUpdate, view }, + given(failed), + themeResolved(), + expectView(text("Couldn't load the anomaly behind this incident.")).toExist(), + expectView(role("link", { name: /Open anomaly/ })).toExist(), + ) + }) +}) diff --git a/apps/web/src/page/alerts/incident/message.ts b/apps/web/src/page/alerts/incident/message.ts index 5a806c2d53..e11561c1a4 100644 --- a/apps/web/src/page/alerts/incident/message.ts +++ b/apps/web/src/page/alerts/incident/message.ts @@ -2,6 +2,8 @@ import { AlertDeliveryEventDocument, AlertRulePreviewResponse } from "@maple/dom import { V2AlertIncident, V2AlertIncidentEvent, + V2AnomalyIncident, + V2AnomalyIncidentTimeseries, V2Investigation, V2Slo, V2UptimeMonitor, @@ -48,6 +50,12 @@ export const Message = defineMessageUnion({ key: Schema.String, result: Schema.Result(Schema.Array(V2UptimeMonitor), LoadFailure), }, + CompletedFetchAnomaly: { key: Schema.String, result: Schema.Result(V2AnomalyIncident, LoadFailure) }, + CompletedFetchAnomalyTimeseries: { + key: Schema.String, + result: Schema.Result(V2AnomalyIncidentTimeseries, LoadFailure), + }, + GotAnomalyChartMessage: { message: Chart.Message }, ClickedInvestigate: {}, ClickedAcknowledge: { acknowledged: Schema.Boolean }, CompletedSetAcknowledged: { diff --git a/apps/web/src/page/alerts/incident/model.ts b/apps/web/src/page/alerts/incident/model.ts index 49293a4d1f..dae3409243 100644 --- a/apps/web/src/page/alerts/incident/model.ts +++ b/apps/web/src/page/alerts/incident/model.ts @@ -1,5 +1,12 @@ import { AlertDeliveryEventDocument } from "@maple/domain/http" -import { V2AlertIncidentEvent, V2Investigation, V2Slo, V2UptimeMonitor } from "@maple/domain/http/v2" +import { + V2AlertIncidentEvent, + V2AnomalyIncident, + V2AnomalyIncidentTimeseries, + V2Investigation, + V2Slo, + V2UptimeMonitor, +} from "@maple/domain/http/v2" import { AlertIncidentRowSchema, AlertRuleRowSchema, @@ -38,6 +45,10 @@ export const Model = Schema.Struct({ slos: KeyedSchema(Schema.Array(V2Slo), LoadFailure), /** An uptime incident's link to its monitor: `uptimeMonitors.list`, read only for one. */ monitors: KeyedSchema(Schema.Array(V2UptimeMonitor), LoadFailure), + /** An anomaly rule's incident: the detector's anomaly and its signal, read only for one. */ + anomaly: KeyedSchema(V2AnomalyIncident, LoadFailure), + anomalyTimeseries: KeyedSchema(V2AnomalyIncidentTimeseries, LoadFailure), + anomalyChart: Chart.Model, /** `useAsyncAction`: the create request is running, so repeat clicks cannot start duplicates. */ isStartingInvestigation: Schema.Boolean, chart: Chart.Model, diff --git a/apps/web/src/page/alerts/incident/update.ts b/apps/web/src/page/alerts/incident/update.ts index 3bce573d36..3b4f3db2ea 100644 --- a/apps/web/src/page/alerts/incident/update.ts +++ b/apps/web/src/page/alerts/incident/update.ts @@ -18,6 +18,8 @@ import { type PageContext, PageOutMessage, type PageReturn } from "../../contrac import { CreateInvestigation, FetchIncidentActivity, + FetchIncidentAnomaly, + FetchIncidentAnomalyTimeseries, FetchIncidentChecks, FetchIncidentDeliveries, FetchIncidentInvestigation, @@ -54,6 +56,9 @@ export const init = (route: IncidentRoute, _context: PageContext): Return => ({ isUpdatingAcknowledgement: false, slos: Keyed.idle(), monitors: Keyed.idle(), + anomaly: Keyed.idle(), + anomalyTimeseries: Keyed.idle(), + anomalyChart: Chart.init(), isStartingInvestigation: false, chart: Chart.init(), chartRuntime: { _tag: "Loading" }, @@ -179,6 +184,34 @@ const followData = (model: Model, context: PageContext): Return => ), ), ), + ...Option.toArray( + Option.flatMap(Option.fromNullishOr(view.incident.anomalyIncidentId), (id) => { + const key = requestKey(context.orgId, "anomalies.getIncident", { id }) + return Option.map( + sameKey(model.anomaly, key) + ? Option.none() + : Keyed.request(model.anomaly, key, "Load"), + (anomaly) => ({ + patch: { anomaly }, + commands: [FetchIncidentAnomaly({ key, id })], + }), + ) + }), + ), + ...Option.toArray( + Option.flatMap(Option.fromNullishOr(view.incident.anomalyIncidentId), (id) => { + const key = requestKey(context.orgId, "anomalies.getIncidentTimeseries", { id }) + return Option.map( + sameKey(model.anomalyTimeseries, key) + ? Option.none() + : Keyed.request(model.anomalyTimeseries, key, "Load"), + (anomalyTimeseries) => ({ + patch: { anomalyTimeseries }, + commands: [FetchIncidentAnomalyTimeseries({ key, id })], + }), + ) + }), + ), ...Option.toArray( Option.flatMap(view.payload, (payload) => { const key = requestKey(context.orgId, "alertRules.preview", payload) @@ -389,6 +422,21 @@ export const update = (model: Model, message: Message, context: PageContext): Re model: { ...model, chartRuntime: { _tag: "Failed", reason } }, }), ResolvedThemeColors: ({ colors }) => ({ model: { ...model, themeColors: colors } }), + CompletedFetchAnomaly: ({ key, result }) => ({ + model: { ...model, anomaly: Keyed.accept(model.anomaly, key, result) }, + }), + CompletedFetchAnomalyTimeseries: ({ key, result }) => ({ + model: { ...model, anomalyTimeseries: Keyed.accept(model.anomalyTimeseries, key, result) }, + }), + GotAnomalyChartMessage: ({ message: child }) => { + const next = Chart.update(model.anomalyChart, child) + return { + model: { ...model, anomalyChart: next.model }, + commands: Command.mapMessages(next.commands ?? [], (message) => + Message.GotAnomalyChartMessage({ message }), + ), + } + }, GotChartMessage: ({ message: child }) => { const next = Chart.update(model.chart, child) return { diff --git a/apps/web/src/page/alerts/incident/view-anomaly.ts b/apps/web/src/page/alerts/incident/view-anomaly.ts new file mode 100644 index 0000000000..62aed708fd --- /dev/null +++ b/apps/web/src/page/alerts/incident/view-anomaly.ts @@ -0,0 +1,164 @@ +import type { AnomalyIncidentId } from "@maple/domain/http" +import type { V2AnomalyIncident, V2AnomalyIncidentTimeseries } from "@maple/domain/http/v2" +import { EMPTY_VALUE } from "@maple/ui/lib/format" +import { cn } from "@maple/ui/lib/utils" +import { + META_CHIP_CLASS, + SIGNAL_LABEL, + TRIAGE_STATUS_CHIP, + deviation, + formatSignalValue, +} from "@maple/web-data/anomalies/format" +import { Array as Arr } from "effect" +import { AsyncData } from "foldkit" +import type { Html, HtmlBuilder } from "foldkit/html" +import type * as Chart from "@maple/views/charts/chart" +import type { ChartRuntime } from "@maple/views/charts/chart-runtime" +import type { ThemeColors } from "@maple/views/charts/theme-colors" +import type { LoadFailure } from "@maple/views/data/load-failure" +import { linkButton } from "@maple/views/ui/button" +import { badge } from "@maple/views/ui/display" +import { eyebrow } from "@maple/views/ui/eyebrow" +import { icon } from "@maple/views/ui/icon" +import { keyValue, keyValueList } from "@maple/views/ui/key-value" +import { skeleton } from "@maple/views/ui/skeleton" +import { hstack } from "@maple/views/ui/stack" +import { typography } from "@maple/views/ui/typography" +import { anomalyIncidentFromV2 } from "../../../lib/services/anomalies" +import { AppRoute, hrefOf } from "../../../route" +import { anomalyFingerprintRow, anomalySignalChart, anomalySummary } from "../../anomalies/kit/evidence" + +// An anomaly rule's incident: the detector's evidence (its signal against the baseline, the +// values, the grouped errors) and the way to the anomaly, its issue and its triage. + +/** What the evidence section draws from: the page reads it off its Model, the lab off a fixture. */ +export type AnomalyEvidenceInputs = Readonly<{ + anomalyIncidentId: AnomalyIncidentId + anomaly: AsyncData.AsyncData + timeseries: AsyncData.AsyncData + chart: Chart.Model + toChartMessage: (message: Chart.Message) => M + chartRuntime: ChartRuntime + colors: ThemeColors | null + now: number + timeZone: string +}> + +const anomalyHref = (id: AnomalyIncidentId): string => hrefOf(AppRoute.Anomaly({ incidentId: id })) + +const openAnomalyLink = (h: HtmlBuilder, id: AnomalyIncidentId): Html => + linkButton(h, { size: "sm", variant: "outline", href: anomalyHref(id) }, [ + "Open anomaly", + icon(h, "ArrowRightIcon"), + ]) + +const loaded = (h: HtmlBuilder, inputs: AnomalyEvidenceInputs, wire: V2AnomalyIncident): Html => { + const anomaly = anomalyIncidentFromV2(wire) + const fmt = (value: number) => formatSignalValue(anomaly.signalType, value) + const triage = TRIAGE_STATUS_CHIP[anomaly.triageStatus] + return h.div( + [h.Class("space-y-4")], + [ + anomalySummary(h, anomaly, inputs.now), + anomalySignalChart(h, { + chartId: "alert-incident-anomaly", + chart: inputs.chart, + toChartMessage: inputs.toChartMessage, + timeseries: inputs.timeseries, + incident: anomaly, + colors: inputs.colors, + chartRuntime: inputs.chartRuntime, + timeZone: inputs.timeZone, + }), + keyValueList(h, { layout: "split", className: "max-w-md" }, [ + keyValue(h, { label: "Signal" }, [SIGNAL_LABEL[anomaly.signalType]]), + keyValue(h, { label: "Service", title: anomaly.serviceName }, [ + anomaly.serviceName || EMPTY_VALUE, + ]), + keyValue(h, { label: "Environment" }, [anomaly.deploymentEnv || EMPTY_VALUE]), + keyValue(h, { label: "Observed", mono: true }, [fmt(anomaly.lastObservedValue)]), + keyValue(h, { label: "Baseline", mono: true }, [fmt(anomaly.baselineMedian)]), + keyValue(h, { label: "Deviation", mono: true }, [deviation(anomaly).label]), + ]), + ...(anomaly.fingerprints.length > 0 + ? [ + h.div( + [h.Class("space-y-1")], + [ + typography(h, { size: "xs", tone: "muted", as: "p" }, [ + `Grouped errors · ${anomaly.fingerprints.length}`, + ]), + h.div( + [h.Class("max-w-md")], + Arr.map(anomaly.fingerprints, (fingerprint) => + anomalyFingerprintRow(h, anomaly, fingerprint), + ), + ), + ], + ), + ] + : []), + hstack(h, { gap: "2", wrap: true }, [ + openAnomalyLink(h, inputs.anomalyIncidentId), + ...(anomaly.errorIssueId === null + ? [] + : [ + linkButton( + h, + { + size: "sm", + variant: "outline", + href: hrefOf( + AppRoute.ErrorIssue({ issueId: anomaly.errorIssueId, search: {} }), + ), + }, + ["Open issue", icon(h, "ArrowRightIcon")], + ), + ]), + triage === null + ? typography(h, { size: "xs", tone: "muted" }, ["No AI triage yet"]) + : badge( + h, + { + pill: true, + size: "sm", + className: cn(META_CHIP_CLASS, "font-medium", triage.tone), + }, + [triage.label], + ), + ]), + ], + ) +} + +/** The anomaly evidence section of an anomaly rule's incident, under its "Anomaly · group" heading. */ +export const anomalyEvidence = ( + h: HtmlBuilder, + heading: string, + inputs: AnomalyEvidenceInputs, +): Html => + h.section( + [h.Class("space-y-3")], + [ + eyebrow(h, { as: "h2" }, [heading]), + AsyncData.match(inputs.anomaly, { + onIdle: () => skeleton(h, "h-[300px] w-full"), + onLoading: () => skeleton(h, "h-[300px] w-full"), + onFailure: () => failed(h, inputs.anomalyIncidentId), + onStale: () => failed(h, inputs.anomalyIncidentId), + onSuccess: (wire) => loaded(h, inputs, wire), + onRefreshing: (wire) => loaded(h, inputs, wire), + }), + ], + ) + +const failed = (h: HtmlBuilder, id: AnomalyIncidentId): Html => + h.div( + [h.Class("space-y-3")], + [ + typography(h, { size: "sm", tone: "muted", as: "p" }, [ + "Couldn't load the anomaly behind this incident.", + ]), + openAnomalyLink(h, id), + ], + ) diff --git a/apps/web/src/page/alerts/incident/view-rail.ts b/apps/web/src/page/alerts/incident/view-rail.ts index 905c586de9..feb020fe92 100644 --- a/apps/web/src/page/alerts/incident/view-rail.ts +++ b/apps/web/src/page/alerts/incident/view-rail.ts @@ -2,6 +2,7 @@ import { pluralize } from "@maple/ui/lib/format" import { type AlertIncidentDocument, type AlertRuleDocument, isManagedSignalType } from "@maple/domain/http" import { formatWarehouseDateTime } from "@maple/query-engine" import { cn } from "@maple/ui/lib/utils" +import { anomalyScopeSummary } from "@maple/web-data/alerts/anomaly-rule" import { formatSignalValue } from "@maple/web-data/alerts/form-utils" import { conditionPhrase, incidentGroup, signalTitle } from "@maple/web-data/alerts/incident-timeline" import type { AlertRuleStateRow } from "@maple/web-data/collections/alerts" @@ -123,6 +124,7 @@ const incidentServices = ( rule: AlertRuleDocument | null, incident: AlertIncidentDocument, ): ReadonlyArray => { + if (incident.labels?.service != null) return [incident.labels.service] const group = incidentGroup(incident) if (group != null && rule?.groupBy?.length === 1 && rule.groupBy[0] === "service.name") return [group] return rule?.serviceNames ?? [] @@ -225,7 +227,10 @@ const ruleSection = ( (part) => part != null, ) const breaches = rule.consecutiveBreachesRequired - const summary = `${signalTitle(rule.signalType)} ${conditionPhrase(rule)} over ${rule.windowMinutes}m, ${breaches} ${breaches === 1 ? "check" : "checks in a row"} to open${scope.length > 0 ? `, ${Arr.join(scope, ", ")}` : ""}.` + const summary = + rule.signalType === "anomaly" + ? `Pages on anomalies in ${anomalyScopeSummary(rule)}.` + : `${signalTitle(rule.signalType)} ${conditionPhrase(rule)} over ${rule.windowMinutes}m, ${breaches} ${breaches === 1 ? "check" : "checks in a row"} to open${scope.length > 0 ? `, ${Arr.join(scope, ", ")}` : ""}.` // A monitor's or SLO's rule is edited through its owner, so it keeps only the history. const isManaged = isManagedSignalType(rule.signalType) return railSection( diff --git a/apps/web/src/page/alerts/incident/view.ts b/apps/web/src/page/alerts/incident/view.ts index 9b1f8e8354..7ea6773a87 100644 --- a/apps/web/src/page/alerts/incident/view.ts +++ b/apps/web/src/page/alerts/incident/view.ts @@ -53,11 +53,19 @@ import { Message } from "./message" import type { Model } from "./model" import { incidentRail } from "./view-rail" import { incidentTimeline } from "./view-timeline" +import { anomalyEvidence, type AnomalyEvidenceInputs } from "./view-anomaly" +import { ANOMALY_CHART_TOKENS } from "../../anomalies/kit/evidence" import { typography } from "@maple/views/ui/typography" import { hstack } from "@maple/views/ui/stack" type H = HtmlBuilder +/** The rule chart's tokens and the anomaly chart's, which an anomaly rule's incident draws instead. */ +export const INCIDENT_CHART_TOKENS: ReadonlyArray = Arr.dedupe([ + ...RULE_CHART_TOKENS, + ...ANOMALY_CHART_TOKENS, +]) + const statusLine = (incident: AlertIncidentDocument, now: number, timeZone: string): string => { const at = (iso: string) => formatDateInTimezone(iso, { timeZone }) === formatDateInTimezone(now, { timeZone }) @@ -87,31 +95,35 @@ export const headerTitle = ( const isUptime = incident.signalType === "uptime" const isSlo = incident.signalType === "slo_burn_rate" const isQuery = isQueryIncident(incident) - // An uptime or SLO incident names its monitor or SLO and a query incident its rule. + const isAnomaly = incident.signalType === "anomaly" + // An uptime or SLO incident names its monitor or SLO, a query incident its rule, an anomaly its group. const title = - isUptime || isSlo || isQuery + isUptime || isSlo || isQuery || isAnomaly ? incidentTitle(incident) : `${signalTitle(incident.signalType)} ${formatSignalValue(incident.signalType, incident.lastObservedValue)}${group != null ? ` on ${group}` : ""}` const subline = Arr.join( Arr.filter( isUptime ? ["Uptime check", group] - : isSlo - ? [ - "SLO burn rate", - // A resolved incident's last value is the healthy burn it ended on. - incident.status === "resolved" - ? null - : `now ${formatSignalValue(incident.signalType, incident.lastObservedValue)}`, - group, - ] - : [ - isQuery ? group : `Rule ${rule?.name ?? incident.ruleName}`, - `${isQuery ? "value" : Str.toLowerCase(signalLabels[incident.signalType])} ${conditionPhrase(incident)}${rule != null ? ` over ${rule.windowMinutes}m` : ""}`, - rule != null && rule.environments.length > 0 - ? Arr.join(rule.environments, ", ") - : null, - ], + : // An anomaly's severity badge says how bad it is: no threshold to read out. + isAnomaly + ? [`Rule ${rule?.name ?? incident.ruleName}`, "detected by Maple's anomaly detector"] + : isSlo + ? [ + "SLO burn rate", + // A resolved incident's last value is the healthy burn it ended on. + incident.status === "resolved" + ? null + : `now ${formatSignalValue(incident.signalType, incident.lastObservedValue)}`, + group, + ] + : [ + isQuery ? group : `Rule ${rule?.name ?? incident.ruleName}`, + `${isQuery ? "value" : Str.toLowerCase(signalLabels[incident.signalType])} ${conditionPhrase(incident)}${rule != null ? ` over ${rule.windowMinutes}m` : ""}`, + rule != null && rule.environments.length > 0 + ? Arr.join(rule.environments, ", ") + : null, + ], (part) => part != null, ), " · ", @@ -205,6 +217,8 @@ export type IncidentBodyInputs = Readonly<{ chart: Chart.Model toChartMessage: (message: Chart.Message) => M colors: ThemeColors + /** An anomaly rule's incident: the detector's evidence, in place of the rule's chart. */ + anomaly?: AnomalyEvidenceInputs | null }> /** A managed incident's note on what opened it, and a link to its SLO or monitor (its tab until known). */ @@ -229,57 +243,71 @@ const sourceNote = ( export const incidentBody = (h: HtmlBuilder, inputs: IncidentBodyInputs): Html => { const { incident, rule } = inputs const group = incidentGroup(incident) + const heading = `${signalTitle(incident.signalType)}${group != null ? ` · ${group}` : ""}` + const anomaly = inputs.anomaly ?? null return h.div( [h.Class("grid gap-8 lg:grid-cols-[minmax(0,1fr)_18rem]")], [ h.div( [h.Class("min-w-0 space-y-8")], [ - h.section( - [h.Class("space-y-3")], - [ - eyebrow(h, { as: "h2" }, [ - signalTitle(incident.signalType), - ...(group != null ? [` · ${group}`] : []), - ]), - incident.signalType === "uptime" - ? sourceNote(h, { - text: "This incident follows an uptime monitor: it opened when the monitor's checks went down and resolves when one passes again. The monitor's page lists each check with its failing trace.", - href: inputs.sourceHref ?? null, - label: "Open monitor", - fallback: { href: uptimeTabHref(), label: "Open monitors" }, - }) - : incident.signalType === "slo_burn_rate" - ? sourceNote(h, { - text: "This incident follows an SLO burning its error budget: it opened when the burn rate held at a tier's factor over both of its windows, and resolves when the short window cools. The SLO's page charts the budget and each window's burn rate.", - href: inputs.sourceHref ?? null, - label: "Open SLO", - fallback: { href: slosTabHref(), label: "Open SLOs" }, - }) - : rule === null + incident.signalType === "anomaly" && anomaly !== null + ? anomalyEvidence(h, heading, anomaly) + : h.section( + [h.Class("space-y-3")], + [ + eyebrow(h, { as: "h2" }, [ + signalTitle(incident.signalType), + ...(group != null ? [` · ${group}`] : []), + ]), + incident.signalType === "anomaly" ? typography(h, { size: "sm", tone: "muted", as: "p" }, [ - "The rule behind this incident was deleted, so its signal cannot be replayed.", + "The anomaly behind this incident is no longer recorded, so its signal cannot be shown.", ]) - : ruleChart(h, { - chartId: inputs.chartId, - chart: inputs.chart, - toChartMessage: inputs.toChartMessage, - colors: inputs.colors, - timeZone: inputs.timeZone, - preview: inputs.preview, - checks: inputs.checks, - incidents: [incident], - threshold: incident.threshold, - thresholdUpper: incident.thresholdUpper, - comparator: incident.comparator, - signalType: incident.signalType, - window: { min: inputs.range.start, max: inputs.range.end }, - source: "preview", - isLoading: inputs.isPreviewLoading, - error: inputs.previewError, - }), - ], - ), + : incident.signalType === "uptime" + ? sourceNote(h, { + text: "This incident follows an uptime monitor: it opened when the monitor's checks went down and resolves when one passes again. The monitor's page lists each check with its failing trace.", + href: inputs.sourceHref ?? null, + label: "Open monitor", + fallback: { + href: uptimeTabHref(), + label: "Open monitors", + }, + }) + : incident.signalType === "slo_burn_rate" + ? sourceNote(h, { + text: "This incident follows an SLO burning its error budget: it opened when the burn rate held at a tier's factor over both of its windows, and resolves when the short window cools. The SLO's page charts the budget and each window's burn rate.", + href: inputs.sourceHref ?? null, + label: "Open SLO", + fallback: { href: slosTabHref(), label: "Open SLOs" }, + }) + : rule === null + ? typography(h, { size: "sm", tone: "muted", as: "p" }, [ + "The rule behind this incident was deleted, so its signal cannot be replayed.", + ]) + : ruleChart(h, { + chartId: inputs.chartId, + chart: inputs.chart, + toChartMessage: inputs.toChartMessage, + colors: inputs.colors, + timeZone: inputs.timeZone, + preview: inputs.preview, + checks: inputs.checks, + incidents: [incident], + threshold: incident.threshold, + thresholdUpper: incident.thresholdUpper, + comparator: incident.comparator, + signalType: incident.signalType, + window: { + min: inputs.range.start, + max: inputs.range.end, + }, + source: "preview", + isLoading: inputs.isPreviewLoading, + error: inputs.previewError, + }), + ], + ), incidentTimeline(h, { entries: inputs.entries, timeZone: inputs.timeZone, @@ -361,6 +389,20 @@ const body = (h: H, model: Model, view: IncidentView, timeZone: string, now: num chart: model.chart, toChartMessage: (message) => Message.GotChartMessage({ message }), colors: model.themeColors ?? {}, + anomaly: + incident.anomalyIncidentId == null + ? null + : { + anomalyIncidentId: incident.anomalyIncidentId, + anomaly: model.anomaly.data, + timeseries: model.anomalyTimeseries.data, + chart: model.anomalyChart, + toChartMessage: (message) => Message.GotAnomalyChartMessage({ message }), + chartRuntime: model.chartRuntime, + colors: model.themeColors, + now, + timeZone, + }, }) } @@ -412,7 +454,10 @@ export const view = Submodel.defineView((model, ) }, }) - return h.div([h.Class("contents"), h.OnMount(ResolveThemeColors({ tokens: RULE_CHART_TOKENS }))], [page]) + return h.div( + [h.Class("contents"), h.OnMount(ResolveThemeColors({ tokens: INCIDENT_CHART_TOKENS }))], + [page], + ) }) /** `DashboardPage`'s breadcrumbs per state. */ diff --git a/apps/web/src/page/alerts/shared/anomaly-test-fixtures.ts b/apps/web/src/page/alerts/shared/anomaly-test-fixtures.ts new file mode 100644 index 0000000000..ddb22dc35b --- /dev/null +++ b/apps/web/src/page/alerts/shared/anomaly-test-fixtures.ts @@ -0,0 +1,197 @@ +import { AnomalyIncidentId, ErrorIssueId } from "@maple/domain/http" +import { + encodePublicId, + PublicIdPrefixes, + V2AnomalyIncident, + V2AnomalyIncidentTimeseries, +} from "@maple/domain/http/v2" +import type { AlertIncidentRow, AlertRuleRow } from "@maple/web-data/collections/alerts" +import { Array as Arr, Schema } from "effect" +import { DESTINATION_ID, incidentRow, NOW, ruleId, ruleRow } from "./test-rows" + +// Anomaly rules, their alert incidents and the detector's anomaly behind one, for the scene tests +// and `/lab/anomaly-alerts`. + +const MINUTE = 60_000 +const iso = (msBefore: number) => new Date(NOW - msBefore).toISOString() + +export const anomalyId = (n: number) => + Schema.decodeSync(AnomalyIncidentId)(`0199a060-0000-7000-8000-00000000000${n}`) +export const anomalyAlertIncidentId = (n: number) => `0199a003-0000-7000-8000-0000000000a${n}` +const ISSUE_ID = Schema.decodeSync(ErrorIssueId)("0199a070-0000-7000-8000-000000000001") + +const TIERS = (warning: ReadonlyArray, critical: ReadonlyArray) => [ + { severity: "warning", value: 1, upper: null, recoveryValue: null, destinationIds: warning }, + { severity: "critical", value: 2, upper: null, recoveryValue: null, destinationIds: critical }, +] + +/** An anomaly rule's synced row; no scope fields makes it the org's default rule. */ +export const anomalyRuleRow = ( + n: number, + fields: Partial & { + readonly warningIds?: ReadonlyArray + readonly criticalIds?: ReadonlyArray + } = {}, +): AlertRuleRow => { + const { warningIds = [], criticalIds = [], ...rest } = fields + return ruleRow(n, { + name: "Anomaly detection", + severity: "critical", + signal_type: "anomaly", + service_names_json: null, + exclude_service_names_json: null, + environments_json: null, + anomaly_signals_json: null, + comparator: "gte", + threshold: 1, + thresholds_json: TIERS(warningIds, criticalIds), + window_minutes: 5, + evaluation_interval_minutes: 1, + minimum_sample_count: 0, + consecutive_breaches_required: 1, + consecutive_healthy_required: 1, + renotify_interval_minutes: 30, + destination_ids_json: Arr.dedupe([...warningIds, ...criticalIds]), + ...rest, + }) +} + +export const defaultAnomalyRuleRow = (n = 7) => anomalyRuleRow(n) + +/** "checkout (production)" on the payments team's scoped rule, routed to the on-call webhook. */ +export const scopedAnomalyRuleRow = (n = 8) => + anomalyRuleRow(n, { + name: "Checkout anomalies", + service_names_json: ["checkout"], + environments_json: ["production"], + anomaly_signals_json: ["error_rate", "latency_p95"], + warningIds: [DESTINATION_ID], + criticalIds: [DESTINATION_ID], + }) + +export type AnomalyIncidentScenario = Readonly<{ + n: number + ruleN: number + severity: "warning" | "critical" + service: string + environment: string + status?: "open" | "resolved" +}> + +/** The alert incident an anomaly rule opened for an anomaly. */ +export const anomalyIncidentRow = ( + scenario: AnomalyIncidentScenario, + fields: Partial = {}, +) => { + const resolved = scenario.status === "resolved" + return incidentRow({ + id: anomalyAlertIncidentId(scenario.n), + rule_id: ruleId(scenario.ruleN), + rule_name: scenario.ruleN === 7 ? "Anomaly detection" : "Checkout anomalies", + incident_key: `anomaly:${anomalyId(scenario.n)}`, + group_key: anomalyId(scenario.n), + group_label: `${scenario.service} (${scenario.environment})`, + labels_json: { service: scenario.service, environment: scenario.environment }, + anomaly_incident_id: anomalyId(scenario.n), + signal_type: "anomaly", + severity: scenario.severity, + comparator: "gte", + threshold: 1, + last_observed_value: resolved ? 0 : scenario.severity === "critical" ? 2 : 1, + dedupe_key: `anomaly:${anomalyId(scenario.n)}`, + status: resolved ? "resolved" : "open", + resolved_at: resolved ? iso(20 * MINUTE) : null, + first_triggered_at: iso((resolved ? 90 : 42) * MINUTE), + ...fields, + }) +} + +type SignalCase = "error_rate" | "latency_p95" | "error_spike" + +const SIGNAL_VALUES = { + error_rate: { opened: 0.061, baseline: 0.004, threshold: 0.02, observed: 0.083, unit: "ratio" }, + latency_p95: { opened: 1620, baseline: 412, threshold: 640, observed: 1840, unit: "milliseconds" }, + error_spike: { opened: 96, baseline: 6, threshold: 30, observed: 148, unit: "count_per_30m" }, +} as const + +/** The detector's anomaly behind an incident, as `anomalies.getIncident` returns it. */ +export const anomalyWire = ( + n: number, + signal: SignalCase, + severity: "warning" | "critical", + service: string, + environment = "production", +): V2AnomalyIncident => { + const values = SIGNAL_VALUES[signal] + const isSpike = signal === "error_spike" + return Schema.decodeSync(V2AnomalyIncident)({ + id: encodePublicId(PublicIdPrefixes.anomalyIncident, anomalyId(n)), + object: "anomaly_incident", + detector_key: `${signal}:${service}:${environment}`, + signal_type: signal, + service_name: service, + deployment_env: environment, + fingerprint_hash: isSpike ? "fp_7c1e9a40d2b5" : null, + error_issue_id: isSpike ? encodePublicId(PublicIdPrefixes.errorIssue, ISSUE_ID) : null, + status: "open", + severity, + opened_value: values.opened, + baseline_median: values.baseline, + baseline_sigma: values.baseline / 2, + threshold_value: values.threshold, + last_observed_value: values.observed, + last_sample_count: 3100, + first_triggered_at: iso(42 * MINUTE), + last_triggered_at: iso(MINUTE), + resolved_at: null, + resolve_reason: null, + triage_status: isSpike ? "pending" : "completed", + fingerprints: isSpike + ? [ + { + fingerprint_hash: "fp_7c1e9a40d2b5", + error_issue_id: encodePublicId(PublicIdPrefixes.errorIssue, ISSUE_ID), + opened_value: 96, + last_value: 148, + severity: "critical", + attached_at: iso(42 * MINUTE), + resolved_at: null, + }, + { + fingerprint_hash: "fp_02be61f9aa13", + error_issue_id: null, + opened_value: 34, + last_value: 41, + severity: "warning", + attached_at: iso(30 * MINUTE), + resolved_at: null, + }, + ] + : [], + reopen_count: 0, + last_reopened_at: null, + }) +} + +/** Two hours of the signal at five-minute buckets: on its baseline, then the jump. */ +export const anomalyTimeseriesWire = (signal: SignalCase): V2AnomalyIncidentTimeseries => { + const values = SIGNAL_VALUES[signal] + const buckets = Arr.makeBy(24, (index) => { + const isAnomalous = index >= 16 + const wobble = 1 + 0.12 * Math.sin(index * 1.7) + return { + bucket: new Date(NOW - (24 - index) * 5 * MINUTE).toISOString(), + value: isAnomalous ? values.observed * (0.85 + 0.03 * (index - 16)) : values.baseline * wobble, + sample_count: 900, + } + }) + return Schema.decodeSync(V2AnomalyIncidentTimeseries)({ + object: "anomaly_incident.timeseries", + signal_type: signal, + unit: values.unit, + bucket_seconds: 300, + buckets, + baseline_median: values.baseline, + threshold_value: values.threshold, + }) +} diff --git a/apps/web/src/page/alerts/shared/preview.ts b/apps/web/src/page/alerts/shared/preview.ts index 6cff49f916..31726f85ae 100644 --- a/apps/web/src/page/alerts/shared/preview.ts +++ b/apps/web/src/page/alerts/shared/preview.ts @@ -26,6 +26,8 @@ export const PreviewSlot = KeyedSchema(AlertRulePreviewResponse, PreviewFailure) export type PreviewSlot = typeof PreviewSlot.Type const isPreviewQueryReady = (form: RuleFormState): boolean => { + // An anomaly rule has no query: the detector's own chart is its evidence. + if (form.signalType === "anomaly") return false if (!Number.isFinite(Number(form.threshold))) return false if (isRangeComparator(form.comparator) && !Number.isFinite(Number(form.thresholdUpper))) return false if (form.signalType === "raw_query") { diff --git a/apps/web/src/page/anomalies/detail/view-body.ts b/apps/web/src/page/anomalies/detail/view-body.ts index bf9d49e9e5..c4a5f1f029 100644 --- a/apps/web/src/page/anomalies/detail/view-body.ts +++ b/apps/web/src/page/anomalies/detail/view-body.ts @@ -1,42 +1,23 @@ import type { AnomalyIncidentDocument } from "@maple/domain/http" import { formatNumber, pluralize } from "@maple/ui/lib/format" -import { formatRelativeTime } from "@maple/ui/lib/time-format" -import { cn } from "@maple/ui/lib/utils" -import { anomalyTimeseriesFromV2 } from "../../../lib/services/anomalies" import { errorIssueFromV2 } from "../../../lib/services/error-issues" -import { - deviation, - formatSignalValue, - isStaleOpenIncident, - META_CHIP_CLASS, - SIGNAL_LABEL, - severityToneFor, -} from "@maple/web-data/anomalies/format" +import { META_CHIP_CLASS, SIGNAL_LABEL } from "@maple/web-data/anomalies/format" import { TONE_SOFT } from "@maple/ui/lib/tone" -import { Option, Array as Arr } from "effect" -import { AsyncData, Submodel } from "foldkit" +import { AsyncData } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" -import { bucketTooltipBody } from "@maple/views/charts/bucket-tooltip" -import type * as Chart from "@maple/views/charts/chart" -import { isChartRuntimeReady } from "@maple/views/charts/chart-runtime" -import { ChartSpec } from "@maple/views/charts/chart-spec" -import { plotFrame } from "@maple/views/charts/plot-frame" -import { resolveColor } from "@maple/views/charts/theme-colors" import { AppRoute, hrefOf } from "../../../route" import { button, linkButton } from "@maple/views/ui/button" import { badge } from "@maple/views/ui/display" import { card, cardPanel } from "@maple/views/ui/card" -import { emptyMessage } from "@maple/views/ui/empty" import { detailTitle } from "../../kit/detail-header" import { icon } from "@maple/views/ui/icon" import { iconButton } from "@maple/views/ui/icon-button" import { inlineCode } from "@maple/views/ui/inline-code" import { skeleton } from "@maple/views/ui/skeleton" -import { chartError, chartLoading } from "@maple/views/ui/chart-state" import { serviceDot } from "@maple/views/ui/values" -import { seriesLegend } from "../../kit/series-legend" import { WORKFLOW_LABEL } from "../../errors/kit/workflow" import { actorAvatar } from "../../errors/kit/actor" +import { ANOMALY_CHART_TOKENS, anomalySignalChart } from "../kit/evidence" import { shortIssueId } from "../kit/row" import { Message } from "./message" import type { Model } from "./model" @@ -47,16 +28,8 @@ import { hstack } from "@maple/views/ui/stack" type H = HtmlBuilder -export const CHART_TOKENS: ReadonlyArray = [ - "--destructive", - "--severity-error", - "--severity-warn", - "--muted-foreground", - "--border", - "--background", -] - -const SEVERITY_STROKE = { critical: "--severity-error", warning: "--severity-warn" } as const +/** The theme tokens the signal chart reads. */ +export const CHART_TOKENS = ANOMALY_CHART_TOKENS /** The detail header's title side: the signal and service under the "Anomaly" eyebrow. */ export const anomalyTitle = (h: H, incident: AnomalyIncidentDocument): Html => @@ -70,143 +43,18 @@ export const anomalyTitle = (h: H, incident: AnomalyIncidentDocument): Html => titleText: `${SIGNAL_LABEL[incident.signalType]} · ${incident.serviceName}`, }) -/** The observed value against its baseline, above the signal chart. */ -export const anomalySummary = (h: H, incident: AnomalyIncidentDocument, nowMs: number): Html => { - const tone = severityToneFor(incident) - const isStale = isStaleOpenIncident(incident, nowMs) - const dev = deviation(incident) - const accent = cn( - typographyClass({ weight: "medium", mono: true }), - incident.status === "open" && !isStale ? tone.text : "text-foreground", - ) - const fmt = (value: number) => formatSignalValue(incident.signalType, value) - return h.p( - [h.Class("max-w-2xl text-sm text-muted-foreground")], - [ - isStale ? "Last recorded" : "Observed", - " ", - h.span([h.Class(accent)], [fmt(incident.lastObservedValue)]), - " ", - "against a ", - typography(h, { tone: "foreground", mono: true }, [fmt(incident.baselineMedian)]), - " 7-day baseline:", - " ", - h.span([h.Class(accent)], [dev.label]), - dev.kind === "sigma" ? " above median" : dev.kind === "percent" ? " vs baseline" : "", - ", threshold ", - typography(h, { tone: "foreground", mono: true }, [fmt(incident.thresholdValue)]), - ".", - isStale - ? ` Last triggered ${formatRelativeTime(incident.lastTriggeredAt, nowMs)}; this stale detector state does not represent current service health.` - : "", - ], - ) -} - -const chartView = Submodel.defineView>( - (model, inputs, h) => - plotFrame(h, { - chartId: "anomaly-signal", - spec: inputs.spec, - ariaLabel: "Observed signal", - className: "h-64 w-full", - tooltipBody: (portal) => - Option.flatMap(model.tooltip, (tooltip) => - tooltip._tag === "Bucket" - ? Option.some(bucketTooltipBody(h, tooltip, portal)) - : Option.none(), - ), - }), -) - -/** `AnomalySignalChart`: loading until the series, the theme and the chart runtime are all in. */ -export const signalChart = ( - h: H, - model: Model, - incident: AnomalyIncidentDocument, - timeZone: string, -): Html => { - const colors = model.themeColors - const isReady = colors !== null && isChartRuntimeReady(model.chartRuntime) - return AsyncData.match(model.timeseries.data, { - onIdle: () => chartLoading(h, { variant: "line", height: 256 }), - onLoading: () => chartLoading(h, { variant: "line", height: 256 }), - onFailure: () => chartError(h, { height: 256 }, ["Couldn't load signal data."]), - onStale: () => chartError(h, { height: 256 }, ["Couldn't load signal data."]), - onSuccess: (wire) => loaded(wire), - onRefreshing: (wire) => loaded(wire), +/** `AnomalySignalChart` over the page's series, theme and chart runtime. */ +export const signalChart = (h: H, model: Model, incident: AnomalyIncidentDocument, timeZone: string): Html => + anomalySignalChart(h, { + chartId: "anomaly-signal", + chart: model.chart, + toChartMessage: (message) => Message.GotChartMessage({ message }), + timeseries: model.timeseries.data, + incident, + colors: model.themeColors, + chartRuntime: model.chartRuntime, + timeZone, }) - function loaded(wire: Parameters[0]): Html { - if (!isReady || colors === null) return chartLoading(h, { variant: "line", height: 256 }) - const series = anomalyTimeseriesFromV2(wire) - if (series.buckets.length === 0) - return emptyMessage( - h, - { - dashed: true, - className: - "flex h-64 w-full items-center justify-center border-border-subtle py-0 text-xs", - }, - ["No signal data in this window"], - ) - const stroke = resolveColor(colors, SEVERITY_STROKE[incident.severity]) - // Both threshold rules draw in `--destructive`. - const rule = resolveColor(colors, "--destructive") - const fmt = (value: number) => formatSignalValue(series.signalType, value) - return h.div( - [h.Class("space-y-2")], - [ - h.submodel({ - slotId: "anomaly-signal-chart", - model: model.chart, - view: chartView, - viewInputs: { - spec: ChartSpec.AnomalySignal({ - points: Arr.map(series.buckets, (bucket) => ({ - bucket: bucket.bucket, - value: bucket.value, - })), - signalType: series.signalType, - baselineMedian: series.baselineMedian, - thresholdValue: series.thresholdValue, - firstTriggeredAt: incident.firstTriggeredAt, - resolvedAt: incident.resolvedAt, - colors: { - stroke, - muted: rule, - threshold: rule, - border: resolveColor(colors, "--border"), - background: resolveColor(colors, "--background"), - }, - gradientId: "anomaly-observed", - timeZone, - }), - }, - toParentMessage: (message) => Message.GotChartMessage({ message }), - }), - seriesLegend(h, { - className: "justify-start gap-x-4", - swatch: "dashed", - items: [ - { key: "observed", label: "Observed", color: stroke, swatch: "line" }, - { - key: "baseline", - label: "Baseline median", - value: fmt(series.baselineMedian), - color: "var(--muted-foreground)", - }, - { - key: "threshold", - label: "Threshold", - value: fmt(series.thresholdValue), - color: "var(--severity-error)", - }, - ], - }), - ], - ) - } -} const IN_FLIGHT_SOFT = "bg-severity-debug-a2 text-severity-debug" const WORKFLOW_TONE = { diff --git a/apps/web/src/page/anomalies/detail/view-sidebar.ts b/apps/web/src/page/anomalies/detail/view-sidebar.ts index 4d54c32d83..d0993e7948 100644 --- a/apps/web/src/page/anomalies/detail/view-sidebar.ts +++ b/apps/web/src/page/anomalies/detail/view-sidebar.ts @@ -12,15 +12,13 @@ import { TRIAGE_STATUS_CHIP, } from "@maple/web-data/anomalies/format" import type { Html, HtmlBuilder } from "foldkit/html" -import { AppRoute, hrefOf } from "../../../route" import { button } from "@maple/views/ui/button" import { detailRailGroup, detailRailRow } from "@maple/views/ui/detail-rail" import { badge } from "@maple/views/ui/display" import { icon } from "@maple/views/ui/icon" -import { statusDot } from "@maple/views/ui/status-dot" -import { truncatedId } from "@maple/views/ui/truncated-id" import { serviceDot } from "@maple/views/ui/values" import { relativeTime } from "../../kit/relative-time" +import { anomalyFingerprintRow } from "../kit/evidence" import { shortIssueId } from "../kit/row" import { Message } from "./message" import { typography, typographyClass } from "@maple/views/ui/typography" @@ -196,62 +194,7 @@ export const anomalySidebar = (h: H, input: SidebarInput): Html => { h, `Grouped errors · ${incident.fingerprints.length}`, Arr.map(incident.fingerprints, (fingerprint) => - h.div( - [ - h.Key(fingerprint.fingerprintHash), - h.Class( - "grid min-h-7 grid-cols-[1fr_auto] items-center gap-x-2 py-0.5", - ), - h.Title(fingerprint.fingerprintHash), - ], - [ - hstack(h, { gap: "1.5", shrink: true, as: "span" }, [ - statusDot(h, { - tone: - fingerprint.resolvedAt !== null - ? "neutral" - : fingerprint.severity === "critical" - ? "crit" - : "warn", - }), - fingerprint.errorIssueId !== null - ? h.a( - [ - h.Href( - hrefOf( - AppRoute.ErrorIssue({ - issueId: fingerprint.errorIssueId, - search: {}, - }), - ), - ), - h.Class( - "truncate font-mono text-xs text-muted-foreground hover:text-foreground active:text-foreground", - ), - ], - [shortIssueId(fingerprint.errorIssueId)], - ) - : truncatedId(h, { - value: fingerprint.fingerprintHash, - length: 10, - className: typographyClass({ - size: "xs", - tone: "muted", - truncate: true, - }), - }), - ]), - typography( - h, - { size: "xs", tone: "muted", mono: true, numeric: true }, - [ - fingerprint.resolvedAt !== null - ? "resolved" - : fmt(fingerprint.lastValue), - ], - ), - ], - ), + anomalyFingerprintRow(h, incident, fingerprint), ), ), ] diff --git a/apps/web/src/page/anomalies/detail/view.ts b/apps/web/src/page/anomalies/detail/view.ts index 0cff12bc18..2bfde33686 100644 --- a/apps/web/src/page/anomalies/detail/view.ts +++ b/apps/web/src/page/anomalies/detail/view.ts @@ -21,11 +21,13 @@ import type { PageViewInputs } from "../../contract" import { dashboardPage } from "../../kit/dashboard-page" import { relativeTime } from "../../kit/relative-time" import { resourceNotFound } from "../../kit/resource-not-found" +import { alertsHref } from "../../alerts/shared/links" import { crumbOf, pageStateOf } from "./derive" import { Message } from "./message" import type { Model } from "./model" import { detailTitleSkeleton } from "../../kit/detail-header" -import { anomalySummary, anomalyTitle, CHART_TOKENS, linkedIssueCard, signalChart } from "./view-body" +import { anomalySummary } from "../kit/evidence" +import { anomalyTitle, CHART_TOKENS, linkedIssueCard, signalChart } from "./view-body" import { linkIssueDialog } from "./view-link-dialog" import { anomalySidebar } from "./view-sidebar" import { hstack } from "@maple/views/ui/stack" @@ -35,6 +37,9 @@ import { Array as Arr } from "effect" type H = HtmlBuilder +/** The anomalies list is the Alerts Inbox's Anomalies view. */ +const ANOMALIES_HREF = alertsHref({ signal: "anomaly" }) + const headerActions = (h: H, model: Model, incident: AnomalyIncidentDocument, timeZone: string): Html => { const isOpen = incident.status === "open" const isStale = isStaleOpenIncident(incident, model.nowMs) @@ -169,7 +174,7 @@ export const view = Submodel.defineView((model, resourceNotFound(h, { title: "Anomaly not found", description: "It may have been pruned, or the link is stale.", - back: { href: "/anomalies", label: "Back to anomalies" }, + back: { href: ANOMALIES_HREF, label: "Back to anomalies" }, }), ]) : state._tag === "Failed" @@ -196,7 +201,8 @@ export const view = Submodel.defineView((model, export const breadcrumbsOf = (model: Model): ReadonlyArray => { const state = pageStateOf(model) return [ - { label: "Anomalies", href: "/anomalies" }, + { label: "Alerts", href: alertsHref({}) }, + { label: "Anomalies", href: ANOMALIES_HREF }, { label: state._tag === "Loaded" diff --git a/apps/web/src/page/anomalies/kit/evidence.ts b/apps/web/src/page/anomalies/kit/evidence.ts new file mode 100644 index 0000000000..e5ac773d00 --- /dev/null +++ b/apps/web/src/page/anomalies/kit/evidence.ts @@ -0,0 +1,250 @@ +import type { AnomalyIncidentDocument } from "@maple/domain/http" +import type { V2AnomalyIncidentTimeseries } from "@maple/domain/http/v2" +import { formatRelativeTime } from "@maple/ui/lib/time-format" +import { cn } from "@maple/ui/lib/utils" +import { + deviation, + formatSignalValue, + isStaleOpenIncident, + severityToneFor, +} from "@maple/web-data/anomalies/format" +import { Option, Array as Arr } from "effect" +import { AsyncData, Submodel } from "foldkit" +import type { Html, HtmlBuilder } from "foldkit/html" +import { bucketTooltipBody } from "@maple/views/charts/bucket-tooltip" +import type * as Chart from "@maple/views/charts/chart" +import { type ChartRuntime, isChartRuntimeReady } from "@maple/views/charts/chart-runtime" +import { ChartSpec } from "@maple/views/charts/chart-spec" +import { plotFrame } from "@maple/views/charts/plot-frame" +import { resolveColor, type ThemeColors } from "@maple/views/charts/theme-colors" +import type { LoadFailure } from "@maple/views/data/load-failure" +import { chartError, chartLoading } from "@maple/views/ui/chart-state" +import { emptyMessage } from "@maple/views/ui/empty" +import { statusDot } from "@maple/views/ui/status-dot" +import { truncatedId } from "@maple/views/ui/truncated-id" +import { typography, typographyClass } from "@maple/views/ui/typography" +import { hstack } from "@maple/views/ui/stack" +import { anomalyTimeseriesFromV2 } from "../../../lib/services/anomalies" +import { AppRoute, hrefOf } from "../../../route" +import { seriesLegend } from "../../kit/series-legend" +import { shortIssueId } from "./row" + +// The anomaly detector's evidence: the observed value against its baseline, the signal chart with +// its baseline and threshold, and the error fingerprints grouped into it. The anomaly page and the +// alert incident page of an anomaly rule both draw these. + +/** The theme tokens the signal chart resolves; a page that draws it resolves these on mount. */ +export const ANOMALY_CHART_TOKENS: ReadonlyArray = [ + "--destructive", + "--severity-error", + "--severity-warn", + "--muted-foreground", + "--border", + "--background", +] + +const SEVERITY_STROKE = { critical: "--severity-error", warning: "--severity-warn" } as const + +/** One sentence: the observed value against its 7-day baseline, the deviation and the threshold. */ +export const anomalySummary = ( + h: HtmlBuilder, + incident: AnomalyIncidentDocument, + nowMs: number, +): Html => { + const tone = severityToneFor(incident) + const isStale = isStaleOpenIncident(incident, nowMs) + const dev = deviation(incident) + const accent = cn( + typographyClass({ weight: "medium", mono: true }), + incident.status === "open" && !isStale ? tone.text : "text-foreground", + ) + const fmt = (value: number) => formatSignalValue(incident.signalType, value) + return h.p( + [h.Class("max-w-2xl text-sm text-muted-foreground")], + [ + isStale ? "Last recorded" : "Observed", + " ", + h.span([h.Class(accent)], [fmt(incident.lastObservedValue)]), + " ", + "against a ", + typography(h, { tone: "foreground", mono: true }, [fmt(incident.baselineMedian)]), + " 7-day baseline:", + " ", + h.span([h.Class(accent)], [dev.label]), + dev.kind === "sigma" ? " above median" : dev.kind === "percent" ? " vs baseline" : "", + ", threshold ", + typography(h, { tone: "foreground", mono: true }, [fmt(incident.thresholdValue)]), + ".", + isStale + ? ` Last triggered ${formatRelativeTime(incident.lastTriggeredAt, nowMs)}; this stale detector state does not represent current service health.` + : "", + ], + ) +} + +type ChartViewInputs = Readonly<{ spec: ChartSpec; chartId: string }> + +const chartView = Submodel.defineView((model, inputs, h) => + plotFrame(h, { + chartId: inputs.chartId, + spec: inputs.spec, + ariaLabel: "Observed signal", + className: "h-64 w-full", + tooltipBody: (portal) => + Option.flatMap(model.tooltip, (tooltip) => + tooltip._tag === "Bucket" + ? Option.some(bucketTooltipBody(h, tooltip, portal)) + : Option.none(), + ), + }), +) + +export type AnomalySignalChartInputs = Readonly<{ + /** Unique per page: the chart's submodel slot and plot id derive from it. */ + chartId: string + chart: Chart.Model + toChartMessage: (message: Chart.Message) => M + timeseries: AsyncData.AsyncData + incident: AnomalyIncidentDocument + colors: ThemeColors | null + chartRuntime: ChartRuntime + timeZone: string +}> + +/** The observed signal with its baseline and threshold; loading until the series, theme and runtime are in. */ +export const anomalySignalChart = (h: HtmlBuilder, inputs: AnomalySignalChartInputs): Html => { + const { incident, colors } = inputs + const isReady = colors !== null && isChartRuntimeReady(inputs.chartRuntime) + const loaded = (wire: V2AnomalyIncidentTimeseries): Html => { + if (!isReady || colors === null) return chartLoading(h, { variant: "line", height: 256 }) + const series = anomalyTimeseriesFromV2(wire) + if (series.buckets.length === 0) + return emptyMessage( + h, + { + dashed: true, + className: + "flex h-64 w-full items-center justify-center border-border-subtle py-0 text-xs", + }, + ["No signal data in this window"], + ) + const stroke = resolveColor(colors, SEVERITY_STROKE[incident.severity]) + // Both threshold rules draw in `--destructive`. + const rule = resolveColor(colors, "--destructive") + const fmt = (value: number) => formatSignalValue(series.signalType, value) + return h.div( + [h.Class("space-y-2")], + [ + h.submodel({ + slotId: `${inputs.chartId}-chart`, + model: inputs.chart, + view: chartView, + viewInputs: { + chartId: inputs.chartId, + spec: ChartSpec.AnomalySignal({ + points: Arr.map(series.buckets, (bucket) => ({ + bucket: bucket.bucket, + value: bucket.value, + })), + signalType: series.signalType, + baselineMedian: series.baselineMedian, + thresholdValue: series.thresholdValue, + firstTriggeredAt: incident.firstTriggeredAt, + resolvedAt: incident.resolvedAt, + colors: { + stroke, + muted: rule, + threshold: rule, + border: resolveColor(colors, "--border"), + background: resolveColor(colors, "--background"), + }, + gradientId: `${inputs.chartId}-observed`, + timeZone: inputs.timeZone, + }), + }, + toParentMessage: inputs.toChartMessage, + }), + seriesLegend(h, { + className: "justify-start gap-x-4", + swatch: "dashed", + items: [ + { key: "observed", label: "Observed", color: stroke, swatch: "line" }, + { + key: "baseline", + label: "Baseline median", + value: fmt(series.baselineMedian), + color: "var(--muted-foreground)", + }, + { + key: "threshold", + label: "Threshold", + value: fmt(series.thresholdValue), + color: "var(--severity-error)", + }, + ], + }), + ], + ) + } + return AsyncData.match(inputs.timeseries, { + onIdle: () => chartLoading(h, { variant: "line", height: 256 }), + onLoading: () => chartLoading(h, { variant: "line", height: 256 }), + onFailure: () => chartError(h, { height: 256 }, ["Couldn't load signal data."]), + onStale: () => chartError(h, { height: 256 }, ["Couldn't load signal data."]), + onSuccess: loaded, + onRefreshing: loaded, + }) +} + +/** One grouped error fingerprint: its issue (or hash) and its last value, or "resolved". */ +export const anomalyFingerprintRow = ( + h: HtmlBuilder, + incident: AnomalyIncidentDocument, + fingerprint: AnomalyIncidentDocument["fingerprints"][number], +): Html => + h.div( + [ + h.Key(fingerprint.fingerprintHash), + h.Class("grid min-h-7 grid-cols-[1fr_auto] items-center gap-x-2 py-0.5"), + h.Title(fingerprint.fingerprintHash), + ], + [ + hstack(h, { gap: "1.5", shrink: true, as: "span" }, [ + statusDot(h, { + tone: + fingerprint.resolvedAt !== null + ? "neutral" + : fingerprint.severity === "critical" + ? "crit" + : "warn", + }), + fingerprint.errorIssueId !== null + ? h.a( + [ + h.Href( + hrefOf( + AppRoute.ErrorIssue({ + issueId: fingerprint.errorIssueId, + search: {}, + }), + ), + ), + h.Class( + "truncate font-mono text-xs text-muted-foreground hover:text-foreground active:text-foreground", + ), + ], + [shortIssueId(fingerprint.errorIssueId)], + ) + : truncatedId(h, { + value: fingerprint.fingerprintHash, + length: 10, + className: typographyClass({ size: "xs", tone: "muted", truncate: true }), + }), + ]), + typography(h, { size: "xs", tone: "muted", mono: true, numeric: true }, [ + fingerprint.resolvedAt !== null + ? "resolved" + : formatSignalValue(incident.signalType, fingerprint.lastValue), + ]), + ], + ) diff --git a/apps/web/src/page/anomalies/list/anomalies.scene.test.ts b/apps/web/src/page/anomalies/list/anomalies.scene.test.ts deleted file mode 100644 index a1b9dc2005..0000000000 --- a/apps/web/src/page/anomalies/list/anomalies.scene.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { OrgId } from "@maple/domain" -import { encodePublicId, PublicIdPrefixes, V2AnomalyIncident } from "@maple/domain/http/v2" -import { Result, Schema, Array as Arr, String as Str } from "effect" -import { - click, - expect as expectView, - expectOutMessage, - given, - label, - Mount, - role, - scene, - text, - withViewInputs, -} from "foldkit/scene" -import { MeasureTabIndicator, Message as TabsMessage } from "@maple/views/ui/tabs" -import { describe, expect, test } from "vitest" -import { toLoadFailure } from "@maple/views/data/load-failure" -import { type PageContext, PageOutMessage } from "../../contract" -import { formatRelativeTime } from "@maple/ui/lib/time-format" -import { allIncidentsOf } from "./derive" -import { Message } from "./message" -import type { Model } from "./model" -import { init, routeChanged, update } from "./update" -import { view as pageView } from "./view" - -const context: PageContext = { - orgId: Schema.decodeSync(OrgId)("org_parity"), - pinnedNamespace: null, - timeZone: "UTC", -} -const pageUpdate = (model: Model, message: Message) => update(model, message, context) -const view = withViewInputs(pageView, { context })() -const names = (commands: ReadonlyArray<{ readonly name: string }> | undefined) => - Arr.map(commands ?? [], ({ name }) => name) -const NOW = Date.UTC(2026, 9, 1, 12) -const measured = () => - Mount.resolve( - MeasureTabIndicator, - TabsMessage.MeasuredIndicator({ - rect: { left: 2, right: 115, top: 2, bottom: 2, width: 51, height: 28 }, - }), - ) -const withPresence = (model: Model, status: "present" | "absent"): Model => - pageUpdate( - model, - Message.CompletedLoadSignalPresence({ - result: Result.succeed({ traces: { status, lastSeenMs: null } }), - }), - ).model -const iso = (agoMs: number) => new Date(NOW - agoMs).toISOString() - -const incident = (index: number, severity: "critical" | "warning", resolved = false) => - Schema.decodeSync(V2AnomalyIncident)({ - id: encodePublicId(PublicIdPrefixes.anomalyIncident, `0199a060-0000-7000-8000-00000000000${index}`), - object: "anomaly_incident", - detector_key: `latency_p95:payment-svc:production`, - signal_type: "latency_p95", - service_name: "payment-svc", - deployment_env: "production", - fingerprint_hash: null, - error_issue_id: null, - status: resolved ? "resolved" : "open", - severity, - opened_value: 1000, - baseline_median: 400, - baseline_sigma: 100, - threshold_value: 800, - last_observed_value: 1800, - last_sample_count: 100, - first_triggered_at: iso(60 * 60_000), - last_triggered_at: iso(60_000), - resolved_at: resolved ? iso(30_000) : null, - resolve_reason: resolved ? "returned_to_baseline" : null, - triage_status: "none", - fingerprints: [], - reopen_count: 0, - last_reopened_at: null, - }) - -const loaded = (rows: ReadonlyArray, search = {}) => { - const entered = init({ search }, context).model - return Arr.reduce( - [ - Message.ReadClock({ nowMs: NOW }), - Message.CompletedFetchFirstPage({ - key: entered.firstPage.key ?? "", - result: Result.succeed({ data: rows, nextCursor: null }), - }), - ], - entered, - (model, message) => pageUpdate(model, message).model, - ) -} - -describe("anomalies list", () => { - test("entry reads the clock, the open incidents and the remembered filter sections", () => { - expect(names(init({ search: {} }, context).commands)).toEqual( - expect.arrayContaining([ - "LoadAnomaliesSignalPresence", - "ReadAnomaliesClock", - "FetchAnomalyIncidents", - "ReadRememberedFilterSections", - ]), - ) - }) - - test("a stale answer is dropped; a failure renders the error state", () => { - const entered = init({ search: {} }, context).model - expect( - pageUpdate( - entered, - Message.CompletedFetchFirstPage({ - key: "other", - result: Result.succeed({ data: [], nextCursor: null }), - }), - ).model.firstPage, - ).toEqual(entered.firstPage) - const failed = pageUpdate( - entered, - Message.CompletedFetchFirstPage({ - key: entered.firstPage.key ?? "", - result: Result.fail(toLoadFailure(new Error("down"))), - }), - ).model - scene( - { update: pageUpdate, view }, - given(failed), - measured(), - expectView(text("Couldn't load anomalies")).toExist(), - ) - }) - - test("j and k walk the rows in group order, Enter opens the focused one, Escape clears it", () => { - const model = loaded([incident(2, "warning"), incident(1, "critical")]) - const first = pageUpdate(model, Message.PressedListKey({ key: "Next" })) - expect(first.model.focusedId).toContain("00000000001") - expect(names(first.commands)).toEqual(["ScrollAnomalyIncidentIntoView"]) - const second = pageUpdate(first.model, Message.PressedListKey({ key: "Next" })).model - expect(second.focusedId).not.toBe(first.model.focusedId) - expect(pageUpdate(second, Message.PressedListKey({ key: "Previous" })).model.focusedId).toBe( - first.model.focusedId, - ) - const opened = pageUpdate(second, Message.PressedListKey({ key: "Open" })) - expect(opened.outMessage?._tag).toBe("RequestedNavigation") - expect(pageUpdate(second, Message.PressedListKey({ key: "Escape" })).model.focusedId).toBeNull() - }) - - test("rows group by severity and a group collapses", () => { - scene( - { update: pageUpdate, view }, - given(loaded([incident(1, "critical"), incident(2, "warning")])), - measured(), - expectView(text("2 anomalies")).toExist(), - click(role("button", { name: /^Critical/ })), - expectView(role("button", { name: /^Critical/ })).toHaveAttr("aria-expanded", "false"), - ) - }) - - test("the Live chip pauses through the URL; a tab switch keeps the rows while it loads", () => { - const model = loaded([incident(1, "critical")]) - scene( - { update: pageUpdate, view }, - given(model), - measured(), - click(role("button", { name: "Live" })), - expectOutMessage( - PageOutMessage.RequestedNavigation({ url: "/anomalies?live=false", isReplace: false }), - ), - ) - const switched = routeChanged(model, { search: { status: "all" } }, context) - expect(names(switched.commands)).toEqual(["FetchAnomalyIncidents"]) - expect(switched.model.firstPage.data._tag).toBe("Refreshing") - }) - - test("an empty list shows a skeleton until presence is known, then setup or the quiet copy", () => { - scene( - { update: pageUpdate, view }, - given(loaded([])), - measured(), - expectView(text("No open anomalies")).not.toExist(), - expectView(label("Loading")).toExist(), - ) - scene( - { update: pageUpdate, view }, - given(withPresence(loaded([]), "absent")), - measured(), - expectView(text("No anomalies yet")).toExist(), - expectView(role("link", { name: "Set up tracing" })).toExist(), - ) - scene( - { update: pageUpdate, view }, - given(withPresence(loaded([]), "present")), - measured(), - expectView(role("link", { name: "Create an alert rule" })).toExist(), - ) - }) - - test("a live tick reads the clock, so a quiet open row turns stale", () => { - const model = loaded([incident(1, "critical")]) - const ticked = pageUpdate(model, Message.TickedLiveRefresh({ nowMs: NOW + 3 * 3_600_000 })).model - scene( - { update: pageUpdate, view }, - given(ticked), - measured(), - expectView(text(`last seen ${formatRelativeTime(NOW - 60_000, NOW + 3 * 3_600_000)}`)).toExist(), - ) - }) - - test("during a tab switch Load more waits for the new tab's first page and drops the old tab's cursor", () => { - const entered = init({ search: {} }, context).model - const open = pageUpdate( - entered, - Message.CompletedFetchFirstPage({ - key: entered.firstPage.key ?? "", - result: Result.succeed({ data: [incident(1, "critical")], nextCursor: "c1" }), - }), - ).model - const switched = routeChanged(open, { search: { status: "all" } }, context).model - expect(pageUpdate(switched, Message.ClickedLoadMore()).commands ?? []).toHaveLength(0) - const mixed = pageUpdate( - switched, - Message.CompletedLoadMore({ - status: "all", - cursor: "c1", - result: Result.succeed({ data: [incident(2, "critical")], nextCursor: null }), - }), - ).model - expect(Arr.map(allIncidentsOf(mixed), (row) => Str.slice(-1)(row.id))).toEqual(["1"]) - const landed = pageUpdate( - mixed, - Message.CompletedFetchFirstPage({ - key: switched.firstPage.key ?? "", - result: Result.succeed({ data: [incident(3, "critical")], nextCursor: "a1" }), - }), - ).model - expect(names(pageUpdate(landed, Message.ClickedLoadMore()).commands)).toEqual([ - "LoadMoreAnomalyIncidents", - ]) - }) - - test("a load-more answer for a cursor a live tick discarded is dropped", () => { - const entered = init({ search: {} }, context).model - const first = (rows: ReadonlyArray) => - Message.CompletedFetchFirstPage({ - key: entered.firstPage.key ?? "", - result: Result.succeed({ data: rows, nextCursor: "c1" }), - }) - const model = Arr.reduce( - [ - first([incident(1, "critical")]), - Message.CompletedLoadMore({ - status: "open", - cursor: "c1", - result: Result.succeed({ data: [incident(2, "critical")], nextCursor: "c2" }), - }), - Message.TickedLiveRefresh({ nowMs: NOW }), - first([incident(1, "critical")]), - Message.CompletedLoadMore({ - status: "open", - cursor: "c2", - result: Result.succeed({ data: [incident(3, "critical")], nextCursor: null }), - }), - ], - entered, - (next, message) => pageUpdate(next, message).model, - ) - expect(Arr.map(allIncidentsOf(model), (row) => Str.slice(-1)(row.id))).toEqual(["1"]) - expect(model.isLoadingMore).toBe(false) - }) -}) diff --git a/apps/web/src/page/anomalies/list/command.ts b/apps/web/src/page/anomalies/list/command.ts deleted file mode 100644 index 12fbabe0f5..0000000000 --- a/apps/web/src/page/anomalies/list/command.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Clock, Effect, Schema, Option, Array as Arr } from "effect" -import { Command } from "foldkit" -import { toLoadFailure } from "@maple/views/data/load-failure" -import { MapleApiV2Client } from "../../../platform/api-clients" -import { loadSignalPresences } from "../../kit/signal-presence" -import { Message } from "./message" -import { StatusTab } from "./model" - -// v2 lists cap at 100; further pages arrive through the explicit "Load more". -export const INCIDENTS_PAGE_LIMIT = 100 - -/** `listQuery`: every status but "all" filters server-side. */ -export const listQueryOf = (status: StatusTab) => - status === "all" ? { limit: INCIDENTS_PAGE_LIMIT } : { status, limit: INCIDENTS_PAGE_LIMIT } - -const fetchPage = (status: StatusTab, cursor: string | undefined) => - Effect.gen(function* () { - const client = yield* MapleApiV2Client - const page = yield* client.anomalies.listIncidents({ - query: { ...listQueryOf(status), ...(cursor === undefined ? undefined : { cursor }) }, - }) - return { data: page.data, nextCursor: page.next_cursor } - }).pipe(Effect.mapError(toLoadFailure), Effect.result) - -/** `scrollIntoView(id)`: the focused row, nearest edge, smoothly. Ids are read off the rows. */ -export const ScrollIncidentIntoView = Command.define("ScrollAnomalyIncidentIntoView", { - args: { incidentId: Schema.String }, - messages: [Message.CompletedScrollRow], - execute: ({ incidentId }) => - Effect.sync(() => { - Option.map( - Arr.findFirst( - Array.from(document.querySelectorAll("[data-incident-id]")), - (row) => row.getAttribute("data-incident-id") === incidentId, - ), - (row) => row.scrollIntoView({ block: "nearest", behavior: "smooth" }), - ) - return Message.CompletedScrollRow() - }), -}) - -export const ReadAnomaliesClock = Command.define("ReadAnomaliesClock", { - messages: [Message.ReadClock], - execute: Effect.map(Clock.currentTimeMillis, (nowMs) => Message.ReadClock({ nowMs })), -}) - -export const FetchFirstPage = Command.define("FetchAnomalyIncidents", { - args: { key: Schema.String, status: StatusTab }, - messages: [Message.CompletedFetchFirstPage], - execute: ({ key, status }) => - Effect.map(fetchPage(status, undefined), (result) => - Message.CompletedFetchFirstPage({ key, result }), - ), -}) - -export const LoadMoreIncidents = Command.define("LoadMoreAnomalyIncidents", { - args: { status: StatusTab, cursor: Schema.String }, - messages: [Message.CompletedLoadMore], - execute: ({ status, cursor }) => - Effect.map(fetchPage(status, cursor), (result) => - Message.CompletedLoadMore({ status, cursor, result }), - ), -}) - -export const LoadSignalPresence = Command.define("LoadAnomaliesSignalPresence", { - args: { orgId: Schema.String }, - messages: [Message.CompletedLoadSignalPresence], - execute: ({ orgId }) => - Effect.map(loadSignalPresences(orgId), (result) => Message.CompletedLoadSignalPresence({ result })), -}) diff --git a/apps/web/src/page/anomalies/list/derive.ts b/apps/web/src/page/anomalies/list/derive.ts deleted file mode 100644 index 62aa278b94..0000000000 --- a/apps/web/src/page/anomalies/list/derive.ts +++ /dev/null @@ -1,160 +0,0 @@ -import type { AnomalyIncidentDocument, AnomalySignalType } from "@maple/domain/http" -import { - anomalyFilterChips, - hasAnomalyFilters, - matchesAnomalyFilters, - type AnomalyFilters, -} from "../../../lib/anomalies/anomaly-filters" -import { anomalyIncidentFromV2 } from "../../../lib/services/anomalies" -import { SIGNAL_LABEL } from "@maple/web-data/anomalies/format" -import { AsyncData } from "foldkit" -import type { AnomaliesSearch } from "../../../route/search" -import type { GroupKey, Model, StatusTab } from "./model" -import { Array as Arr, String as Str, Option, Order } from "effect" - -export type Search = typeof AnomaliesSearch.Type - -export const statusOf = (search: Search): StatusTab => search.status ?? "open" -export const isLiveOf = (search: Search): boolean => search.live ?? statusOf(search) === "open" - -export const filtersOf = (search: Search): AnomalyFilters => ({ - severity: search.severity, - signals: search.signals, - services: search.services, - envs: search.envs, - excludedSeverity: search.excludedSeverity, - excludedSignals: search.excludedSignals, - excludedServices: search.excludedServices, - excludedEnvs: search.excludedEnvs, -}) - -export { anomalyFilterChips, hasAnomalyFilters } - -/** The first page plus "Load more" pages for this tab, deduped by id (first page wins). */ -export const allIncidentsOf = (model: Model): ReadonlyArray => { - const first = AsyncData.getOrElse(model.firstPage.data, () => null) - const firstRows = first === null ? [] : Arr.map(first.data, anomalyIncidentFromV2) - const pages = model.loadedPages?.key === statusOf(model.search) ? model.loadedPages : null - if (pages === null) return firstRows - const byId = new Map(Arr.map(firstRows, (incident) => [incident.id, incident])) - Arr.forEach(Arr.map(pages.rows, anomalyIncidentFromV2), (incident) => { - if (!byId.has(incident.id)) byId.set(incident.id, incident) - }) - return [...byId.values()] -} - -/** - * The cursor "Load more" continues from: none while a tab switch keeps the previous tab's first - * page on screen (`retained`), so a load-more never pairs the new tab with the old tab's cursor. - */ -export const nextCursorOf = (model: Model): string | null => { - if (model.firstPage.retained === true) return null - const pages = model.loadedPages?.key === statusOf(model.search) ? model.loadedPages : null - if (pages !== null) return pages.nextCursor - return AsyncData.getOrElse(model.firstPage.data, () => null)?.nextCursor ?? null -} - -export const filteredOf = (model: Model, all: ReadonlyArray) => { - const filters = filtersOf(model.search) - return Arr.filter(all, (incident) => matchesAnomalyFilters(incident, filters)) -} - -export const GROUP_ORDER: ReadonlyArray = ["critical", "warning", "resolved"] -export const GROUP_LABEL = { critical: "Critical", warning: "Warning", resolved: "Resolved" } as const - -export const groupKeyOf = (incident: AnomalyIncidentDocument): GroupKey => - incident.status !== "open" ? "resolved" : incident.severity - -const clusterKey = (incident: AnomalyIncidentDocument) => - `${incident.serviceName}\u0000${incident.deploymentEnv}` - -/** Buckets by group; each bucket clusters by service+env, clusters by freshest incident. */ -export const groupIncidents = ( - incidents: ReadonlyArray, -): ReadonlyArray]> => - Arr.flatMap(GROUP_ORDER, (group) => { - const bucket = Arr.filter(incidents, (incident) => groupKeyOf(incident) === group) - if (bucket.length === 0) return [] - const latest = new Map() - Arr.forEach(bucket, (incident) => { - const current = latest.get(clusterKey(incident)) - if (current === undefined || Str.localeCompare(current)(incident.lastTriggeredAt) > 0) - latest.set(clusterKey(incident), incident.lastTriggeredAt) - }) - const sorted = Arr.sort(bucket, (a: AnomalyIncidentDocument, b: AnomalyIncidentDocument) => { - const clusterA = latest.get(clusterKey(a)) ?? "" - const clusterB = latest.get(clusterKey(b)) ?? "" - if (clusterA !== clusterB) return Str.localeCompare(clusterA)(clusterB) - const keyCompare = Str.localeCompare(clusterKey(b))(clusterKey(a)) - if (keyCompare !== 0) return keyCompare - return Str.localeCompare(a.lastTriggeredAt)(b.lastTriggeredAt) - }) - return [[group, sorted] as const] - }) - -export interface ServiceCluster { - readonly key: string - readonly serviceName: string - readonly deploymentEnv: string - readonly incidents: ReadonlyArray -} - -/** `clusterByService`: consecutive same-service runs. */ -export const clusterByService = ( - incidents: ReadonlyArray, -): ReadonlyArray => - Arr.reduce>( - incidents, - [], - (clusters, incident) => { - const key = clusterKey(incident) - const last = clusters[clusters.length - 1] - return last !== undefined && last.key === key - ? [...clusters.slice(0, -1), { ...last, incidents: [...last.incidents, incident] }] - : [ - ...clusters, - { - key, - serviceName: incident.serviceName, - deploymentEnv: incident.deploymentEnv, - incidents: [incident], - }, - ] - }, - ) - -const SEVERITY_LABEL = { critical: "Critical", warning: "Warning" } as const - -const byCount = (entries: ReadonlyArray): ReadonlyArray => { - const counts = new Map() - Arr.forEach(entries, (entry) => counts.set(entry, (counts.get(entry) ?? 0) + 1)) - return Arr.sort( - [...counts.entries()], - Order.flip(Order.mapInput(Order.Number, (entry: readonly [K, number]) => entry[1])), - ) -} - -/** The sidebar facets, derived from the unfiltered incidents of the tab. */ -export const facetsOf = (incidents: ReadonlyArray) => { - const severity = byCount(Arr.map(incidents, (incident) => incident.severity)) - const signals = byCount(Arr.map(incidents, (incident) => incident.signalType)) - return { - severity, - signals, - services: byCount(Arr.map(incidents, (incident) => incident.serviceName)), - envs: byCount( - Arr.map( - Arr.filter(incidents, (incident) => incident.deploymentEnv !== ""), - (incident) => incident.deploymentEnv, - ), - ), - severityLabel: (name: string) => - name === "critical" || name === "warning" ? SEVERITY_LABEL[name] : name, - signalLabel: (name: string) => { - return Option.match( - Arr.findFirst(signals, ([signal]) => signal === name), - { onNone: () => name, onSome: (entry) => SIGNAL_LABEL[entry[0] satisfies AnomalySignalType] }, - ) - }, - } -} diff --git a/apps/web/src/page/anomalies/list/index.ts b/apps/web/src/page/anomalies/list/index.ts deleted file mode 100644 index a6d5a4b624..0000000000 --- a/apps/web/src/page/anomalies/list/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { isDialogOpen, isEditableTarget } from "@maple/ui/lib/keyboard" -import { Clock, Duration, Effect, Option, Schema, Stream } from "effect" -import { AsyncData, Dom, Subscription } from "foldkit" -import { definePage, type PageSubscriptionInput } from "../../contract" -import { isLiveOf } from "./derive" -import { Message } from "./message" -import { Model } from "./model" -import { init, routeChanged, update } from "./update" -import { view } from "./view" -import { pollTicks } from "../../../data/poll" - -const LIVE_REFRESH_INTERVAL = Duration.seconds(15) - -const LIST_KEYS = new Map([ - ["j", "Next"], - ["J", "Next"], - ["ArrowDown", "Next"], - ["k", "Previous"], - ["K", "Previous"], - ["ArrowUp", "Previous"], - ["Enter", "Open"], - ["Escape", "Escape"], -]) - -const isInteractiveTarget = (target: EventTarget | null): boolean => - target instanceof HTMLElement && - target.closest("button, a, [role='button'], [role='menuitem'], [role='option']") !== null - -/** `useListNavigation`'s hotkeys; suspended while a dialog owns the keyboard or a field has focus. */ -const listKeys: Stream.Stream = Dom.streamFromEventFilterMap({ - target: () => document, - type: "keydown", - filterMapEvent: (event) => { - const key = LIST_KEYS.get(event.key) - if (key === undefined || event.metaKey || event.ctrlKey || event.altKey) return Option.none() - if ((key === "Next" || key === "Previous") && event.shiftKey) return Option.none() - if (isEditableTarget(event.target) || isDialogOpen()) return Option.none() - if (key === "Open" && isInteractiveTarget(event.target)) return Option.none() - if (key === "Next" || key === "Previous") event.preventDefault() - return Option.some(Message.PressedListKey({ key })) - }, -}) - -/** `useIntervalRefresh`: the first page again every 15 s while Live (paused while hidden or offline). */ -const subscriptions = Subscription.make, Message>()((entry) => ({ - listKeys: entry( - { hasRows: Schema.Boolean }, - { - modelToDependencies: ({ model }) => ({ hasRows: AsyncData.hasData(model.firstPage.data) }), - dependenciesToStream: ({ hasRows }) => (hasRows ? listKeys : Stream.empty), - }, - ), - live: entry( - { isLive: Schema.Boolean }, - { - modelToDependencies: ({ model }) => ({ isLive: isLiveOf(model.search) }), - dependenciesToStream: ({ isLive }) => - isLive - ? pollTicks(LIVE_REFRESH_INTERVAL).pipe( - Stream.mapEffect(() => - Effect.map(Clock.currentTimeMillis, (nowMs) => Message.TickedLiveRefresh({ nowMs })), - ), - ) - : Stream.empty, - }, - ), -})) - -/** `/anomalies`: one instance; a search change arrives as `routeChanged`. */ -export const page = definePage( - "AnomaliesList", - { Model, Message }, - { - routes: ["Anomalies"], - init, - update, - routeChanged, - view, - subscriptions, - breadcrumbs: () => [{ label: "Anomalies", isPageHeading: true }], - }, -) diff --git a/apps/web/src/page/anomalies/list/message.ts b/apps/web/src/page/anomalies/list/message.ts deleted file mode 100644 index d6e9b5d8d8..0000000000 --- a/apps/web/src/page/anomalies/list/message.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Schema } from "effect" -import { defineMessageUnion } from "foldkit/message" -import { LoadFailure } from "@maple/views/data/load-failure" -import * as FilterSections from "@maple/views/ui/filters/filter-sections" -import * as Tabs from "@maple/views/ui/tabs" -import { SignalPresences } from "@maple/views/data/signal-presence" -import { GroupKey, IncidentPage, StatusTab } from "./model" - -/** The sidebar facets, each with its excluded twin in the URL. */ -export const Facet = Schema.Literals(["severity", "signals", "services", "envs"]) -export type Facet = typeof Facet.Type - -/** A chip's URL param (`AnomalyFilterChipDescriptor.param`). */ -export const FilterParam = Schema.Literals([ - "severity", - "signals", - "services", - "envs", - "excludedSeverity", - "excludedSignals", - "excludedServices", - "excludedEnvs", -]) -export type FilterParam = typeof FilterParam.Type - -export const Message = defineMessageUnion({ - ReadClock: { nowMs: Schema.Number }, - CompletedFetchFirstPage: { key: Schema.String, result: Schema.Result(IncidentPage, LoadFailure) }, - ClickedLoadMore: {}, - /** `cursor` is the page it continues; a refresh in between moves the cursor and drops it. */ - CompletedLoadMore: { status: StatusTab, cursor: Schema.String, result: Schema.Result(IncidentPage, LoadFailure) }, - ClickedRetry: {}, - /** Each live tick reads the clock, so relative times age with the data. */ - TickedLiveRefresh: { nowMs: Schema.Number }, - ToggledLive: {}, - ToggledFacetOption: { facet: Facet, name: Schema.String }, - ClickedOnlyFacetOption: { facet: Facet, name: Schema.String }, - ClickedExcludeFacetOption: { facet: Facet, name: Schema.String }, - RemovedFilterChip: { param: FilterParam }, - ClickedClearFilters: {}, - ClickedClearExclusions: {}, - ToggledGroup: { group: GroupKey }, - HoveredRow: { id: Schema.String }, - /** `useListNavigation`: j/k and the arrows move the focus, Enter opens it, Escape clears it. */ - PressedListKey: { key: Schema.Literals(["Next", "Previous", "Open", "Escape"]) }, - CompletedScrollRow: {}, - CompletedLoadSignalPresence: { result: Schema.Result(SignalPresences, LoadFailure) }, - GotTabsMessage: { message: Tabs.Message }, - GotFilterSectionsMessage: { message: FilterSections.Message }, -}) -export type Message = typeof Message.Type diff --git a/apps/web/src/page/anomalies/list/model.ts b/apps/web/src/page/anomalies/list/model.ts deleted file mode 100644 index b175f7570a..0000000000 --- a/apps/web/src/page/anomalies/list/model.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { V2AnomalyIncident } from "@maple/domain/http/v2" -import { Schema } from "effect" -import { KeyedSchema } from "@maple/views/data/keyed" -import { LoadFailure } from "@maple/views/data/load-failure" -import { AnomaliesSearch } from "../../../route/search" -import * as FilterSections from "@maple/views/ui/filters/filter-sections" -import * as Tabs from "@maple/views/ui/tabs" -import { SignalPresenceData } from "@maple/views/data/signal-presence" - -// `/anomalies`. - -export const StatusTab = Schema.Literals(["open", "resolved", "all"]) -export type StatusTab = typeof StatusTab.Type - -export const GroupKey = Schema.Literals(["critical", "warning", "resolved"]) -export type GroupKey = typeof GroupKey.Type - -/** One page of `anomalies.listIncidents`. */ -export const IncidentPage = Schema.Struct({ - data: Schema.Array(V2AnomalyIncident), - nextCursor: Schema.NullOr(Schema.String), -}) -export type IncidentPage = typeof IncidentPage.Type - -/** `LoadedPages`: pages past the first, for the tab they were fetched for. */ -export const LoadedPages = Schema.Struct({ - key: StatusTab, - rows: Schema.Array(V2AnomalyIncident), - nextCursor: Schema.NullOr(Schema.String), -}) -export type LoadedPages = typeof LoadedPages.Type - -export const Model = Schema.Struct({ - search: AnomaliesSearch, - /** `Date.now()` at render: relative times and the stale-incident cut. */ - nowMs: Schema.Number, - firstPage: KeyedSchema(IncidentPage, LoadFailure), - loadedPages: Schema.NullOr(LoadedPages), - isLoadingMore: Schema.Boolean, - presence: SignalPresenceData.schema, - tabs: Tabs.Model, - filterSections: FilterSections.Model, - /** `AnomalyGroup`'s `isOpen`, per group: the collapsed ones. */ - collapsedGroups: Schema.Array(GroupKey), - focusedId: Schema.NullOr(Schema.String), -}) -export type Model = typeof Model.Type diff --git a/apps/web/src/page/anomalies/list/update.ts b/apps/web/src/page/anomalies/list/update.ts deleted file mode 100644 index fabf8c9759..0000000000 --- a/apps/web/src/page/anomalies/list/update.ts +++ /dev/null @@ -1,336 +0,0 @@ -import type { AnomalyIncidentDocument } from "@maple/domain/http" -import { Option, Result, Array as Arr, Record as Rec } from "effect" -import { AsyncData, Command } from "foldkit" -import * as Keyed from "@maple/views/data/keyed" -import { requestKey } from "@maple/views/data/request-key" -import type { Resources } from "../../../platform/resources" -import { AppRoute, hrefOf } from "../../../route" -import * as FilterSections from "@maple/views/ui/filters/filter-sections" -import * as Tabs from "@maple/views/ui/tabs" -import { type PageContext, PageOutMessage, type PageReturn } from "../../contract" -import { SignalPresenceData } from "@maple/views/data/signal-presence" -import { - FetchFirstPage, - listQueryOf, - LoadMoreIncidents, - LoadSignalPresence, - ReadAnomaliesClock, - ScrollIncidentIntoView, -} from "./command" -import { - allIncidentsOf, - anomalyFilterChips, - facetsOf, - filteredOf, - filtersOf, - groupIncidents, - isLiveOf, - nextCursorOf, - type Search, - statusOf, -} from "./derive" -import { type Facet, Message } from "./message" -import type { Model, StatusTab } from "./model" -import { anomalyHref } from "../kit/row" - -type Return = PageReturn -type Commands = ReadonlyArray> - -export const STATUS_TABS: ReadonlyArray = ["open", "resolved", "all"] - -const EXCLUDED_OF = { - severity: "excludedSeverity", - signals: "excludedSignals", - services: "excludedServices", - envs: "excludedEnvs", -} as const satisfies Record - -/** Requests the tab's first page under `mode`; a refresh drops the "Load more" pages. */ -const loadFirstPage = (model: Model, context: PageContext, mode: Keyed.RequestMode): Return => { - const status = statusOf(model.search) - const key = requestKey(context.orgId, "anomalies.listIncidents", { query: listQueryOf(status) }) - const base = mode === "Refresh" ? { ...model, loadedPages: null } : model - return Option.match(Keyed.request(model.firstPage, key, mode), { - onNone: () => ({ model: base }), - onSome: (firstPage) => ({ - model: { ...base, firstPage }, - commands: [FetchFirstPage({ key, status })], - }), - }) -} - -export const init = (route: { readonly search: Search }, context: PageContext): Return => { - const model: Model = { - search: route.search, - nowMs: 0, - firstPage: Keyed.idle(), - loadedPages: null, - isLoadingMore: false, - presence: SignalPresenceData.Loading(), - tabs: Tabs.init({ idBase: 0, values: STATUS_TABS, selected: statusOf(route.search) }), - filterSections: FilterSections.init(), - collapsedGroups: [], - focusedId: null, - } - const loaded = loadFirstPage(model, context, "Load") - return { - model: loaded.model, - commands: [ - LoadSignalPresence({ orgId: context.orgId }), - ReadAnomaliesClock(), - ...(loaded.commands ?? []), - Command.mapMessage(FilterSections.ReadRemembered(), (message) => - Message.GotFilterSectionsMessage({ message }), - ), - ], - } -} - -export const routeChanged = ( - model: Model, - route: { readonly search: Search }, - context: PageContext, -): Return => { - const status = statusOf(route.search) - const next: Model = { - ...model, - search: route.search, - tabs: { ...model.tabs, selected: status, highlightedIndex: STATUS_TABS.indexOf(status) }, - } - return loadFirstPage(next, context, "Load") -} - -const navigate = (search: Search): Return["outMessage"] => - PageOutMessage.RequestedNavigation({ url: hrefOf(AppRoute.Anomalies({ search })), isReplace: false }) - -const toNavigation = (model: Model, search: Search): Return => ({ model, outMessage: navigate(search) }) - -const listOrUndefined = (values: ReadonlyArray) => (values.length === 0 ? undefined : values) - -/** Severity and signal keep the sidebar's order and drop values the tab has no rows for. */ -const inFacetOrder = ( - entries: ReadonlyArray, - values: ReadonlyArray, -) => - listOrUndefined( - Arr.filter( - Arr.map(entries, ([name]) => name), - (name) => values.includes(name), - ), - ) - -const withFacet = ( - model: Model, - facet: Facet, - selected: ReadonlyArray, - excluded: ReadonlyArray, -): Search => { - const facets = facetsOf(allIncidentsOf(model)) - switch (facet) { - case "severity": - return { - ...model.search, - severity: inFacetOrder(facets.severity, selected), - excludedSeverity: inFacetOrder(facets.severity, excluded), - } - case "signals": - return { - ...model.search, - signals: inFacetOrder(facets.signals, selected), - excludedSignals: inFacetOrder(facets.signals, excluded), - } - case "services": - return { - ...model.search, - services: listOrUndefined(selected), - excludedServices: listOrUndefined(excluded), - } - case "envs": - return { - ...model.search, - envs: listOrUndefined(selected), - excludedEnvs: listOrUndefined(excluded), - } - } -} - -const facetValues = (model: Model, facet: Facet) => { - const selected: ReadonlyArray = model.search[facet] ?? [] - const excluded: ReadonlyArray = model.search[EXCLUDED_OF[facet]] ?? [] - return { selected, excluded } -} - -const toggledFacet = (model: Model, facet: Facet, name: string): Search => { - const { selected, excluded } = facetValues(model, facet) - return withFacet( - model, - facet, - selected.includes(name) ? Arr.filter(selected, (value) => value !== name) : [...selected, name], - Arr.filter(excluded, (value) => value !== name), - ) -} - -const excludedFacet = (model: Model, facet: Facet, name: string): Search => { - const { selected, excluded } = facetValues(model, facet) - return withFacet( - model, - facet, - Arr.filter(selected, (value) => value !== name), - excluded.includes(name) ? Arr.filter(excluded, (value) => value !== name) : [...excluded, name], - ) -} - -const tabsStep = (model: Model, message: Tabs.Message): Return => { - const next = Tabs.update(model.tabs, message) - const updated = { ...model, tabs: next.model } - const commands: Commands = Command.mapMessages(next.commands ?? [], (tabs) => - Message.GotTabsMessage({ message: tabs }), - ) - if (message._tag !== "ClickedTab") return { model: updated, commands } - const value = model.tabs.values[message.index] - if (value === undefined || value === model.tabs.selected) return { model: updated, commands } - const status = value === "resolved" || value === "all" ? value : undefined - return { model: updated, commands, outMessage: navigate({ ...model.search, status }) } -} - -/** The rows in reading order: every visible group's incidents, collapsed groups included. */ -const rowsInOrder = (model: Model): ReadonlyArray => - Arr.flatMap(groupIncidents(filteredOf(model, allIncidentsOf(model))), ([, incidents]) => incidents) - -/** `useListNavigation` over the incident rows. */ -const pressedListKey = (model: Model, key: "Next" | "Previous" | "Open" | "Escape"): Return => { - if (key === "Escape") - return model.focusedId === null ? { model } : { model: { ...model, focusedId: null } } - const incidents = rowsInOrder(model) - if (key === "Open") { - return Option.match( - Arr.findFirst(incidents, (incident) => incident.id === model.focusedId), - { - onNone: (): Return => ({ model }), - onSome: (focused): Return => ({ - model, - outMessage: PageOutMessage.RequestedNavigation({ - url: anomalyHref(focused.id), - isReplace: false, - }), - }), - }, - ) - } - if (incidents.length === 0) return { model } - const current = - model.focusedId === null - ? -1 - : Option.getOrElse( - Arr.findFirstIndex(incidents, (incident) => incident.id === model.focusedId), - () => -1, - ) - const next = - key === "Next" - ? current < 0 - ? 0 - : Math.min(current + 1, incidents.length - 1) - : current <= 0 - ? 0 - : current - 1 - const target = incidents[next] - if (target === undefined) return { model } - return { - model: { ...model, focusedId: target.id }, - commands: [ScrollIncidentIntoView({ incidentId: target.id })], - } -} - -export const update = (model: Model, message: Message, context: PageContext): Return => - Message.match(message, { - ReadClock: ({ nowMs }) => ({ model: { ...model, nowMs } }), - CompletedFetchFirstPage: ({ key, result }) => ({ - model: { ...model, firstPage: Keyed.accept(model.firstPage, key, result) }, - }), - ClickedLoadMore: () => { - const cursor = nextCursorOf(model) - return cursor === null || model.isLoadingMore - ? { model } - : { - model: { ...model, isLoadingMore: true }, - commands: [LoadMoreIncidents({ status: statusOf(model.search), cursor })], - } - }, - CompletedLoadMore: ({ status, cursor, result }) => - status !== statusOf(model.search) || cursor !== nextCursorOf(model) - ? { model: { ...model, isLoadingMore: false } } - : Result.match(result, { - onFailure: () => ({ - model: { ...model, isLoadingMore: false }, - outMessage: PageOutMessage.RequestedToast({ - type: "error", - title: "Couldn't load more anomalies", - description: "", - }), - }), - onSuccess: (page) => ({ - model: { - ...model, - isLoadingMore: false, - loadedPages: { - key: status, - rows: [ - ...(model.loadedPages?.key === status ? model.loadedPages.rows : []), - ...page.data, - ], - nextCursor: page.nextCursor, - }, - }, - }), - }), - ClickedRetry: () => loadFirstPage(model, context, "Refresh"), - TickedLiveRefresh: ({ nowMs }) => loadFirstPage({ ...model, nowMs }, context, "Refresh"), - ToggledLive: () => { - const next = !isLiveOf(model.search) - return toNavigation(model, { - ...model.search, - live: next === (statusOf(model.search) === "open") ? undefined : next, - }) - }, - ToggledFacetOption: ({ facet, name }) => toNavigation(model, toggledFacet(model, facet, name)), - ClickedOnlyFacetOption: ({ facet, name }) => toNavigation(model, withFacet(model, facet, [name], [])), - ClickedExcludeFacetOption: ({ facet, name }) => - toNavigation(model, excludedFacet(model, facet, name)), - RemovedFilterChip: ({ param }) => toNavigation(model, { ...model.search, [param]: undefined }), - ClickedClearFilters: () => - toNavigation(model, { status: model.search.status, live: model.search.live }), - ClickedClearExclusions: () => - toNavigation(model, { - ...model.search, - ...Rec.fromEntries( - Arr.map( - Arr.filter(anomalyFilterChips(filtersOf(model.search)), (chip) => chip.negated), - (chip) => [chip.param, undefined], - ), - ), - }), - ToggledGroup: ({ group }) => ({ - model: { - ...model, - collapsedGroups: model.collapsedGroups.includes(group) - ? Arr.filter(model.collapsedGroups, (key) => key !== group) - : [...model.collapsedGroups, group], - }, - }), - HoveredRow: ({ id }) => (model.focusedId === id ? { model } : { model: { ...model, focusedId: id } }), - PressedListKey: ({ key }) => pressedListKey(model, key), - CompletedScrollRow: () => ({ model }), - CompletedLoadSignalPresence: ({ result }) => ({ - model: { ...model, presence: AsyncData.settle(model.presence, result) }, - }), - GotTabsMessage: ({ message: inner }) => tabsStep(model, inner), - GotFilterSectionsMessage: ({ message: inner }) => { - const next = FilterSections.update(model.filterSections, inner) - return { - model: { ...model, filterSections: next.model }, - commands: Command.mapMessages(next.commands ?? [], (sections) => - Message.GotFilterSectionsMessage({ message: sections }), - ), - } - }, - }) diff --git a/apps/web/src/page/anomalies/list/view-filters.ts b/apps/web/src/page/anomalies/list/view-filters.ts deleted file mode 100644 index 6ccea6d9a9..0000000000 --- a/apps/web/src/page/anomalies/list/view-filters.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { AnomalyIncidentDocument } from "@maple/domain/http" -import { getServiceColor } from "@maple/ui/lib/colors" -import type { Html, HtmlBuilder } from "foldkit/html" -import * as FilterSections from "@maple/views/ui/filters/filter-sections" -import { type FilterOption, filterSection } from "@maple/views/ui/filters/filter-section" -import { - filterSidebarBody, - filterSidebarEmpty, - filterSidebarFrame, - filterSidebarHeader, - filterSidebarLoading, -} from "@maple/views/ui/filters/filter-sidebar" -import { facetsOf, filtersOf, hasAnomalyFilters } from "./derive" -import { type Facet, Message } from "./message" -import type { Model } from "./model" -import { Array as Arr } from "effect" - -// `AnomaliesFilterSidebar`: the list's filter rail. - -type H = HtmlBuilder - -const toSections = (message: FilterSections.Message) => Message.GotFilterSectionsMessage({ message }) - -const options = (entries: ReadonlyArray): ReadonlyArray => - Arr.map(entries, ([name, count]) => ({ name, count })) - -export const filtersRail = ( - h: H, - model: Model, - incidents: ReadonlyArray, - isLoading: boolean, -): Html => { - const filters = filtersOf(model.search) - const hasFilters = hasAnomalyFilters(filters) - if (isLoading) return filterSidebarLoading(h, 4) - if (incidents.length === 0 && !hasFilters) return filterSidebarEmpty(h) - const facets = facetsOf(incidents) - const section = ( - title: string, - facet: Facet, - entries: ReadonlyArray, - extra: Partial>[1]> = {}, - ) => - filterSection(h, { - title, - options: options(entries), - selected: model.search[facet] ?? [], - excluded: - (facet === "severity" - ? model.search.excludedSeverity - : facet === "signals" - ? model.search.excludedSignals - : facet === "services" - ? model.search.excludedServices - : model.search.excludedEnvs) ?? [], - sections: model.filterSections, - toSectionsMessage: toSections, - onToggle: (name) => Message.ToggledFacetOption({ facet, name }), - onOnly: (name) => Message.ClickedOnlyFacetOption({ facet, name }), - onExclude: (name) => Message.ClickedExcludeFacetOption({ facet, name }), - ...extra, - }) - return filterSidebarFrame(h, { isWaiting: false }, [ - filterSidebarHeader(h, hasFilters ? { onClear: Message.ClickedClearFilters() } : {}), - ...filterSidebarBody( - h, - "anomalies-filters", - Arr.filter( - [ - section("Severity", "severity", facets.severity, { - getOptionLabel: facets.severityLabel, - }), - section("Signal", "signals", facets.signals, { getOptionLabel: facets.signalLabel }), - section("Service", "services", facets.services, { - colorOf: (name) => getServiceColor(name), - }), - section("Environment", "envs", facets.envs), - ], - (node): node is Html => node !== null, - ), - ), - ]) -} diff --git a/apps/web/src/page/anomalies/list/view-list.ts b/apps/web/src/page/anomalies/list/view-list.ts deleted file mode 100644 index 328c4cd170..0000000000 --- a/apps/web/src/page/anomalies/list/view-list.ts +++ /dev/null @@ -1,227 +0,0 @@ -import type { AnomalyIncidentDocument } from "@maple/domain/http" -import { SEVERITY_TONE } from "@maple/web-data/anomalies/format" -import { cn } from "@maple/ui/lib/utils" -import type { Html, HtmlBuilder } from "foldkit/html" -import { AppRoute, hrefOf } from "../../../route" -import { linkButton } from "@maple/views/ui/button" -import { empty, emptyContent, emptyDescription, emptyHeader, emptyTitle } from "@maple/views/ui/empty" -import { excludedEmptyHint } from "@maple/views/ui/filters/excluded-empty-hint" -import { icon } from "@maple/views/ui/icon" -import { listFooter } from "@maple/views/ui/list-footer" -import { pressable } from "@maple/views/ui/pressable" -import { statusDot } from "@maple/views/ui/status-dot" -import { serviceDot } from "@maple/views/ui/values" -import { docsLink, signalEmptyState } from "../../kit/signal-empty-state" -import { emptyActions, filteredEmpty, presenceLoading } from "@maple/views/ui/signal-empty-state" -import { presenceOf } from "@maple/views/data/signal-presence" -import { anomalyRow } from "../kit/row" -import { clusterByService, GROUP_LABEL, groupIncidents } from "./derive" -import { Message } from "./message" -import type { GroupKey, Model, StatusTab } from "./model" -import { typography } from "@maple/views/ui/typography" -import { Array as Arr } from "effect" - -// `AnomaliesPageBody` and `AnomalyGroup`: the anomalies, grouped. - -type H = HtmlBuilder - -const group = ( - h: H, - model: Model, - key: GroupKey, - incidents: ReadonlyArray, - timeZone: string, -): Html => { - const isOpen = !model.collapsedGroups.includes(key) - return h.section( - [h.Key(key)], - [ - pressable( - h, - { - onClick: Message.ToggledGroup({ group: key }), - className: cn( - "sticky top-0 z-10 flex h-8 w-full items-center gap-2 border-b border-border-subtle bg-surface-sunken pr-2 pl-2 text-left outline-none", - "backdrop-blur supports-[backdrop-filter]:bg-surface-strong", - "hover:bg-surface-strong active:bg-surface-strong", - ), - attributes: [h.AriaExpanded(isOpen), h.AriaControls(`anomaly-group-${key}`)], - }, - [ - h.span( - [h.Class("flex size-4 shrink-0 items-center justify-center text-muted-foreground")], - [icon(h, isOpen ? "ChevronDownIcon" : "ChevronRightIcon", { size: 14 })], - ), - statusDot(h, { tone: SEVERITY_TONE[key].tone, size: "lg" }), - h.span([h.Class("shrink-0 text-sm font-medium text-foreground")], [GROUP_LABEL[key]]), - typography(h, { size: "xs", tone: "muted", numeric: true }, [String(incidents.length)]), - ], - ), - ...(isOpen - ? [ - h.div( - [ - h.Id(`anomaly-group-${key}`), - h.Role("list"), - h.Class("divide-y divide-border-subtle"), - ], - Arr.map(clusterByService(incidents), (cluster) => - h.div( - [h.Key(cluster.key + (cluster.incidents[0]?.id ?? ""))], - [ - ...(cluster.incidents.length >= 2 - ? [ - h.div( - [ - h.Class( - "flex h-7 items-center gap-1.5 border-b border-border-subtle bg-surface-faint pl-5 text-2xs text-muted-foreground", - ), - ], - [ - serviceDot(h, { - serviceName: cluster.serviceName, - size: "sm", - }), - h.span( - [h.Class("font-medium text-foreground-soft")], - [cluster.serviceName], - ), - ...(cluster.deploymentEnv - ? [h.span([], [cluster.deploymentEnv])] - : []), - h.span( - [h.Class("tabular-nums")], - [ - String(cluster.incidents.length), - " anomalies", - ], - ), - ], - ), - ] - : []), - h.div( - [h.Class("divide-y divide-border-subtle")], - Arr.map(cluster.incidents, (incident) => - h.div( - [h.Key(incident.id), h.Role("listitem")], - [ - anomalyRow(h, { - incident, - nowMs: model.nowMs, - timeZone, - isFocused: model.focusedId === incident.id, - onHover: Message.HoveredRow({ id: incident.id }), - }), - ], - ), - ), - ), - ], - ), - ), - ), - ] - : []), - ], - ) -} - -const PURPOSE = - "The detector compares every service's error rate, latency, throughput, error fingerprints, and log volume against its own 7-day baseline. Incidents appear here when something deviates." - -const quietEmpty = (h: H, model: Model, status: StatusTab, excludedHint: Html | null): Html => { - const presence = presenceOf(model.presence, "traces") - if (presence.status === "loading") return h.div([h.Class("p-4")], [presenceLoading(h)]) - if (presence.status === "absent") - return h.div( - [h.Class("p-4")], - [ - signalEmptyState(h, { - signal: "traces", - presence, - nowMs: model.nowMs, - noun: "anomalies", - purpose: PURPOSE, - detail: excludedHint, - }), - ], - ) - return h.div( - [h.Class("p-4")], - [ - empty(h, [ - emptyHeader(h, [ - emptyTitle(h, [status === "open" ? "No open anomalies" : "No anomalies"]), - emptyDescription(h, [PURPOSE]), - ]), - ...(presence.status === "present" - ? [ - emptyContent(h, [ - emptyActions(h, [ - linkButton( - h, - { - variant: "outline", - size: "sm", - href: hrefOf(AppRoute.AlertsCreate({ search: {} })), - }, - ["Create an alert rule"], - ), - docsLink(h, "alertRules", "Alert rules"), - ]), - ]), - ] - : []), - ...(excludedHint === null ? [] : [excludedHint]), - ]), - ], - ) -} - -export type BodyInput = Readonly<{ - incidents: ReadonlyArray - status: StatusTab - hasActiveFilters: boolean - excludedValues: ReadonlyArray - hasMore: boolean - timeZone: string -}> - -export const listBody = (h: H, model: Model, input: BodyInput): Html => { - const excludedHint = excludedEmptyHint(h, { - excluded: input.excludedValues, - onClear: Message.ClickedClearExclusions(), - className: "max-w-lg", - }) - if (input.incidents.length === 0 && input.hasActiveFilters) - return h.div( - [h.Class("p-4")], - [ - filteredEmpty(h, { - noun: "anomalies", - description: "Try widening or clearing the filters.", - onClear: Message.ClickedClearFilters(), - detail: excludedHint, - }), - ], - ) - if (input.incidents.length === 0) return quietEmpty(h, model, input.status, excludedHint) - const footer = input.hasMore - ? listFooter(h, { - hasMore: true, - isLoading: model.isLoadingMore, - onLoadMore: Message.ClickedLoadMore(), - className: "p-4", - }) - : null - return h.div( - [], - [ - ...Arr.map(groupIncidents(input.incidents), ([key, incidents]) => - group(h, model, key, incidents, input.timeZone), - ), - ...(footer === null ? [] : [footer]), - ], - ) -} diff --git a/apps/web/src/page/anomalies/list/view.ts b/apps/web/src/page/anomalies/list/view.ts deleted file mode 100644 index c1e15ddfb9..0000000000 --- a/apps/web/src/page/anomalies/list/view.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { pluralize } from "@maple/ui/lib/format" -import { AsyncData, Submodel } from "foldkit" -import type { Html, HtmlBuilder } from "foldkit/html" -import { errorState } from "@maple/views/ui/error-state" -import { filterChip } from "@maple/views/ui/filter-chip" -import { activeFilterChips } from "@maple/views/ui/filters/active-filter-chips" -import { skeleton } from "@maple/views/ui/skeleton" -import * as TabsView from "@maple/views/ui/tabs-view" -import { tooltipTrigger } from "@maple/views/ui/tooltip-host" -import type { PageViewInputs } from "../../contract" -import { dashboardPage } from "../../kit/dashboard-page" -import { listToolbar, toolbarCount } from "../../kit/list-toolbar" -import { - allIncidentsOf, - anomalyFilterChips, - filteredOf, - filtersOf, - hasAnomalyFilters, - isLiveOf, - nextCursorOf, - statusOf, -} from "./derive" -import { Message } from "./message" -import type { Model } from "./model" -import { filtersRail } from "./view-filters" -import { listBody } from "./view-list" -import { Array as Arr } from "effect" - -// The anomalies page. - -type H = HtmlBuilder - -const TAB_LABELS = ["Open", "Resolved", "All"] - -const liveChip = (h: H, isLive: boolean): Html => - filterChip( - h, - { - isPressed: isLive, - onClick: Message.ToggledLive(), - tone: "ok", - hasDot: true, - slot: "tooltip-trigger", - attributes: [ - h.DataAttribute("base-ui-tooltip-trigger", ""), - ...tooltipTrigger( - h, - isLive - ? "Auto-refreshing every 15s. Click to pause." - : "Paused. Click to resume auto-refresh.", - ), - ], - }, - [isLive ? "Live" : "Paused"], - ) - -/** `ListToolbar`: the status tabs and the count readout. */ -// The sticky area pads the page edge, so the toolbar adds no gap under itself. -const toolbar = (h: H, model: Model, count: number | null): Html => - listToolbar(h, { - className: "pb-0", - leading: h.submodel({ - slotId: "anomalies-status-tabs", - model: model.tabs, - view: TabsView.view, - viewInputs: { - labels: TAB_LABELS, - ariaLabel: "Filter anomalies", - tabClassName: "group/tab h-7 grow-0 px-2.5 text-xs sm:h-7 sm:text-xs", - panel: () => undefined, - }, - toParentMessage: (message) => Message.GotTabsMessage({ message }), - }), - trailing: - count === null - ? [] - : [toolbarCount(h, [String(count), " ", pluralize(count, "anomaly", "anomalies")])], - }) - -const SKELETON_WIDTHS = ["w-24", "w-20", "w-28", "w-16", "w-24", "w-20"] - -/** The list before its first page: a group header and rows in the lanes `anomalyRow` draws. */ -const listSkeleton = (h: H): Html => - h.div( - [h.AriaBusy(true), h.AriaLabel("Loading anomalies")], - [ - h.div( - [h.Class("flex h-8 items-center gap-2 border-b border-border-subtle bg-surface-sunken px-2")], - [skeleton(h, "size-3.5 rounded-sm"), skeleton(h, "h-3 w-16")], - ), - h.div( - [h.Class("divide-y divide-border-subtle")], - Arr.map(SKELETON_WIDTHS, (width) => - h.div( - [h.Class("flex h-9 items-center gap-2 px-3"), h.AriaHidden(true)], - [ - h.span( - [h.Class("flex w-3 shrink-0 justify-center")], - [skeleton(h, "size-2 rounded-full")], - ), - h.span( - [h.Class("flex flex-1 items-center sm:w-32 sm:flex-none")], - [skeleton(h, `h-3.5 ${width}`)], - ), - h.span([h.Class("flex w-28 shrink-0 justify-end")], [skeleton(h, "h-3 w-14")]), - h.span([h.Class("hidden md:flex")], [skeleton(h, "h-4 w-24 rounded-full")]), - h.span([h.Class("hidden min-w-0 flex-1 sm:block")]), - h.span([h.Class("flex w-28 shrink-0 justify-end")], [skeleton(h, "h-3 w-12")]), - ], - ), - ), - ), - ], - ) - -export const view = Submodel.defineView((model, inputs, h) => { - const all = allIncidentsOf(model) - const filtered = filteredOf(model, all) - const filters = filtersOf(model.search) - const chips = anomalyFilterChips(filters) - const excludedChips = Arr.filter(chips, (chip) => chip.negated) - const data = model.firstPage.data - const hasFirstPage = AsyncData.hasData(data) && !AsyncData.isFailure(data) - const body = AsyncData.match(data, { - onIdle: () => listSkeleton(h), - onLoading: () => listSkeleton(h), - onFailure: (failure) => failed(h, failure), - onStale: ({ error }) => failed(h, error), - onSuccess: () => loaded(), - onRefreshing: () => loaded(), - }) - function loaded(): Html { - return h.div( - [h.Key("loaded"), h.Class("contents")], - [ - listBody(h, model, { - incidents: filtered, - status: statusOf(model.search), - hasActiveFilters: hasAnomalyFilters(filters), - excludedValues: Arr.flatMap(excludedChips, (chip) => chip.values), - hasMore: nextCursorOf(model) !== null, - timeZone: inputs.context.timeZone, - }), - ], - ) - } - const chipBar = activeFilterChips(h, { - chips: Arr.map(chips, (chip) => ({ - id: chip.param, - label: chip.label, - values: chip.values, - isNegated: chip.negated, - onRemove: Message.RemovedFilterChip({ param: chip.param }), - })), - onClearAll: Message.ClickedClearFilters(), - className: "mb-0 px-2 pb-2", - }) - return dashboardPage( - h, - { - title: "Anomalies", - filters: filtersRail(h, model, all, AsyncData.isLoading(data) || AsyncData.isIdle(data)), - // The live toggle is an action; while reads fail the error block owns the retry status. - actions: AsyncData.hasError(data) ? undefined : liveChip(h, isLiveOf(model.search)), - sticky: toolbar(h, model, hasFirstPage ? filtered.length : null), - }, - [h.div([], [...(chipBar === null ? [] : [chipBar]), body])], - ) -}) - -const failed = (h: H, failure: Parameters>[1]["failure"]): Html => - h.div( - [h.Class("pt-4")], - [errorState(h, { failure, title: "Couldn't load anomalies", onRetry: Message.ClickedRetry() })], - ) diff --git a/apps/web/src/page/kit/grid-list-row.ts b/apps/web/src/page/kit/grid-list-row.ts new file mode 100644 index 0000000000..3c75a99e6d --- /dev/null +++ b/apps/web/src/page/kit/grid-list-row.ts @@ -0,0 +1,20 @@ +import { cn } from "@maple/ui/lib/utils" +import type { Attribute, Html, HtmlBuilder } from "foldkit/html" + +/** + * `GridListRow`: one `
  • ` of a bordered grid list (the alerts inbox, rules and resolved lists), padded, + * tinted on hover and press, positioned so a stretched row link covers it. + */ +export const gridListRow = ( + h: HtmlBuilder, + options: Readonly<{ key: string; grid: string; attributes?: ReadonlyArray> }>, + children: ReadonlyArray, +): Html => + h.li( + [ + h.Key(options.key), + h.Class(cn(options.grid, "relative px-4 py-3 hover:bg-surface-sunken active:bg-surface-sunken")), + ...(options.attributes ?? []), + ], + children, + ) diff --git a/apps/web/src/page/registry.ts b/apps/web/src/page/registry.ts index f5b22298e9..63b58aea1e 100644 --- a/apps/web/src/page/registry.ts +++ b/apps/web/src/page/registry.ts @@ -22,7 +22,6 @@ import * as AgentSessionTools from "./agent-sessions/tools" import * as McpServers from "./mcp/hub" import * as AlertEditor from "./alerts/editor" import * as AlertsHub from "./alerts/hub" -import * as AnomaliesList from "./anomalies/list" import * as AnomalyDetail from "./anomalies/detail" import * as AlertIncident from "./alerts/incident" import * as AlertRule from "./alerts/rule" @@ -170,7 +169,6 @@ export const pages = [ AgentSession.page, Integrations.page, Recommendation.page, - AnomaliesList.page, AnomalyDetail.page, ReleasesList.page, ReleaseDetail.page, diff --git a/apps/web/src/route/redirect.ts b/apps/web/src/route/redirect.ts index 694379d4d2..eb94cbcf73 100644 --- a/apps/web/src/route/redirect.ts +++ b/apps/web/src/route/redirect.ts @@ -41,7 +41,7 @@ const DATA_INTEGRATION_IDS: ReadonlySet = new Set(DATA_INTEGRATIONS) const redirect = (to: AppRoute, isReplace: boolean) => Option.some(Redirect.make({ url: hrefOf(to), isReplace })) -/** Redirects of retired paths (connectors, developer, infra, errors/issues, alerts status pages, security) and moved integrations. */ +/** Redirects of retired paths (connectors, developer, infra, errors/issues, alerts status pages, anomalies, security) and moved integrations. */ export const routeRedirect = (route: AppRoute): Option.Option => AppRoute.matchOrElse>( route, @@ -73,6 +73,22 @@ export const routeRedirect = (route: AppRoute): Option.Option => InfraDiscover: () => redirect(AppRoute.Infra({ search: {} }), true), InfraLegacyHost: ({ hostName }) => redirect(AppRoute.InfraHost({ hostName, search: {} }), true), AlertIncidentsIndex: () => redirect(AppRoute.Alerts({ search: {} }), true), + /** The anomalies list is the Alerts Inbox's Anomalies view; a service and status carry over. */ + Anomalies: ({ search }) => + redirect( + AppRoute.Alerts({ + search: { + signal: "anomaly", + ...(search.services !== undefined && search.services.length === 1 + ? { serviceName: search.services[0] } + : undefined), + ...(search.status === "resolved" || search.status === "all" + ? { anomalyStatus: search.status } + : undefined), + }, + }), + true, + ), /** Status pages left the Alerts hub for their own sidebar entry. */ Alerts: ({ search }) => search.tab === "status-pages" ? redirect(AppRoute.StatusPages(), true) : Option.none(), diff --git a/apps/web/src/route/route.test.ts b/apps/web/src/route/route.test.ts index 0fe63a400a..8dc60fdc90 100644 --- a/apps/web/src/route/route.test.ts +++ b/apps/web/src/route/route.test.ts @@ -85,6 +85,28 @@ describe("routes", () => { ) }) + test("the anomalies list opens the Alerts Inbox's Anomalies view, carrying a service and status", () => { + expect(routeRedirect(parse("/anomalies"))).toEqual( + Option.some({ url: "/alerts?signal=anomaly", isReplace: true }), + ) + const narrowed = routeRedirect( + parse("/anomalies?services=%5B%22checkout%22%5D&status=%22resolved%22"), + ) + expect(Option.map(narrowed, ({ url }) => parse(url))).toEqual( + Option.some( + AppRoute.Alerts({ + search: { signal: "anomaly", serviceName: "checkout", anomalyStatus: "resolved" }, + }), + ), + ) + // "open" is the view's default, and several services have no one-service filter to land on. + const several = routeRedirect(parse("/anomalies?services=%5B%22a%22%2C%22b%22%5D&status=%22open%22")) + expect(Option.map(several, ({ url }) => parse(url))).toEqual( + Option.some(AppRoute.Alerts({ search: { signal: "anomaly" } })), + ) + expect(routeRedirect(parse("/anomalies/00000000-0000-4000-8000-000000000004"))).toEqual(Option.none()) + }) + test("the old Hazel integration link opens the Hazel chat connector", () => { expect(routeRedirect(parse("/integrations?integration=hazel"))).toEqual( Option.some({ url: "/integrations?integration=chat-hazel", isReplace: true }), diff --git a/apps/web/src/route/search.ts b/apps/web/src/route/search.ts index 7803dd621d..bfa3bc8d82 100644 --- a/apps/web/src/route/search.ts +++ b/apps/web/src/route/search.ts @@ -510,6 +510,10 @@ export const AlertsSearch = Schema.Struct({ tab: Schema.optional(Schema.String), serviceName: Schema.optional(Schema.String), status: Schema.optional(Schema.String), + /** The Inbox narrowed to one kind of incident: "anomaly" lists anomalies alone. */ + signal: Schema.optional(Schema.String), + /** The Anomalies view's incidents: open (the default), "resolved" or "all". */ + anomalyStatus: Schema.optional(Schema.String), }) // sessionTimeRangeSearchMiddleware() diff --git a/apps/web/src/view.lazy.test.ts b/apps/web/src/view.lazy.test.ts index 8aea4963a0..404050eca0 100644 --- a/apps/web/src/view.lazy.test.ts +++ b/apps/web/src/view.lazy.test.ts @@ -96,7 +96,6 @@ const SAMPLE_PATHS = [ "/mcp-servers/maple-ai/tools/run_sql", "/integrations", "/recommendations/rec_1", - "/anomalies", "/anomalies/00000000-0000-4000-8000-000000000004", "/releases", "/releases/abc123", diff --git a/packages/views/src/ui/INDEX.md b/packages/views/src/ui/INDEX.md index 53137c3e47..02939bfc4e 100644 --- a/packages/views/src/ui/INDEX.md +++ b/packages/views/src/ui/INDEX.md @@ -185,6 +185,7 @@ existing copies onto it (see `apps/web/CLAUDE.md`). - `apps/web/src/page/kit/destination-picker.ts` `destinationPicker`, `pickedDestination`: `DestinationPicker`: the alert destinations a form notifies, each removable, then an "Add destination" menu of the rest; the owner keeps the menu's Model and the chosen ids. - `apps/web/src/page/kit/detail-header.ts` `detailEyebrow`, `detailTitle`, `detailTitleSkeleton`, `detailActions`: `DetailHeader`'s title side and `DetailHeaderSkeleton`. - `apps/web/src/page/kit/form-section.ts` `formSection`, `formCard`, `formActionBar`, `managersOnlyPage`: `FormSection`: one row of a form card, what it sets on the left (a title and an optional hint) and its controls on the right; the two stack on phones. +- `apps/web/src/page/kit/grid-list-row.ts` `gridListRow`: `GridListRow`: one `
  • ` of a bordered grid list (the alerts inbox, rules and resolved lists), padded, tinted on hover and press, positioned so a stretched row link covers it. - `apps/web/src/page/kit/guided-setup.ts` `frameworkIconStrip`, `frameworkPicker`, `agentPromptButton`, `connectInstructions`: `FrameworkIconStrip`, `FrameworkPicker` and `ConnectInstructions`, with `PackageManagerCodeBlock`. - `apps/web/src/page/kit/infra-correlation/command.ts` `operationOf`: The four `*InfraTimeseries` reads the panel's charts make, through the read cache under the atom key (`requestKey(org, "", { data })`), so a reopened panel reuses them. - `apps/web/src/page/kit/infra-correlation/update.ts` `chartIdOf`, `requestsOf` (stateful: Model, init, update): A chart's slot and chart id, `-` (the linked-cursor id). diff --git a/packages/web-data/src/alerts/anomaly-rule.test.ts b/packages/web-data/src/alerts/anomaly-rule.test.ts new file mode 100644 index 0000000000..2179d64d9e --- /dev/null +++ b/packages/web-data/src/alerts/anomaly-rule.test.ts @@ -0,0 +1,56 @@ +import { Array as Arr } from "effect" +import { describe, expect, it } from "vitest" +import { anomalyScopeSummary, isDefaultAnomalyRule } from "./anomaly-rule" +import { buildRuleCreateParamsV2, defaultRuleForm, deriveTierIssues } from "./form-utils" +import { switchSignalKind } from "./rule-modes" + +const anomalyForm = (overrides: Partial> = {}) => + switchSignalKind({ ...defaultRuleForm(), name: "Checkout anomalies", ...overrides }, "anomaly").form + +describe("anomaly rules", () => { + it("only an unscoped anomaly rule is the default one", () => { + expect(isDefaultAnomalyRule(anomalyForm())).toBe(true) + expect(isDefaultAnomalyRule(anomalyForm({ serviceNames: ["checkout"] }))).toBe(false) + expect(isDefaultAnomalyRule({ ...anomalyForm(), anomalySignals: ["throughput"] })).toBe(false) + expect(isDefaultAnomalyRule(defaultRuleForm())).toBe(false) + }) + + it("saves both tiers at the severity codes and sends its signals and scope", () => { + const form = { + ...anomalyForm({ serviceNames: ["checkout"], environments: ["production"] }), + anomalySignals: ["error_spike" as const], + } + const params = buildRuleCreateParamsV2(form) + expect(params.signal_type).toBe("anomaly") + expect(params.anomaly_signals).toEqual(["error_spike"]) + expect(params.service_names).toEqual(["checkout"]) + expect(params.environments).toEqual(["production"]) + expect(Arr.map(params.thresholds ?? [], (tier) => [tier.severity, tier.value])).toEqual([ + ["warning", 1], + ["critical", 2], + ]) + }) + + it("lets an anomaly tier stay unrouted, unlike a threshold rule's critical tier", () => { + expect(deriveTierIssues(anomalyForm())).toEqual([]) + }) + + it("leaving the anomaly kind starts a query from error rate with both tiers' destinations", () => { + const anomaly = anomalyForm({ serviceNames: ["checkout"] }) + const left = switchSignalKind(anomaly, "builtin").form + expect(left.signalType).toBe("error_rate") + expect(left.escalation).toBeNull() + expect(left.serviceNames).toEqual(["checkout"]) + expect(left.threshold).toBe(defaultRuleForm().threshold) + }) + + it("summarises the scope, counting long lists", () => { + expect( + anomalyScopeSummary({ + serviceNames: ["a", "b", "c"], + environments: [], + anomalySignals: ["log_volume"], + }), + ).toBe("3 services · all environments · Log volume") + }) +}) diff --git a/packages/web-data/src/alerts/anomaly-rule.ts b/packages/web-data/src/alerts/anomaly-rule.ts new file mode 100644 index 0000000000..ead02814c3 --- /dev/null +++ b/packages/web-data/src/alerts/anomaly-rule.ts @@ -0,0 +1,61 @@ +import type { AlertRuleDocument, AnomalySignalType } from "@maple/domain/http" +import { Array as Arr, String as Str } from "effect" + +/** Every detector signal, in the order the editor offers them. */ +export const ANOMALY_SIGNALS: ReadonlyArray = [ + "error_rate", + "latency_p95", + "throughput", + "error_spike", + "log_volume", +] + +/** A signal as the rule editor and the rules list name it. */ +export const ANOMALY_SIGNAL_LABELS: Record = { + error_rate: "Error rate", + latency_p95: "p95 latency", + throughput: "Throughput", + error_spike: "Error spikes", + log_volume: "Log volume", +} satisfies Record + +/** What an anomaly rule pages on. Each empty list means every value. */ +export type AnomalyRuleScope = Readonly<{ + serviceNames: ReadonlyArray + environments: ReadonlyArray + anomalySignals?: ReadonlyArray | undefined +}> + +/** The org's default anomaly rule: an anomaly rule with no scope. The server keeps exactly one. */ +export const isDefaultAnomalyRule = ( + rule: AnomalyRuleScope & Readonly<{ signalType: AlertRuleDocument["signalType"] }>, +): boolean => + rule.signalType === "anomaly" && + rule.serviceNames.length === 0 && + rule.environments.length === 0 && + (rule.anomalySignals ?? []).length === 0 + +const listOr = (values: ReadonlyArray, all: string): string => + values.length === 0 + ? all + : values.length <= 2 + ? Arr.join(values, ", ") + : `${values.length} ${Str.replace(/^all /, "")(all)}` + +/** "All services · production · Error rate, p95 latency": an anomaly rule's scope in one line. */ +export const anomalyScopeSummary = (scope: AnomalyRuleScope): string => + Arr.join( + [ + listOr(scope.serviceNames, "all services"), + listOr(scope.environments, "all environments"), + listOr( + Arr.map(scope.anomalySignals ?? [], (signal) => ANOMALY_SIGNAL_LABELS[signal]), + "all signals", + ), + ], + " · ", + ) + +/** The incident's severity in words: an anomaly rule observes 1 for warning and 2 for critical. */ +export const anomalySeverityLabel = (value: number | null): string => + value === null ? "Normal" : value >= 2 ? "Critical" : value >= 1 ? "Warning" : "Normal" diff --git a/packages/web-data/src/alerts/form-utils.ts b/packages/web-data/src/alerts/form-utils.ts index ecaf730e67..528f5a70c8 100644 --- a/packages/web-data/src/alerts/form-utils.ts +++ b/packages/web-data/src/alerts/form-utils.ts @@ -20,6 +20,7 @@ import { type AlertEventType, type AlertSeverity, type AlertSignalType, + type AnomalySignalType, type ChatWorkspaceId, type QueryBuilderQueryDraftPayload, MAX_ALERT_BREACH_WINDOW, @@ -68,6 +69,8 @@ export type RuleFormState = { * carry their own filters. */ environments: string[] + /** Anomaly rules only: the detector signals the rule pages on. Empty means every signal. */ + anomalySignals: AnomalySignalType[] /** Free-form tags used to group and filter rules in the alerts list. */ tags: string[] /** @@ -314,6 +317,7 @@ export function defaultRuleForm(serviceName?: string): RuleFormState { serviceNames: serviceName ? [serviceName] : [], excludeServiceNames: [], environments: [], + anomalySignals: [], tags: [], groupBy: [], signalType: "error_rate", @@ -340,6 +344,37 @@ export function defaultRuleForm(serviceName?: string): RuleFormState { } } +/** + * The form as an anomaly rule: both tiers (their values are the severity codes the server pins) and + * the evaluation fields the server pins, so the change list and the save agree with the stored rule. + */ +export function anomalyRuleForm(form: RuleFormState): RuleFormState { + return { + ...form, + signalType: "anomaly", + severity: "warning", + comparator: "gte", + threshold: "1", + thresholdUpper: "", + recoveryValue: "", + escalation: { + threshold: "2", + recoveryValue: "", + destinationIds: form.escalation?.destinationIds ?? [], + }, + excludeServiceNames: [], + groupBy: [], + windowMinutes: "5", + evaluationIntervalMinutes: "1", + minimumSampleCount: "0", + alertOnNoData: false, + consecutiveBreachesRequired: "1", + breachesOutOf: "", + consecutiveHealthyRequired: "1", + renotifyIntervalMinutes: "30", + } +} + /** Grouped rules cannot alert on no data: a group that stops reporting is held by its incident. */ export function ruleFormIsGrouped(form: RuleFormState): boolean { if (form.signalType === "raw_query") return false @@ -363,6 +398,7 @@ export function ruleToFormState(rule: AlertRuleDocument): RuleFormState { serviceNames: rule.serviceNames?.length > 0 ? [...rule.serviceNames] : [], excludeServiceNames: rule.excludeServiceNames?.length > 0 ? [...rule.excludeServiceNames] : [], environments: rule.environments?.length > 0 ? [...rule.environments] : [], + anomalySignals: [...(rule.anomalySignals ?? [])], tags: rule.tags?.length > 0 ? [...rule.tags] : [], groupBy: rule.groupBy ? [...rule.groupBy] : [], signalType: rule.signalType, @@ -489,7 +525,9 @@ export function deriveTierIssues(form: RuleFormState): string[] { issues.push("Critical recovery past warning") } recoveryIssue("Critical recovery value", escalation.threshold, escalation.recoveryValue) - if (escalation.destinationIds.length === 0) issues.push("Critical destinations") + // An anomaly tier may stay unrouted: the default rule ships that way. + if (escalation.destinationIds.length === 0 && form.signalType !== "anomaly") + issues.push("Critical destinations") } if (Str.trim(form.breachesOutOf).length > 0) { const outOf = Number(form.breachesOutOf) @@ -560,6 +598,7 @@ export function buildRuleCreateParamsV2(form: RuleFormState): V2AlertRuleCreateP ? [] : Arr.filter(form.excludeServiceNames, (s) => Str.trim(s).length > 0), environments: queryOwnsScope ? [] : Arr.filter(form.environments, (s) => Str.trim(s).length > 0), + ...(signalType === "anomaly" ? { anomaly_signals: Arr.dedupe(form.anomalySignals) } : undefined), group_by: queryOwnsScope ? null : form.groupBy.length > 0 ? form.groupBy : null, signal_type: signalType, comparator: form.comparator, diff --git a/packages/web-data/src/alerts/incident-timeline.ts b/packages/web-data/src/alerts/incident-timeline.ts index 659c371716..f9546ff4a2 100644 --- a/packages/web-data/src/alerts/incident-timeline.ts +++ b/packages/web-data/src/alerts/incident-timeline.ts @@ -122,7 +122,13 @@ export function conditionPhrase(input: { } /** The incident's group, or null for an ungrouped rule. */ -export function incidentGroup(incident: Pick): string | null { +export function incidentGroup( + incident: Pick & + Partial>, +): string | null { + // An anomaly's key is the anomaly incident id: its stored label is what people read. + if (incident.groupLabel != null) return incident.groupLabel + if (incident.signalType === "anomaly") return null return incident.groupKey == null || incident.groupKey === UNGROUPED_GROUP_KEY ? null : incident.groupKey } @@ -242,7 +248,9 @@ export function buildIncidentTimeline(input: { ? `${incident.ruleName} went down` : incident.signalType === "slo_burn_rate" ? `SLO burning its error budget at ${formatSignalValue(incident.signalType, incident.threshold)} or more` - : `${signalTitle(incident.signalType)} ${conditionPhrase(incident)}` + : incident.signalType === "anomaly" + ? "Maple detected an anomaly" + : `${signalTitle(incident.signalType)} ${conditionPhrase(incident)}` const opened: IncidentTimelineEntry = { key: "opened", diff --git a/packages/web-data/src/alerts/overview-derive.test.ts b/packages/web-data/src/alerts/overview-derive.test.ts index 51ffb59565..8ccb5cf779 100644 --- a/packages/web-data/src/alerts/overview-derive.test.ts +++ b/packages/web-data/src/alerts/overview-derive.test.ts @@ -181,12 +181,12 @@ describe("incident lines", () => { resolvedAt: null, ...(groupLabel === undefined ? undefined : { groupLabel }), }) - expect(incidentTitle(anomaly("checkout (production)"))).toBe( - "Anomaly Critical on checkout (production)", - ) + expect(incidentTitle(anomaly("checkout (production)"))).toBe("Anomaly on checkout (production)") expect(incidentScope(anomaly("checkout (production)"))).toBe("checkout (production)") - expect(incidentTitle(anomaly())).toBe("Anomaly Critical") + expect(incidentTitle(anomaly())).toBe("Anomaly") expect(incidentScope(anomaly())).toBeNull() + // The severity is the reading; the detector's threshold is not the rule's to show. + expect(incidentValues(anomaly())).toEqual({ now: "Critical", threshold: null }) }) it("names a raw SQL incident by its rule, with the group under it", () => { @@ -348,3 +348,21 @@ describe("ruleConditionSummary", () => { expect(ruleConditionSummary(rule)).toBe("error rate > 5.0% · 5m") }) }) + +describe("anomaly rules in the rules list", () => { + it("reads an anomaly rule's scope instead of a threshold, all of it when unscoped", () => { + expect(ruleConditionSummary(makeRule({ signalType: "anomaly" }))).toBe( + "Anomaly · all services · all environments · all signals", + ) + expect( + ruleConditionSummary( + makeRule({ + signalType: "anomaly", + serviceNames: ["checkout"], + environments: ["production"], + anomalySignals: ["error_rate", "latency_p95"], + }), + ), + ).toBe("Anomaly · checkout · production · Error rate, p95 latency") + }) +}) diff --git a/packages/web-data/src/alerts/overview-derive.ts b/packages/web-data/src/alerts/overview-derive.ts index 0cdc56f594..21de7895ef 100644 --- a/packages/web-data/src/alerts/overview-derive.ts +++ b/packages/web-data/src/alerts/overview-derive.ts @@ -8,6 +8,7 @@ import { isRangeComparator, signalLabels, } from "./form-utils" +import { anomalyScopeSummary, anomalySeverityLabel } from "./anomaly-rule" import { incidentGroup } from "./incident-timeline" import { type DerivedRuleStatus, needsAttention } from "./rule-status" import { Array as Arr, String as Str } from "effect" @@ -25,6 +26,8 @@ export function ruleBucket(derived: DerivedRuleStatus | undefined): RuleBucket { /** "Error rate > 5% · 5m · per service.name · checkout" */ export function ruleConditionSummary(rule: AlertRuleDocument): string { + // An anomaly rule has no threshold of its own: what it covers is the whole condition. + if (rule.signalType === "anomaly") return `Anomaly · ${anomalyScopeSummary(rule)}` // Builder rules format in the display unit (5%, not the stored 0.05) and name their aggregation. const display = displaySignalType({ signalType: rule.signalType, @@ -73,6 +76,10 @@ export function incidentTitle(incident: IncidentLine): string { if (isSlo(incident)) return `${sloNameOf(incident.ruleName)} ${incident.status === "resolved" ? "stopped burning its error budget" : "is burning its error budget"}` if (isQueryIncident(incident)) return incident.ruleName + if (incident.signalType === "anomaly") { + const group = shownGroup(incident) + return group === null ? "Anomaly" : `Anomaly on ${group}` + } const value = formatSignalValue(incident.signalType, incident.lastObservedValue) const base = `${signalLabels[incident.signalType]} ${value}` const group = shownGroup(incident) @@ -95,9 +102,12 @@ export function incidentSubline(incident: IncidentLine): string { export const NO_THRESHOLD = "–" /** The Now / threshold cells: an uptime incident is "Down" (or "Up") with no threshold. */ -export function incidentValues(incident: IncidentLine): Readonly<{ now: string; threshold: string }> { +export function incidentValues(incident: IncidentLine): Readonly<{ now: string; threshold: string | null }> { if (isUptime(incident)) return { now: incident.status === "resolved" ? "Up" : "Down", threshold: NO_THRESHOLD } + // An anomaly's threshold is the detector's, not the rule's: its severity is the whole reading. + if (incident.signalType === "anomaly") + return { now: anomalySeverityLabel(incident.lastObservedValue), threshold: null } return { now: formatSignalValue(incident.signalType, incident.lastObservedValue), threshold: formatSignalValue(incident.signalType, incident.threshold), diff --git a/packages/web-data/src/alerts/preview-insights.ts b/packages/web-data/src/alerts/preview-insights.ts index 2d697f7ee7..c4dcbae5b0 100644 --- a/packages/web-data/src/alerts/preview-insights.ts +++ b/packages/web-data/src/alerts/preview-insights.ts @@ -196,6 +196,12 @@ export function diffRuleForms(saved: RuleFormState, draft: RuleFormState): RuleF after: list(b.environments), same: list(a.environments) === list(b.environments), }, + { + field: "Signals", + before: list(a.anomaly_signals), + after: list(b.anomaly_signals), + same: list(a.anomaly_signals) === list(b.anomaly_signals), + }, { field: "Split by", before: list(a.group_by), diff --git a/packages/web-data/src/alerts/rule-modes.ts b/packages/web-data/src/alerts/rule-modes.ts index ad98a2a8e0..73eea6c341 100644 --- a/packages/web-data/src/alerts/rule-modes.ts +++ b/packages/web-data/src/alerts/rule-modes.ts @@ -2,14 +2,25 @@ import type { AlertSignalType } from "@maple/domain/http" import { normalizeKey, parseWhereClause, quoteWhereValue, splitCsv } from "@maple/domain/where-clause" import { createQueryDraft, type QueryBuilderQueryDraft } from "@maple/query-engine/query-builder" -import { domainThresholdToForm, formThresholdToDomain, type RuleFormState } from "./form-utils" +import { + anomalyRuleForm, + defaultRuleForm, + domainThresholdToForm, + formThresholdToDomain, + type RuleFormState, +} from "./form-utils" import { Array as Arr, String as Str } from "effect" -/** The three ways to write a rule's query. Built-in covers the five canned trace signals. */ -export type SignalKind = "builtin" | "builder_query" | "raw_query" +/** + * The ways to write a rule. Built-in covers the five canned trace signals; Anomaly pages on what + * the anomaly detector finds and has no query. + */ +export type SignalKind = "builtin" | "builder_query" | "raw_query" | "anomaly" export const signalTypeToKind = (signalType: AlertSignalType): SignalKind => - signalType === "builder_query" || signalType === "raw_query" ? signalType : "builtin" + signalType === "builder_query" || signalType === "raw_query" || signalType === "anomaly" + ? signalType + : "builtin" /** * The query-builder aggregation each built-in signal evaluates. Mirrors the @@ -143,11 +154,41 @@ export interface KindSwitch { * on Built-in stays 5% in the builder and becomes 0.05 in SQL. */ export function switchSignalKind(form: RuleFormState, next: SignalKind): KindSwitch { - if (next === signalTypeToKind(form.signalType)) return { form, notice: null } + const current = signalTypeToKind(form.signalType) + if (next === current) return { form, notice: null } + if (next === "anomaly") return { form: anomalyRuleForm(form), notice: null } + if (current === "anomaly") { + const left = leaveAnomaly(form) + return next === "builtin" ? { form: left, notice: null } : moveToKind(left, next) + } const moved = moveToKind(form, next) return { form: carryThreshold(form, moved.form), notice: moved.notice } } +/** An anomaly rule's pinned tiers and timing mean nothing to a query: it starts from error rate. */ +function leaveAnomaly(form: RuleFormState): RuleFormState { + const fresh = defaultRuleForm() + return { + ...form, + signalType: "error_rate", + anomalySignals: [], + severity: fresh.severity, + comparator: fresh.comparator, + threshold: fresh.threshold, + thresholdUpper: fresh.thresholdUpper, + recoveryValue: fresh.recoveryValue, + escalation: null, + windowMinutes: fresh.windowMinutes, + evaluationIntervalMinutes: fresh.evaluationIntervalMinutes, + minimumSampleCount: fresh.minimumSampleCount, + consecutiveBreachesRequired: fresh.consecutiveBreachesRequired, + consecutiveHealthyRequired: fresh.consecutiveHealthyRequired, + renotifyIntervalMinutes: fresh.renotifyIntervalMinutes, + // Both tiers' destinations stay where people expect them: on the one remaining tier. + destinationIds: Arr.dedupe([...form.destinationIds, ...(form.escalation?.destinationIds ?? [])]), + } +} + function moveToKind(form: RuleFormState, next: SignalKind): KindSwitch { const current = signalTypeToKind(form.signalType) if (next === "raw_query") return { form: { ...form, signalType: "raw_query" }, notice: null } diff --git a/packages/web-data/src/alerts/rule-status.ts b/packages/web-data/src/alerts/rule-status.ts index c77c521719..7b7113da0a 100644 --- a/packages/web-data/src/alerts/rule-status.ts +++ b/packages/web-data/src/alerts/rule-status.ts @@ -1,3 +1,4 @@ +import { isDefaultAnomalyRule } from "./anomaly-rule" import { ALERT_MAX_EVALUATION_INTERVAL_MINUTES, alertRuleIntervalMinutes } from "@maple/query-engine/datetime" import type { AlertDeliveryEventDocument, AlertIncidentDocument, AlertRuleDocument } from "@maple/domain/http" import type { AlertRuleStateRow } from "../collections/alerts" @@ -93,7 +94,8 @@ export function deriveRuleStatus(input: { const latestDelivery = deliveryEvents[0] ?? null const attention: RuleAttention = { - noDestinations: rule.enabled && rule.destinationIds.length === 0, + // Anomaly paging is opt-in: the default anomaly rule starts unrouted and that is normal. + noDestinations: rule.enabled && rule.destinationIds.length === 0 && !isDefaultAnomalyRule(rule), recentDeliveryFailure: latestDelivery?.status === "failed", slowedDown: rule.enabled && rule.slowedIntervalMinutes !== null, } From d32578eeba0c3f0e684305fb584a7d61c14c6558 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 19:50:06 +0100 Subject: [PATCH 04/28] fix(alerts): review fixes for anomaly rules - MCP says an anomaly rule has no query window to preview instead of reporting a preview that returned no series. - iOS shows an anomaly incident's value as Warning or Critical. - Anomaly rules reject excludeServiceNames rather than dropping it. - The default-rule guard reads the default rule under the write lock for every rule write. - Anomaly rules never take the orphan path, so a rule disabled mid-tick cannot send its incidents to a warehouse probe. --- apps/ai/src/mcp/lib/alert-rule-evaluation.ts | 7 +++++- apps/ai/src/mcp/lib/alert-rules.ts | 4 +++- .../mcp/tools/__tests__/alert-tools.test.ts | 5 +++- .../Maple/Components/AlertFormatting.swift | 7 ++++-- .../src/services/alerts/AlertRuleModel.ts | 3 +++ .../src/services/alerts/AlertRulesService.ts | 15 ++++-------- .../src/services/alerts/AlertsService.ts | 24 +++++++++++-------- .../alerts/anomaly/anomaly-rules.test.ts | 12 +++++++++- packages/domain/src/mcp-outputs/alerts.ts | 2 ++ 9 files changed, 52 insertions(+), 27 deletions(-) diff --git a/apps/ai/src/mcp/lib/alert-rule-evaluation.ts b/apps/ai/src/mcp/lib/alert-rule-evaluation.ts index 3c9001f0cd..16ac4f4665 100644 --- a/apps/ai/src/mcp/lib/alert-rule-evaluation.ts +++ b/apps/ai/src/mcp/lib/alert-rule-evaluation.ts @@ -71,7 +71,12 @@ export const firstEvaluation = Effect.fn("McpTool.alertFirstEvaluation")(functio : null // An anomaly rule reads the detector's open incidents, not a query, so there is nothing to preview. if (rule.signalType === "anomaly") { - const evaluation: typeof AlertRuleFirstEvaluation.Type = { nextEvaluationAt, current: [] } + const evaluation: typeof AlertRuleFirstEvaluation.Type = { + nextEvaluationAt, + current: [], + previewSkipped: + "An anomaly rule pages on the anomaly detector's open anomalies, so there is no query window to preview.", + } return { evaluation, warnings: noWarnings } } const lookbackMs = Math.max(rule.windowMinutes * 6, 60) * 60_000 diff --git a/apps/ai/src/mcp/lib/alert-rules.ts b/apps/ai/src/mcp/lib/alert-rules.ts index 46ce882294..d0c2ba458c 100644 --- a/apps/ai/src/mcp/lib/alert-rules.ts +++ b/apps/ai/src/mcp/lib/alert-rules.ts @@ -216,7 +216,9 @@ const evaluationBlocks = ( ? "The rule is disabled, so the scheduler will not evaluate it." : `First scheduled evaluation by ${Str.slice(0, 19)(evaluation.nextEvaluationAt)}Z (every ${intervalMinutes}m after), no need to poll list_alert_checks before then.` const now = - evaluation.previewError !== undefined + evaluation.previewSkipped !== undefined + ? evaluation.previewSkipped + : evaluation.previewError !== undefined ? `A preview of the latest window failed: ${evaluation.previewError}. Run preview_alert_rule to see what it observes.` : evaluation.current.length === 0 ? "A preview of the latest window returned no series." diff --git a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts index ee6b7aefa1..b17299dd48 100644 --- a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts +++ b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts @@ -556,7 +556,7 @@ describe("alert tools", () => { it("create_alert_rule takes an anomaly rule without comparator or threshold", async () => { const seen: Seen = {} - await ok( + const result = await ok( "create_alert_rule", { name: "Checkout anomalies", @@ -574,6 +574,9 @@ describe("alert tools", () => { serviceNames: ["checkout"], anomalySignals: ["error_rate", "latency_p95"], }) + // No query window exists, so the model is told why instead of "returned no series". + expect(text(result)).toContain("there is no query window to preview") + expect(text(result)).not.toContain("returned no series") }) it("update_alert_rule replaces an anomaly rule's signals only when asked", () => { diff --git a/apps/ios/Maple/Components/AlertFormatting.swift b/apps/ios/Maple/Components/AlertFormatting.swift index a1c7cf9642..3e63f24353 100644 --- a/apps/ios/Maple/Components/AlertFormatting.swift +++ b/apps/ios/Maple/Components/AlertFormatting.swift @@ -17,6 +17,8 @@ enum SignalUnit: Codable { case plain /// An SLO burn rate: a multiple of the sustainable rate, shown as `14.4x` like the web. case burnRate + /// An anomaly rule's severity code: 2 critical, 1 warning, 0 normal, shown as the word like the web. + case anomalySeverity func format(_ value: Double?) -> String { guard let value, value.isFinite else { return Format.placeholder } @@ -27,6 +29,7 @@ enum SignalUnit: Codable { case .apdex: return String(format: "%.2f", value) case .count, .plain: return Format.count(value) case .burnRate: return String(format: "%.1fx", value) + case .anomalySeverity: return value >= 2 ? "Critical" : value >= 1 ? "Warning" : "Normal" } } } @@ -51,8 +54,8 @@ struct SignalDisplay: Codable { case .uptime: self.init(label: "Uptime check", unit: .plain) // An SLO's hidden rule: the value is how many times faster than sustainable the budget burns. case .sloBurnRate: self.init(label: "SLO burn rate", unit: .burnRate) - // An anomaly rule: the value is a severity code (1 warning, 2 critical), read as a plain number. - case .anomaly: self.init(label: "Anomaly", unit: .plain) + // An anomaly rule: the value is a severity code, shown as Warning or Critical. + case .anomaly: self.init(label: "Anomaly", unit: .anomalySeverity) } } diff --git a/packages/backend/src/services/alerts/AlertRuleModel.ts b/packages/backend/src/services/alerts/AlertRuleModel.ts index 23f8cb20c2..cb540f95b3 100644 --- a/packages/backend/src/services/alerts/AlertRuleModel.ts +++ b/packages/backend/src/services/alerts/AlertRuleModel.ts @@ -929,6 +929,9 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { const comparator = anomaly ? ANOMALY_RULE_DEFAULTS.comparator : request.comparator const details: string[] = [] if (name.length === 0 && !options?.forPreview) details.push("name is required") + if (anomaly && Arr.some(request.excludeServiceNames ?? [], (service) => Str.trim(service).length > 0)) { + details.push("excludeServiceNames is not supported for anomaly alerts; scope by serviceNames instead") + } if (!anomaly && request.anomalySignals !== undefined && request.anomalySignals.length > 0) { details.push("anomalySignals is only supported for anomaly alerts") } diff --git a/packages/backend/src/services/alerts/AlertRulesService.ts b/packages/backend/src/services/alerts/AlertRulesService.ts index 8e733a9901..effec6c530 100644 --- a/packages/backend/src/services/alerts/AlertRulesService.ts +++ b/packages/backend/src/services/alerts/AlertRulesService.ts @@ -191,8 +191,6 @@ export const makeAlertRulePersistence = (options: { userId: UserId, existingId: AlertRuleId | null, request: AlertRuleUpsertRequest, - /** The stored rule's signal type on an edit; null on create. */ - previousSignalType: string | null = null, ) { const normalized = yield* normalizeRule(orgId, request) const ruleId = existingId ?? normalized.id @@ -273,13 +271,9 @@ export const makeAlertRulePersistence = (options: { }) } } - // One default anomaly rule per org, kept enabled and unscoped while it exists. - // Only an edit that is or was an anomaly rule can touch it. - const touchesAnomaly = - normalized.signalType === "anomaly" || previousSignalType === "anomaly" - const defaultAnomalyRule = touchesAnomaly - ? yield* findDefaultAnomalyRule(db, orgId) - : Option.none() + // One default anomaly rule per org, kept enabled and unscoped while it exists. Read + // under the lock for every write, so a concurrent edit cannot turn this row into it unseen. + const defaultAnomalyRule = yield* findDefaultAnomalyRule(db, orgId) const becomesDefault = normalized.signalType === "anomaly" && isDefaultAnomalyScope(normalized) // A disabled unscoped rule would hold the default slot and block the backfill. @@ -413,9 +407,8 @@ export const makeAlertRulePersistence = (options: { userId: UserId, existingId: AlertRuleId, request: AlertRuleUpsertRequest, - previousSignalType: string | null = null, ) { - const { ruleId, txid } = yield* writeRuleRow(orgId, userId, existingId, request, previousSignalType) + const { ruleId, txid } = yield* writeRuleRow(orgId, userId, existingId, request) const row = yield* findRuleRow(orgId, ruleId) if (row === undefined) { return yield* new AlertPersistenceError({ diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index f2108ae32e..5455396ab5 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -1660,7 +1660,7 @@ export class AlertsService extends Context.Service r.groupKey)) - yield* resolveOrphanedGroupIncidents( - row.orgId, - normalized.id, - normalized, - evaluatedGroups, - timestamp, - prefetch.openIncidentsForRule(row.orgId, row.id), - pushBudget, - ) + // An anomaly rule's source reports every open incident it still owns, so a missing + // group was closed elsewhere; never probe the warehouse for it. + if (normalized.signalType !== "anomaly") { + yield* resolveOrphanedGroupIncidents( + row.orgId, + normalized.id, + normalized, + evaluatedGroups, + timestamp, + prefetch.openIncidentsForRule(row.orgId, row.id), + pushBudget, + ) + } // Self-heal state rows whose key shape contradicts the rule's // groupedness: a grouped rule can never legitimately own an diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts index 591b7dce83..acbf380903 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts @@ -65,7 +65,6 @@ describe("anomaly rule normalization", () => { serviceNames: ["checkout"], environments: ["production"], anomalySignals: ["error_rate", "error_rate", "latency_p95"], - excludeServiceNames: ["cart"], thresholds: [ { severity: "warning", @@ -156,6 +155,17 @@ describe("anomaly rule normalization", () => { assert.include(error.message, "anomalySignals is only supported for anomaly alerts") }), ) + + it.effect("rejects excluded services, which anomaly scope cannot honour", () => + Effect.gen(function* () { + const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) + const error = yield* Effect.flip( + normalizeRule(ORG, anomalyRequest({ excludeServiceNames: ["checkout"] }), { forPreview: true }), + ) + assert.strictEqual(error._tag, "@maple/http/errors/AlertValidationError") + assert.include(error.message, "excludeServiceNames is not supported for anomaly alerts") + }), + ) }) describe("default anomaly rule", () => { diff --git a/packages/domain/src/mcp-outputs/alerts.ts b/packages/domain/src/mcp-outputs/alerts.ts index 86e0d8cbf2..098fa65621 100644 --- a/packages/domain/src/mcp-outputs/alerts.ts +++ b/packages/domain/src/mcp-outputs/alerts.ts @@ -81,6 +81,8 @@ export const AlertRuleFirstEvaluation = Schema.Struct({ ), /** Set when the preview could not run; the rule saved regardless. */ previewError: Schema.optionalKey(Schema.String), + /** Set when the rule has nothing to preview, e.g. an anomaly rule. */ + previewSkipped: Schema.optionalKey(Schema.String), }) const FirstEvaluation = Schema.optionalKey(AlertRuleFirstEvaluation) From ba6f1199ed4df3caaa6f7562b7d26a13198f8917 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 19:50:39 +0100 Subject: [PATCH 05/28] style(alerts): format review fixes --- apps/ai/src/mcp/lib/alert-rules.ts | 8 ++++---- packages/backend/src/services/alerts/AlertRuleModel.ts | 9 +++++++-- .../src/services/alerts/anomaly/anomaly-rules.test.ts | 4 +++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/ai/src/mcp/lib/alert-rules.ts b/apps/ai/src/mcp/lib/alert-rules.ts index d0c2ba458c..475025366d 100644 --- a/apps/ai/src/mcp/lib/alert-rules.ts +++ b/apps/ai/src/mcp/lib/alert-rules.ts @@ -219,10 +219,10 @@ const evaluationBlocks = ( evaluation.previewSkipped !== undefined ? evaluation.previewSkipped : evaluation.previewError !== undefined - ? `A preview of the latest window failed: ${evaluation.previewError}. Run preview_alert_rule to see what it observes.` - : evaluation.current.length === 0 - ? "A preview of the latest window returned no series." - : undefined + ? `A preview of the latest window failed: ${evaluation.previewError}. Run preview_alert_rule to see what it observes.` + : evaluation.current.length === 0 + ? "A preview of the latest window returned no series." + : undefined return [ doc.text(now === undefined ? when : `${when}\n${now}`), ...(evaluation.current.length === 0 diff --git a/packages/backend/src/services/alerts/AlertRuleModel.ts b/packages/backend/src/services/alerts/AlertRuleModel.ts index cb540f95b3..162b76d3e0 100644 --- a/packages/backend/src/services/alerts/AlertRuleModel.ts +++ b/packages/backend/src/services/alerts/AlertRuleModel.ts @@ -929,8 +929,13 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { const comparator = anomaly ? ANOMALY_RULE_DEFAULTS.comparator : request.comparator const details: string[] = [] if (name.length === 0 && !options?.forPreview) details.push("name is required") - if (anomaly && Arr.some(request.excludeServiceNames ?? [], (service) => Str.trim(service).length > 0)) { - details.push("excludeServiceNames is not supported for anomaly alerts; scope by serviceNames instead") + if ( + anomaly && + Arr.some(request.excludeServiceNames ?? [], (service) => Str.trim(service).length > 0) + ) { + details.push( + "excludeServiceNames is not supported for anomaly alerts; scope by serviceNames instead", + ) } if (!anomaly && request.anomalySignals !== undefined && request.anomalySignals.length > 0) { details.push("anomalySignals is only supported for anomaly alerts") diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts index acbf380903..80f1771d69 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts @@ -160,7 +160,9 @@ describe("anomaly rule normalization", () => { Effect.gen(function* () { const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) const error = yield* Effect.flip( - normalizeRule(ORG, anomalyRequest({ excludeServiceNames: ["checkout"] }), { forPreview: true }), + normalizeRule(ORG, anomalyRequest({ excludeServiceNames: ["checkout"] }), { + forPreview: true, + }), ) assert.strictEqual(error._tag, "@maple/http/errors/AlertValidationError") assert.include(error.message, "excludeServiceNames is not supported for anomaly alerts") From 5cd792c7151672aedf55f7c27f125e45e88ab302 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 20:40:17 +0100 Subject: [PATCH 06/28] fix(alerts): only Maple creates the default anomaly rule --- .../src/services/alerts/AlertRuleModel.ts | 13 +- .../src/services/alerts/AlertRulesService.ts | 27 +- .../alerts/AlertsService.anomaly.test.ts | 15 +- .../services/alerts/anomaly/anomaly-rule.ts | 8 +- .../alerts/anomaly/anomaly-rules.test.ts | 244 +++++++++++------- 5 files changed, 184 insertions(+), 123 deletions(-) diff --git a/packages/backend/src/services/alerts/AlertRuleModel.ts b/packages/backend/src/services/alerts/AlertRuleModel.ts index 162b76d3e0..ced7c86b26 100644 --- a/packages/backend/src/services/alerts/AlertRuleModel.ts +++ b/packages/backend/src/services/alerts/AlertRuleModel.ts @@ -49,7 +49,7 @@ import { DateTime, Array as Arr, Effect, Match, Result, Schema, String as Str } import type { AlertRuntimeApi } from "./AlertRuntime" import type { QueryBuilderDataSource } from "@maple/query-model" import { validateBreachWindow, validateThresholds } from "./alert-thresholds" -import { ANOMALY_RULE_DEFAULTS, anomalySeverityCode } from "./anomaly/anomaly-rule" +import { ANOMALY_RULE_DEFAULTS, anomalySeverityCode, isDefaultAnomalyScope } from "./anomaly/anomaly-rule" const StringArraySchema = Schema.Array(Schema.String) @@ -987,6 +987,17 @@ export const makeAlertRuleNormalizer = (runtime: AlertRuntimeApi) => { requireDestinations: !options?.forPreview && !anomaly, }), ) + // Only the unscoped default may stay unrouted; a scoped rule exists to page someone. + const scoped = !isDefaultAnomalyScope({ serviceNames, environments, anomalySignals }) + if ( + anomaly && + scoped && + (request.enabled ?? true) && + !options?.forPreview && + Arr.every(requestedThresholds, (tier) => tier.destinationIds.length === 0) + ) { + details.push("A scoped anomaly rule needs a destination on at least one tier") + } } const consecutiveBreachesRequired = anomaly ? ANOMALY_RULE_DEFAULTS.consecutiveBreachesRequired diff --git a/packages/backend/src/services/alerts/AlertRulesService.ts b/packages/backend/src/services/alerts/AlertRulesService.ts index effec6c530..41d79581e1 100644 --- a/packages/backend/src/services/alerts/AlertRulesService.ts +++ b/packages/backend/src/services/alerts/AlertRulesService.ts @@ -47,8 +47,8 @@ import { tierConditionChanged } from "./alert-thresholds" import { lockOrgAlertRules, makePersistenceError } from "./alert-persistence" import { AlertRuntime, type AlertRuntimeApi } from "./AlertRuntime" import { - DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE, - DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, + DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE, + DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, isDefaultAnomalyScope, storedAnomalyScope, @@ -274,24 +274,17 @@ export const makeAlertRulePersistence = (options: { // One default anomaly rule per org, kept enabled and unscoped while it exists. Read // under the lock for every write, so a concurrent edit cannot turn this row into it unseen. const defaultAnomalyRule = yield* findDefaultAnomalyRule(db, orgId) - const becomesDefault = - normalized.signalType === "anomaly" && isDefaultAnomalyScope(normalized) - // A disabled unscoped rule would hold the default slot and block the backfill. - if (becomesDefault && !normalized.enabled) { - return yield* makeAlertValidationError( - Option.isSome(defaultAnomalyRule) && defaultAnomalyRule.value.id === existingId - ? DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE - : DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, - ) - } - if (Option.isSome(defaultAnomalyRule)) { - const isTheDefault = defaultAnomalyRule.value.id === existingId - if (isTheDefault && !(becomesDefault && normalized.enabled)) { + // Only the backfill creates the default; it stays an enabled, unscoped anomaly rule. + const isTheDefault = Option.exists(defaultAnomalyRule, (rule) => rule.id === existingId) + if (isTheDefault) { + if (!normalized.enabled || normalized.signalType !== "anomaly") { return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) } - if (!isTheDefault && becomesDefault) { - return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE) + if (!isDefaultAnomalyScope(normalized)) { + return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE) } + } else if (normalized.signalType === "anomaly" && isDefaultAnomalyScope(normalized)) { + return yield* makeAlertValidationError(DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE) } if (normalized.enabled) { const activeRows = yield* db.run( diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts index 766669ff19..dc3869df6f 100644 --- a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts +++ b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts @@ -222,7 +222,11 @@ describe("AlertsService anomaly rules", () => { orgId, userId, adminRoles, - anomalyRule({ name: "Anomalies", thresholds: criticalRouted(destination.id) }), + anomalyRule({ + name: "Checkout anomalies", + serviceNames: ["checkout"], + thresholds: criticalRouted(destination.id), + }), ) yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) @@ -266,7 +270,11 @@ describe("AlertsService anomaly rules", () => { orgId, userId, adminRoles, - anomalyRule({ name: "Anomalies", thresholds: criticalRouted(destination.id) }), + anomalyRule({ + name: "Checkout anomalies", + serviceNames: ["checkout"], + thresholds: criticalRouted(destination.id), + }), ) yield* insertAnomaly(errorRateSpike) @@ -434,6 +442,7 @@ describe("AlertsService anomaly rules", () => { return Effect.gen(function* () { yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) yield* insertAnomaly({ id: "00000000-0000-4000-8000-00000000a003", @@ -456,7 +465,7 @@ describe("AlertsService anomaly rules", () => { value: 1, upper: null, recoveryValue: null, - destinationIds: [], + destinationIds: [destination.id], }, ], }), diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts index 25a3cd8691..c15cba56b6 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -31,10 +31,10 @@ export const DEFAULT_ANOMALY_RULE_NAME = "Anomaly detection" /** Used when a user already took the default name for another rule. */ export const DEFAULT_ANOMALY_RULE_FALLBACK_NAME = "Anomaly detection (default)" -export const DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE = - "An org has one default anomaly rule; scope this rule by service, environment or signal" -export const DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE = - "The default anomaly rule must stay enabled; turn anomaly detection off in anomaly settings instead" +export const DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE = + "Maple creates the default anomaly rule; scope this rule by service, environment or signal" +export const DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE = + "The default anomaly rule covers every anomaly and cannot be scoped; create a scoped anomaly rule instead" export const DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE = "The default anomaly rule cannot be deleted or disabled; turn anomaly detection off in anomaly settings instead" diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts index 80f1771d69..72e55e47a9 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rules.test.ts @@ -7,16 +7,19 @@ import { RoleName, UserId, } from "@maple/domain/http" -import { Array as Arr, Effect, Option, Schema, String as Str } from "effect" +import { Array as Arr, Effect, Schema, String as Str } from "effect" +import * as PG from "@maple-dev/effect-orm/postgres" +import { AlertRules } from "@maple/db/tables" import { Database } from "@maple/backend/platform/DatabaseLive" import { cleanupTestDbs, createTestDb, executeSql, type TestDb } from "@maple/backend/platform/test-pglite" import { makeAlertRuleNormalizer } from "../AlertRuleModel" import { makeAlertRulePersistence } from "../AlertRulesService" import type { AlertRuntimeApi } from "../AlertRuntime" import { - DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE, - DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, + DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE, + DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE, + defaultAnomalyRuleFields, } from "./anomaly-rule" const asOrgId = Schema.decodeUnknownSync(OrgId) @@ -28,6 +31,7 @@ const ORG = asOrgId("org_anomaly_rules") const USER = asUserId("user_anomaly_rules") const ADMIN_ROLES = [asRoleName("org:admin")] const NOW = Date.parse("2026-11-06T12:00:00.000Z") +const DEFAULT_RULE_ID = asRuleId("00000000-0000-4000-8000-0000000000aa") const DESTINATION_ID = Schema.decodeSync(AlertDestinationId)("00000000-0000-4000-8000-0000000000d1") const createdDbs: TestDb[] = [] @@ -78,7 +82,7 @@ describe("anomaly rule normalization", () => { value: 90, upper: null, recoveryValue: null, - destinationIds: [], + destinationIds: [DESTINATION_ID], }, ], consecutiveBreachesRequired: 4, @@ -120,15 +124,26 @@ describe("anomaly rule normalization", () => { }), ) - it.effect("accepts tiers with no destinations, so an unrouted rule only shows in the app", () => + it.effect("lets only the unscoped default go unrouted, and a disabled scoped rule", () => Effect.gen(function* () { const { normalizeRule } = makeAlertRuleNormalizer(yield* makeRuntime) - const rule = yield* normalizeRule(ORG, anomalyRequest({ severity: "critical" })) - assert.deepStrictEqual(rule.destinationIds, []) + const unscoped = yield* normalizeRule(ORG, anomalyRequest({ severity: "critical" })) + assert.deepStrictEqual(unscoped.destinationIds, []) assert.deepStrictEqual( - Arr.map(rule.thresholds, (tier) => [tier.severity, tier.value]), + Arr.map(unscoped.thresholds, (tier) => [tier.severity, tier.value]), [["critical", 2]], ) + + const scoped = anomalyRequest({ severity: "critical", serviceNames: ["checkout"] }) + const refused = yield* Effect.flip(normalizeRule(ORG, scoped)) + assert.include(refused.message, "A scoped anomaly rule needs a destination on at least one tier") + const paused = yield* normalizeRule( + ORG, + new AlertRuleUpsertRequest({ ...scoped, enabled: false }), + ) + assert.isFalse(paused.enabled) + // A preview of the same rule is not held to it. + yield* normalizeRule(ORG, scoped, { forPreview: true }) }), ) @@ -176,43 +191,75 @@ describe("default anomaly rule", () => { return makeAlertRulePersistence({ database, runtime: yield* makeRuntime }) }) - it.effect("allows one unscoped anomaly rule per org, and any number of scoped ones", () => { + /** The default rule as production makes it: the backfill's row, never `createRule`. */ + const seedDefaultRule = Effect.gen(function* () { + const database = yield* Database + yield* database.execute((db) => + db.run( + PG.insertInto(AlertRules).values({ + ...defaultAnomalyRuleFields(ORG), + id: DEFAULT_RULE_ID, + lastScheduledAt: null, + createdAt: NOW, + updatedAt: NOW, + createdBy: USER, + updatedBy: USER, + }), + ), + ) + }).pipe(Effect.orDie) + + const seedDestination = (testDb: TestDb) => + Effect.promise(() => + executeSql( + testDb, + `insert into alert_destinations (id, org_id, name, type, config_json, secret_ciphertext, secret_iv, secret_tag, created_at, updated_at, created_by, updated_by) + values ($1, $2, 'Hook', 'webhook', '{}'::jsonb, '', '', '', now(), now(), 'u', 'u')`, + [DESTINATION_ID, ORG], + ), + ) + + const routed = { destinationIds: [DESTINATION_ID] } + + it.effect("refuses an unscoped rule from the API, with or without a default already there", () => { const testDb = createTestDb(createdDbs) return Effect.gen(function* () { const rules = yield* makePersistence - const first = yield* rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Default" })) - assert.strictEqual(first.signalType, "anomaly") - assert.deepStrictEqual(first.anomalySignals, []) - - const second = yield* Effect.flip( - rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Second default" })), + const unscoped = (name: string, enabled = true) => + Effect.flip(rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name, enabled }))) + assert.strictEqual((yield* unscoped("Mine")).message, DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE) + assert.strictEqual( + (yield* unscoped("Paused", false)).message, + DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE, ) - assert.strictEqual(second._tag, "@maple/http/errors/AlertValidationError") - assert.strictEqual(second.message, DEFAULT_ANOMALY_RULE_CONFLICT_MESSAGE) + assert.deepStrictEqual((yield* rules.listRules(ORG)).rules, []) - const scoped = yield* rules.createRule( - ORG, - USER, - ADMIN_ROLES, - anomalyRequest({ name: "Latency only", anomalySignals: ["latency_p95"] }), - ) - const listed = yield* rules.listRules(ORG) - const stored = Arr.findFirst(listed.rules, (rule) => rule.id === scoped.id) - assert.deepStrictEqual(Option.getOrNull(Option.map(stored, (rule) => rule.anomalySignals)), [ - "latency_p95", - ]) + yield* seedDefaultRule + assert.strictEqual((yield* unscoped("Second")).message, DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE) }).pipe(Effect.provide(testDb.layer)) }) - it.effect("refuses a disabled unscoped rule, which would hold the default slot", () => { + it.effect("refuses editing a scoped rule into an unscoped one", () => { const testDb = createTestDb(createdDbs) return Effect.gen(function* () { + yield* seedDestination(testDb) const rules = yield* makePersistence - const error = yield* Effect.flip( - rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Paused", enabled: false })), + const scoped = yield* rules.createRule( + ORG, + USER, + ADMIN_ROLES, + anomalyRequest({ name: "Latency only", anomalySignals: ["latency_p95"], ...routed }), ) - assert.strictEqual(error.message, DEFAULT_ANOMALY_RULE_DISABLED_MESSAGE) - assert.deepStrictEqual((yield* rules.listRules(ORG)).rules, []) + assert.deepStrictEqual(scoped.anomalySignals, ["latency_p95"]) + const widened = yield* Effect.flip( + rules.upsertRuleRow( + ORG, + USER, + scoped.id, + anomalyRequest({ name: "Latency only", ...routed }), + ), + ) + assert.strictEqual(widened.message, DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE) }).pipe(Effect.provide(testDb.layer)) }) @@ -229,16 +276,9 @@ describe("default anomaly rule", () => { destinationIds: [DESTINATION_ID], }) return Effect.gen(function* () { - yield* Effect.promise(() => - executeSql( - testDb, - `insert into alert_destinations (id, org_id, name, type, config_json, secret_ciphertext, secret_iv, secret_tag, created_at, updated_at, created_by, updated_by) - values ($1, $2, 'Hook', 'webhook', '{}'::jsonb, '', '', '', now(), now(), 'u', 'u')`, - [DESTINATION_ID, ORG], - ), - ) + yield* seedDestination(testDb) const rules = yield* makePersistence - yield* rules.createRule(ORG, USER, ADMIN_ROLES, anomalyRequest({ name: "Default" })) + yield* seedDefaultRule const template = yield* rules.createRule(ORG, USER, ADMIN_ROLES, errorRateRule("r0")) // 98 more copies of it: 99 counted rules beside the default. yield* Effect.promise(() => @@ -258,66 +298,74 @@ describe("default anomaly rule", () => { }).pipe(Effect.provide(testDb.layer)) }) - it.effect("cannot be deleted, disabled or scoped, but other fields can change", () => { - const testDb = createTestDb(createdDbs) - return Effect.gen(function* () { - const rules = yield* makePersistence - const created = yield* rules.createRule( - ORG, - USER, - ADMIN_ROLES, - anomalyRequest({ name: "Default" }), - ) + it.effect( + "keeps the default enabled, unscoped and undeletable, but lets it be renamed and routed", + () => { + const testDb = createTestDb(createdDbs) + return Effect.gen(function* () { + yield* seedDestination(testDb) + yield* seedDefaultRule + const rules = yield* makePersistence + const edit = (fields: Partial[0]>) => + rules.upsertRuleRow( + ORG, + USER, + DEFAULT_RULE_ID, + anomalyRequest({ name: "Default", ...fields }), + ) - const deleted = yield* Effect.flip(rules.deleteRule(ORG, ADMIN_ROLES, created.id)) - assert.strictEqual(deleted._tag, "@maple/http/errors/AlertValidationError") - assert.strictEqual(deleted.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + const deleted = yield* Effect.flip(rules.deleteRule(ORG, ADMIN_ROLES, DEFAULT_RULE_ID)) + assert.strictEqual(deleted.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) + assert.strictEqual( + (yield* Effect.flip(edit({ enabled: false }))).message, + DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, + ) + assert.strictEqual( + (yield* Effect.flip( + rules.upsertRuleRow( + ORG, + USER, + DEFAULT_RULE_ID, + new AlertRuleUpsertRequest({ + name: "Default", + signalType: "error_rate", + comparator: "gt", + threshold: 0.05, + severity: "warning", + windowMinutes: 5, + ...routed, + }), + ), + )).message, + DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE, + ) + assert.strictEqual( + (yield* Effect.flip(edit({ serviceNames: ["api"], ...routed }))).message, + DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE, + ) - const disabled = yield* Effect.flip( - rules.upsertRuleRow( + const renamed = yield* edit({ name: "Everything anomalous", ...routed }) + assert.strictEqual(renamed.name, "Everything anomalous") + assert.isTrue(renamed.enabled) + assert.deepStrictEqual(renamed.destinationIds, [DESTINATION_ID]) + + // A scoped anomaly rule is an ordinary rule: it can be disabled and deleted. + const scopedRule = yield* rules.createRule( ORG, USER, - created.id, - anomalyRequest({ name: "Default", enabled: false }), - ), - ) - assert.strictEqual(disabled.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) - - const scoped = yield* Effect.flip( - rules.upsertRuleRow( + ADMIN_ROLES, + anomalyRequest({ name: "Checkout", serviceNames: ["checkout"], ...routed }), + ) + const paused = yield* rules.upsertRuleRow( ORG, USER, - created.id, - anomalyRequest({ name: "Default", serviceNames: ["api"] }), - ), - ) - assert.strictEqual(scoped.message, DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE) - - const renamed = yield* rules.upsertRuleRow( - ORG, - USER, - created.id, - anomalyRequest({ name: "Everything anomalous", renotifyIntervalMinutes: 60 }), - ) - assert.strictEqual(renamed.name, "Everything anomalous") - assert.isTrue(renamed.enabled) - - // A scoped anomaly rule is an ordinary rule: it can be disabled and deleted. - const scopedRule = yield* rules.createRule( - ORG, - USER, - ADMIN_ROLES, - anomalyRequest({ name: "Checkout", serviceNames: ["checkout"] }), - ) - const paused = yield* rules.upsertRuleRow( - ORG, - USER, - scopedRule.id, - anomalyRequest({ name: "Checkout", serviceNames: ["checkout"], enabled: false }), - ) - assert.isFalse(paused.enabled) - const gone = yield* rules.deleteRule(ORG, ADMIN_ROLES, scopedRule.id) - assert.strictEqual(gone.id, scopedRule.id) - }).pipe(Effect.provide(testDb.layer)) - }) + scopedRule.id, + anomalyRequest({ name: "Checkout", serviceNames: ["checkout"], enabled: false }), + ) + assert.isFalse(paused.enabled) + const gone = yield* rules.deleteRule(ORG, ADMIN_ROLES, scopedRule.id) + assert.strictEqual(gone.id, scopedRule.id) + }).pipe(Effect.provide(testDb.layer)) + }, + ) }) From 30aab2954ad60147fe0b50842aa5238359b6ae60 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 20:46:55 +0100 Subject: [PATCH 07/28] fix(web): anomaly editor fixes from the preview test --- apps/web/src/lab/anomaly-alerts-lab.ts | 43 ++++++++- apps/web/src/page/alerts/editor/actions.ts | 26 ++++-- apps/web/src/page/alerts/editor/derive.ts | 33 ++++--- apps/web/src/page/alerts/editor/dest-steps.ts | 16 +++- .../editor/editor-anomaly.scene.test.ts | 89 ++++++++++++++++++- apps/web/src/page/alerts/editor/form.ts | 4 + apps/web/src/page/alerts/editor/init.ts | 1 + apps/web/src/page/alerts/editor/message.ts | 4 +- apps/web/src/page/alerts/editor/model.ts | 4 +- .../src/page/alerts/editor/view-action-bar.ts | 7 +- .../src/page/alerts/editor/view-details.ts | 4 +- apps/web/src/page/alerts/editor/view-query.ts | 11 ++- apps/web/src/page/alerts/editor/view.ts | 10 ++- apps/web/src/route/redirect.ts | 45 ++++++---- apps/web/src/route/route.test.ts | 13 +++ apps/web/src/route/search.ts | 27 ++---- 16 files changed, 263 insertions(+), 74 deletions(-) diff --git a/apps/web/src/lab/anomaly-alerts-lab.ts b/apps/web/src/lab/anomaly-alerts-lab.ts index a88ff3a3bc..9d5879312d 100644 --- a/apps/web/src/lab/anomaly-alerts-lab.ts +++ b/apps/web/src/lab/anomaly-alerts-lab.ts @@ -4,6 +4,10 @@ import { Effect, Option, Result, Schema, Array as Arr, String as Str } from "eff import { Command, type Update } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" import { defineMessageUnion } from "foldkit/message" +import { AlertDestinationId } from "@maple/domain/http" +import * as Menu from "@maple/views/ui/menu" +import * as DestinationDialog from "../page/alerts/destination/dialog" +import { menuIdOf } from "../page/alerts/editor/dest-steps" import * as EditorMessage from "../page/alerts/editor/message" import * as EditorModel from "../page/alerts/editor/model" import { init as initEditor } from "../page/alerts/editor/init" @@ -38,13 +42,14 @@ import type { PageContext } from "../page/contract" import type { Resources } from "../platform/resources" // `/lab/anomaly-alerts`: anomalies paging through alert rules, over fixtures. `?state=` picks one: -// editor-new, editor-scoped, editor-default, incident-warning, incident-critical, incident-spike, +// editor-new, editor-scoped, editor-default, editor-critical-destination, incident-warning, incident-critical, incident-spike, // hub-anomalies, hub-anomalies-resolved, hub-anomalies-empty, hub-rules. export const LabState = Schema.Literals([ "editor-new", "editor-scoped", "editor-default", + "editor-critical-destination", "incident-warning", "incident-critical", "incident-spike", @@ -184,6 +189,40 @@ const editorFor = (state: LabState) => EditorMessage.Message.CompletedReadGrants({ grants: Policy.allPermissions }), EditorMessage.Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), EditorMessage.Message.UpdatedRules({ orgId: ORG, rows: RULES }), + ...(state === "editor-critical-destination" + ? [ + EditorMessage.Message.ChangedField({ field: "name", value: "Search anomalies" }), + EditorMessage.Message.ToggledAnomalySignal({ signal: "latency_p95" }), + EditorMessage.Message.GotMenuMessage({ + id: menuIdOf("critical"), + message: Menu.Message.ClickedItem({ + index: 0, + item: { kind: "Item", value: "type:webhook" }, + }), + }), + EditorMessage.Message.GotDestinationDialogMessage({ + message: DestinationDialog.Message.CompletedSave({ + openCount: 1, + isCreate: true, + result: Result.succeed(PAGER_ID), + }), + }), + EditorMessage.Message.UpdatedDestinations({ + orgId: ORG, + rows: [ + destinationRow(), + destinationRow({ + id: PAGER_ID, + name: "Search pager", + config_json: { + summary: "https://pager.acme.test/search", + channelLabel: null, + }, + }), + ], + }), + ] + : []), ...(state === "editor-new" ? [ EditorMessage.Message.ChangedField({ field: "name", value: "Search anomalies" }), @@ -256,6 +295,8 @@ const incidentFor = (state: LabState) => { ) } +const PAGER_ID = Schema.decodeSync(AlertDestinationId)("0199a001-0000-7000-8000-000000000009") + const isEditor = Str.startsWith("editor") const isIncident = Str.startsWith("incident") diff --git a/apps/web/src/page/alerts/editor/actions.ts b/apps/web/src/page/alerts/editor/actions.ts index 6c6221bbf2..1aded2af43 100644 --- a/apps/web/src/page/alerts/editor/actions.ts +++ b/apps/web/src/page/alerts/editor/actions.ts @@ -7,7 +7,7 @@ import { Result } from "effect" import { PageOutMessage } from "../../contract" import { alertsHref } from "../shared/links" import { RunRuleTest, SaveRule, TrackRuleCreated } from "./command" -import { previewIdentityKey, saveIssues } from "./derive" +import { isProtectedDefault, previewIdentityKey, saveIssues } from "./derive" import { draftTimingOf } from "./derive-rows" import type { Message } from "./message" import type { Model } from "./model" @@ -23,15 +23,22 @@ export const runTest = (model: Model, isSending: boolean): Return => { const form = model.form if (form === null || model.isDryRunning || model.isSendingTest) return { model } if (!isRulePreviewReady(form)) - return { model, outMessage: errorToast("Complete the rule name, query, and threshold before testing") } + return { + model, + outMessage: errorToast("Complete the rule name, query, and threshold before testing"), + } return { model: isSending ? { ...model, isSendingTest: true } : { ...model, isDryRunning: true }, - commands: [RunRuleTest({ key: previewIdentityKey(form), payload: buildRuleTestParamsV2(form, isSending) })], + commands: [ + RunRuleTest({ key: previewIdentityKey(form), payload: buildRuleTestParamsV2(form, isSending) }), + ], } } export const completeTest = (model: Model, message: Extract): Return => { - const settled: Model = message.isSending ? { ...model, isSendingTest: false } : { ...model, isDryRunning: false } + const settled: Model = message.isSending + ? { ...model, isSendingTest: false } + : { ...model, isDryRunning: false } return Result.match(message.result, { onSuccess: (dryRun) => ({ model: { ...settled, dryRun }, @@ -41,7 +48,10 @@ export const completeTest = (model: Model, message: Extract ({ model: settled, outMessage: errorToast("Couldn't preview rule", error.message) }), + onFailure: (error) => ({ + model: settled, + outMessage: errorToast("Couldn't preview rule", error.message), + }), }) } @@ -49,7 +59,7 @@ export const completeTest = (model: Model, message: Extract { const form = model.form if (form === null || model.isSaving) return { model } - if (saveIssues(form, draftTimingOf(model, form)).length > 0) return { model } + if (saveIssues(form, draftTimingOf(model, form), isProtectedDefault(model)).length > 0) return { model } return { model: { ...model, isSaving: true }, commands: [SaveRule({ ruleId: model.editingRuleId, payload: buildRuleCreateParamsV2(form) })], @@ -61,7 +71,9 @@ export const completeSave = (model: Model, message: Extract ({ model: { ...model, isSaving: false }, commands: - model.editingRuleId === null && model.form !== null ? [TrackRuleCreated({ signal: model.form.signalType })] : [], + model.editingRuleId === null && model.form !== null + ? [TrackRuleCreated({ signal: model.form.signalType })] + : [], outMessage: PageOutMessage.RequestedNavigation({ url: alertsHref({}), isReplace: false, diff --git a/apps/web/src/page/alerts/editor/derive.ts b/apps/web/src/page/alerts/editor/derive.ts index 52dacb186e..bbeb497373 100644 --- a/apps/web/src/page/alerts/editor/derive.ts +++ b/apps/web/src/page/alerts/editor/derive.ts @@ -50,11 +50,13 @@ export const conditionSubject = (form: RuleForm): string => { } /** The rule in one plain sentence, shown in the action bar. */ -export const describeRuleCondition = (form: RuleForm): string => { +export const describeRuleCondition = (form: RuleForm, isDefault = false): string => { if (form.signalType === "anomaly") - return isDefaultAnomalyRule(form) + return isDefault ? "Pages on every anomaly no narrower rule handles" - : `Pages on anomalies in ${anomalyScopeSummary(form)}` + : isDefaultAnomalyRule(form) + ? "Anomalies in a service, environment or signal" + : `Pages on anomalies in ${anomalyScopeSummary(form)}` const unit = thresholdUnitLabel(form) ?? "" const value = (raw: string) => `${Str.trim(raw) || "?"}${unit === "%" ? "%" : unit ? ` ${unit}` : ""}` const threshold = isRangeComparator(form.comparator) @@ -92,8 +94,8 @@ export const suggestCriticalThreshold = (form: RuleForm): string => { } /** `deriveValidationIssues`: what the action bar lists as missing. */ -export const validationIssues = (form: RuleForm): ReadonlyArray => { - if (form.signalType === "anomaly") return anomalyIssues(form) +export const validationIssues = (form: RuleForm, isDefault = false): ReadonlyArray => { + if (form.signalType === "anomaly") return anomalyIssues(form, isDefault) const issues: Array = [] if (Str.trim(form.name).length === 0) issues.push("Rule name") if (!isFilledNumber(form.threshold)) issues.push("Threshold") @@ -124,22 +126,27 @@ export const validationIssues = (form: RuleForm): ReadonlyArray => { } /** - * An anomaly rule has no query or timing to get wrong: a name, and someone to page on a scoped - * rule. The default rule may stay unrouted (anomaly paging is opt-in), as the server allows. + * An anomaly rule has no query or timing to get wrong: a name, a scope and someone to page. Only + * Maple creates the unscoped default rule, which may stay unrouted (anomaly paging is opt-in); the + * server refuses any other unscoped or unrouted enabled anomaly rule. */ -const anomalyIssues = (form: RuleForm): ReadonlyArray => [ +const anomalyIssues = (form: RuleForm, isDefault: boolean): ReadonlyArray => [ ...(Str.trim(form.name).length === 0 ? ["Rule name"] : []), - ...(!isDefaultAnomalyRule(form) && - form.destinationIds.length === 0 && - (form.escalation?.destinationIds.length ?? 0) === 0 + ...(!isDefault && isDefaultAnomalyRule(form) ? ["Scope"] : []), + ...(!isDefault && form.destinationIds.length === 0 && (form.escalation?.destinationIds.length ?? 0) === 0 ? ["Who gets notified"] : []), ] /** What blocks saving: the form's own issues and a query too slow for its interval. */ -export const saveIssues = (form: RuleForm, timing: AlertQueryTiming | null): ReadonlyArray => { +export const saveIssues = ( + form: RuleForm, + timing: AlertQueryTiming | null, + isDefault = false, +): ReadonlyArray => { const timingIssue = queryTimingIssue(form, timing) - return timingIssue === null ? validationIssues(form) : [...validationIssues(form), timingIssue] + const issues = validationIssues(form, isDefault) + return timingIssue === null ? issues : [...issues, timingIssue] } /** `previewIdentityKey`: the rule as evaluated; a dry-run verdict is only shown while it matches. */ diff --git a/apps/web/src/page/alerts/editor/dest-steps.ts b/apps/web/src/page/alerts/editor/dest-steps.ts index 2a1e615451..b3c7b49bda 100644 --- a/apps/web/src/page/alerts/editor/dest-steps.ts +++ b/apps/web/src/page/alerts/editor/dest-steps.ts @@ -31,7 +31,9 @@ export const liftDialog = (model: Model, step: ReturnType ({ model: - next.form === null ? next : { ...next, form: updateTierIds(next.form, "warning", addId(id)) }, + next.form === null + ? next + : { ...next, form: updateTierIds(next.form, next.destinationTier, addId(id)) }, commands, outMessage: PageOutMessage.RequestedToast({ type: "success", @@ -66,7 +68,7 @@ const picked = (model: Model, menuId: string, value: string): Return => { onNone: () => ({ model }), onSome: (type) => liftDialog( - model, + { ...model, destinationTier: tier }, DestinationDialog.openNew(model.destinationDialog, defaultDestinationForm(type)), ), }) @@ -81,7 +83,7 @@ const picked = (model: Model, menuId: string, value: string): Return => { onNone: (): Return => ({ model }), onSome: (workspace) => liftDialog( - model, + { ...model, destinationTier: tier }, DestinationDialog.openNew( model.destinationDialog, chatDestinationForm(connector, workspace.id), @@ -127,7 +129,13 @@ export const connectorsAnswered = (model: Model, connectors: ReadonlyArray ({ model: next }), onSome: (linked) => liftDialog( - { ...next, chatConnect: ChatConnect.finish(next.chatConnect), chatConnectMenuId: null }, + { + ...next, + chatConnect: ChatConnect.finish(next.chatConnect), + chatConnectMenuId: null, + destinationTier: + model.chatConnectMenuId === null ? "warning" : tierOfMenu(model.chatConnectMenuId), + }, DestinationDialog.openNew( next.destinationDialog, chatDestinationForm(linked.connector, linked.id), diff --git a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts index c1de80d1bb..2d501ff38e 100644 --- a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts @@ -1,9 +1,22 @@ import { OrgId } from "@maple/domain" import { AlertDestinationId } from "@maple/domain/http" import { Result, Schema, Array as Arr } from "effect" -import { click, expect as expectView, given, Mount, role, scene, text, withViewInputs } from "foldkit/scene" +import { + click, + expect as expectView, + given, + Mount, + placeholder, + role, + scene, + text, + withViewInputs, +} from "foldkit/scene" import { describe, expect, test } from "vitest" import { ResolveThemeColors, ThemeMessage } from "@maple/views/charts/theme-colors" +import * as Menu from "@maple/views/ui/menu" +import * as DestinationDialog from "../destination/dialog" +import { menuIdOf } from "./dest-steps" import type { PageContext } from "../../contract" import { defaultAnomalyRuleRow, scopedAnomalyRuleRow } from "../shared/anomaly-test-fixtures" import { DESTINATION_ID, destinationRow, NOW, ORG, ruleId, ruleRow } from "../shared/test-rows" @@ -155,4 +168,78 @@ describe("alert rule editor: anomaly rules", () => { expect(failed.model.isSaving).toBe(false) expect(failed.outMessage).toMatchObject({ title: "Couldn't save rule", description: message }) }) + + test("a destination created from the Critical tier joins the Critical tier", () => { + const NEW_ID = Schema.decodeSync(AlertDestinationId)("0199a001-0000-7000-8000-000000000009") + const draft = run(opened({ template: "anomaly", serviceName: "checkout" }), [ + Message.ChangedField({ field: "name", value: "Checkout anomalies" }), + ]) + const opening = run(draft, [ + Message.GotMenuMessage({ + id: menuIdOf("critical"), + message: Menu.Message.ClickedItem({ + index: 0, + item: { kind: "Item", value: "type:webhook" }, + }), + }), + ]) + expect(opening.destinationDialog.dialog.isOpen).toBe(true) + const created = run(opening, [ + Message.GotDestinationDialogMessage({ + message: DestinationDialog.Message.CompletedSave({ + openCount: opening.destinationDialog.openCount, + isCreate: true, + result: Result.succeed(NEW_ID), + }), + }), + Message.UpdatedDestinations({ + orgId: ORG, + rows: [destinationRow(), destinationRow({ id: NEW_ID, name: "Pager bridge" })], + }), + ]) + expect(created.form?.escalation?.destinationIds).toEqual([NEW_ID]) + expect(created.form?.destinationIds).not.toContain(NEW_ID) + scene( + { update: pageUpdate, view }, + // The dialog's close transition is the dialog's own story: the tier lists are what matter here. + given({ ...created, destinationDialog: draft.destinationDialog }), + themeResolved(), + expectView(text(/Pager bridge/)).toExist(), + ) + const saved = pageUpdate(created, Message.ClickedSave()) + expect(saved.commands?.[0]?.args).toMatchObject({ + payload: { + thresholds: [ + expect.objectContaining({ severity: "warning", destination_ids: [DESTINATION] }), + expect.objectContaining({ severity: "critical", destination_ids: [NEW_ID] }), + ], + }, + }) + }) + + test("a new anomaly rule needs a service, environment or signal; the default rule covers the rest", () => { + const unscoped = run(opened({ template: "anomaly" }), [ + Message.ChangedField({ field: "name", value: "Everything" }), + ]) + scene( + { update: pageUpdate, view }, + given(unscoped), + themeResolved(), + expectView(text(/^Pick at least one service, environment or signal/)).toExist(), + expectView(role("button", { name: "Create rule" })).toBeDisabled(), + click(role("button", { name: "Throughput" })), + expectView(text(/^Pick at least one service, environment or signal/)).not.toExist(), + expectView(role("button", { name: "Create rule" })).not.toBeDisabled(), + ) + expect(pageUpdate(unscoped, Message.ClickedSave()).commands ?? []).toHaveLength(0) + }) + + test("an anomaly rule's name suggests what it covers", () => { + scene( + { update: pageUpdate, view }, + given(opened({ template: "anomaly" })), + themeResolved(), + expectView(placeholder("Checkout anomalies")).toExist(), + ) + }) }) diff --git a/apps/web/src/page/alerts/editor/form.ts b/apps/web/src/page/alerts/editor/form.ts index 26cf9fa759..f1f5e4d3db 100644 --- a/apps/web/src/page/alerts/editor/form.ts +++ b/apps/web/src/page/alerts/editor/form.ts @@ -60,3 +60,7 @@ export type RuleForm = typeof RuleForm.Type /** Both directions are checked: a field added to one side fails the build. */ export const asFormState = (form: RuleForm): RuleFormState => form export const fromFormState = (form: RuleFormState): RuleForm => form + +/** The warning tier's destinations, or the critical tier's. */ +export const Tier = Schema.Literals(["warning", "critical"]) +export type Tier = typeof Tier.Type diff --git a/apps/web/src/page/alerts/editor/init.ts b/apps/web/src/page/alerts/editor/init.ts index 54757ad7e2..fa95a2d4d4 100644 --- a/apps/web/src/page/alerts/editor/init.ts +++ b/apps/web/src/page/alerts/editor/init.ts @@ -66,6 +66,7 @@ export const init = (route: CreateRoute, _context: PageContext): PageReturn): const bar = (h: H, model: BarView, form: RuleForm, timing: AlertQueryTiming | null): Html => { // Unsaved field changes while editing, `null` on a new rule. const changeCount = model.savedForm === null ? null : diffRuleForms(model.savedForm, form).length - const issues = saveIssues(form, timing) + const isDefault = isProtectedDefault(model) + const issues = saveIssues(form, timing, isDefault) const isEditing = model.editingRuleId !== null - const summary = describeRuleCondition(form) + const summary = describeRuleCondition(form, isDefault) return h.div( [ h.Class( diff --git a/apps/web/src/page/alerts/editor/view-details.ts b/apps/web/src/page/alerts/editor/view-details.ts index af7f626354..4d9a11dfa0 100644 --- a/apps/web/src/page/alerts/editor/view-details.ts +++ b/apps/web/src/page/alerts/editor/view-details.ts @@ -112,7 +112,9 @@ const nameField = (h: H, model: DetailsView, form: RuleForm): Html => { ids, state, attributes: [ - h.Placeholder("Error rate on payments"), + h.Placeholder( + form.signalType === "anomaly" ? "Checkout anomalies" : "Error rate on payments", + ), h.Value(form.name), h.OnInput((value) => Message.ChangedField({ field: "name", value })), ], diff --git a/apps/web/src/page/alerts/editor/view-query.ts b/apps/web/src/page/alerts/editor/view-query.ts index a7fdd8d8e9..692ed209ed 100644 --- a/apps/web/src/page/alerts/editor/view-query.ts +++ b/apps/web/src/page/alerts/editor/view-query.ts @@ -57,6 +57,9 @@ const KIND_HINT = new Map([ const ANOMALY_DESCRIPTION = "Pages when Maple detects unusual error rate, latency, throughput, error spikes or log volume. The most specific anomaly rule handles each anomaly." +const SCOPE_NEEDED_NOTE = + "Pick at least one service, environment or signal. Maple's default anomaly rule already covers everything." + const DEFAULT_ANOMALY_NOTE = "This is the default anomaly rule. It handles every anomaly no narrower rule covers, so its scope stays empty and it stays on." @@ -315,6 +318,8 @@ const builtinForm = (h: H, model: QueryView, form: RuleForm): Html => { const anomalyForm = (h: H, model: QueryView, form: RuleForm): Html => { const facets = traceFacetsOf(model) const isLocked = model.isDefaultAnomaly + const isUnscoped = + form.serviceNames.length === 0 && form.environments.length === 0 && form.anomalySignals.length === 0 return card( h, [ @@ -337,7 +342,9 @@ const anomalyForm = (h: H, model: QueryView, form: RuleForm): Html => { ], ), ] - : []), + : isUnscoped + ? [typography(h, { size: "xs", tone: "warning", as: "p" }, [SCOPE_NEEDED_NOTE])] + : []), ], ), h.div( @@ -446,6 +453,8 @@ const query = (h: H, model: QueryView, form: RuleForm, timing: AlertQueryTiming onChange: (next) => Message.ClickedSignalKind({ kind: next }), ariaLabel: "Query mode", size: "sm", + // Four modes outgrow a phone's width: the track wraps instead of clipping the last one. + className: "max-w-full flex-wrap", }), typography(h, { size: "xs", tone: "muted", as: "p", className: "min-w-0 flex-1" }, [ model.kindNotice ?? KIND_HINT.get(kind) ?? "", diff --git a/apps/web/src/page/alerts/editor/view.ts b/apps/web/src/page/alerts/editor/view.ts index 504d4ae9e6..d0355857b9 100644 --- a/apps/web/src/page/alerts/editor/view.ts +++ b/apps/web/src/page/alerts/editor/view.ts @@ -1,4 +1,3 @@ -import { isDefaultAnomalyRule } from "@maple/web-data/alerts/anomaly-rule" import { deriveTierIssues, isFilledNumber } from "@maple/web-data/alerts/form-utils" import { cn } from "@maple/ui/lib/utils" import { Submodel } from "foldkit" @@ -17,6 +16,7 @@ import { RULE_CHART_TOKENS } from "../shared/rule-chart" import type { RuleForm } from "./form" import { Message } from "./message" import type { Model, Notice } from "./model" +import { isProtectedDefault } from "./derive" import { actionBar } from "./view-action-bar" import { conditionFields } from "./view-condition" import { detailsSection } from "./view-details" @@ -109,9 +109,9 @@ const evaluatedFormOf = stableSlice((form: RuleForm): RuleForm => ({ })) /** Who gets paged is missing: no destination on a tier that needs one. */ -const isNotifyMissingOf = (form: RuleForm): boolean => +const isNotifyMissingOf = (form: RuleForm, isDefault: boolean): boolean => form.signalType === "anomaly" - ? !isDefaultAnomalyRule(form) && + ? !isDefault && form.destinationIds.length === 0 && (form.escalation?.destinationIds.length ?? 0) === 0 : form.destinationIds.length === 0 || deriveTierIssues(form).includes("Critical destinations") @@ -171,7 +171,9 @@ const surface = (h: H, model: Model, form: RuleForm, timeZone: string): Readonly number: stepAfterQuery, hasDivider: false, title: "Who gets notified", - status: isNotifyMissingOf(form) ? "missing" : "done", + status: isNotifyMissingOf(form, isProtectedDefault(model)) + ? "missing" + : "done", }, [notificationsSection(h, model, form)], ), diff --git a/apps/web/src/route/redirect.ts b/apps/web/src/route/redirect.ts index eb94cbcf73..813b9acb05 100644 --- a/apps/web/src/route/redirect.ts +++ b/apps/web/src/route/redirect.ts @@ -1,9 +1,9 @@ import type { OrganizationFeatureFlags } from "@maple/domain/organization-feature-flags" -import { Option, Schema } from "effect" +import { Option, Predicate, Schema } from "effect" import { AppRoute } from "./app-route" import { hrefOf } from "./routers" import { DATA_INTEGRATIONS } from "./data-integrations" -import { ErrorsSearch } from "./search" +import { type AlertsSearch, type AnomaliesSearch, ErrorsSearch } from "./search" /** Where a route sends the browser instead of rendering, and whether it replaces history. */ export const Redirect = Schema.Struct({ url: Schema.String, isReplace: Schema.Boolean }) @@ -36,6 +36,30 @@ const errorsSearchOf = ({ search }: typeof AppRoute.ErrorIssues.Type): typeof Er ) } +const decodeServices = Schema.decodeUnknownOption( + Schema.Union([Schema.NonEmptyString, Schema.Array(Schema.String)]), +) + +/** The one service an old anomalies link named, under either spelling, or none. */ +const anomalyServiceOf = (search: typeof AnomaliesSearch.Type): string | undefined => { + const named = Option.match(decodeServices(search.services ?? search.service), { + onNone: (): ReadonlyArray => [], + onSome: (value): ReadonlyArray => (Predicate.isString(value) ? [value] : value), + }) + return named.length === 1 ? named[0] : undefined +} + +const anomaliesViewSearch = (search: typeof AnomaliesSearch.Type): typeof AlertsSearch.Type => { + const serviceName = anomalyServiceOf(search) + return { + signal: "anomaly", + ...(serviceName === undefined ? undefined : { serviceName }), + ...(search.status === "resolved" || search.status === "all" + ? { anomalyStatus: search.status } + : undefined), + } +} + const DATA_INTEGRATION_IDS: ReadonlySet = new Set(DATA_INTEGRATIONS) const redirect = (to: AppRoute, isReplace: boolean) => @@ -73,22 +97,9 @@ export const routeRedirect = (route: AppRoute): Option.Option => InfraDiscover: () => redirect(AppRoute.Infra({ search: {} }), true), InfraLegacyHost: ({ hostName }) => redirect(AppRoute.InfraHost({ hostName, search: {} }), true), AlertIncidentsIndex: () => redirect(AppRoute.Alerts({ search: {} }), true), - /** The anomalies list is the Alerts Inbox's Anomalies view; a service and status carry over. */ + /** The anomalies list is the Alerts Inbox's Anomalies view; one service and the status carry over. */ Anomalies: ({ search }) => - redirect( - AppRoute.Alerts({ - search: { - signal: "anomaly", - ...(search.services !== undefined && search.services.length === 1 - ? { serviceName: search.services[0] } - : undefined), - ...(search.status === "resolved" || search.status === "all" - ? { anomalyStatus: search.status } - : undefined), - }, - }), - true, - ), + redirect(AppRoute.Alerts({ search: anomaliesViewSearch(search) }), true), /** Status pages left the Alerts hub for their own sidebar entry. */ Alerts: ({ search }) => search.tab === "status-pages" ? redirect(AppRoute.StatusPages(), true) : Option.none(), diff --git a/apps/web/src/route/route.test.ts b/apps/web/src/route/route.test.ts index 8dc60fdc90..4775655275 100644 --- a/apps/web/src/route/route.test.ts +++ b/apps/web/src/route/route.test.ts @@ -107,6 +107,19 @@ describe("routes", () => { expect(routeRedirect(parse("/anomalies/00000000-0000-4000-8000-000000000004"))).toEqual(Option.none()) }) + test("old anomalies links with a plain or singular service still land on that service", () => { + const landing = (path: string) => Option.map(routeRedirect(parse(path)), ({ url }) => parse(url)) + const checkout = Option.some( + AppRoute.Alerts({ search: { signal: "anomaly", serviceName: "demo-api" } }), + ) + expect(landing("/anomalies?services=demo-api")).toEqual(checkout) + expect(landing("/anomalies?service=demo-api")).toEqual(checkout) + // A value it cannot read is dropped: the view opens unfiltered instead of "Page not found". + expect(landing("/anomalies?services=%7B%22a%22%3A1%7D&status=nope")).toEqual( + Option.some(AppRoute.Alerts({ search: { signal: "anomaly" } })), + ) + }) + test("the old Hazel integration link opens the Hazel chat connector", () => { expect(routeRedirect(parse("/integrations?integration=hazel"))).toEqual( Option.some({ url: "/integrations?integration=chat-hazel", isReplace: true }), diff --git a/apps/web/src/route/search.ts b/apps/web/src/route/search.ts index bfa3bc8d82..f07913dd33 100644 --- a/apps/web/src/route/search.ts +++ b/apps/web/src/route/search.ts @@ -523,26 +523,15 @@ export const AnalyticsSearch = Schema.Struct({ tab: Schema.optional(Schema.String), }) -const AnomalySeverityLiteral = Schema.Literals(["warning", "critical"]) -const AnomalySignalLiteral = Schema.Literals([ - "error_rate", - "latency_p95", - "throughput", - "error_spike", - "log_volume", -]) - +/** + * `/anomalies`, retired: it only redirects to the Alerts Inbox's Anomalies view. Old links spelled + * the filters several ways (`services=["a"]`, `services=a`, `service=a`), so every value is read + * as given and the redirect keeps what it can understand; a malformed one is dropped, never a 404. + */ export const AnomaliesSearch = Schema.Struct({ - status: Schema.optional(Schema.Literals(["open", "resolved", "all"])), - severity: Schema.optional(Schema.Array(AnomalySeverityLiteral)), - signals: Schema.optional(Schema.Array(AnomalySignalLiteral)), - services: Schema.optional(Schema.Array(Schema.String)), - envs: Schema.optional(Schema.Array(Schema.String)), - excludedSeverity: Schema.optional(Schema.Array(AnomalySeverityLiteral)), - excludedSignals: Schema.optional(Schema.Array(AnomalySignalLiteral)), - excludedServices: Schema.optional(Schema.Array(Schema.String)), - excludedEnvs: Schema.optional(Schema.Array(Schema.String)), - live: Schema.optional(Schema.Boolean), + status: Schema.optional(Schema.Unknown), + services: Schema.optional(Schema.Unknown), + service: Schema.optional(Schema.Unknown), }) export const CodeReviewSearch = Schema.Struct(CodeReviewSearchFields) From d4fd8969d522fe3ef9b9ea539a7f16f12fd4edfd Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:07:50 +0100 Subject: [PATCH 08/28] test(alerts): scope the anomaly silences test rule now that only Maple creates the default --- .../alerts/AlertsService.anomaly-silences.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts index c88533f117..af1f760788 100644 --- a/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts +++ b/packages/backend/src/services/alerts/AlertsService.anomaly-silences.test.ts @@ -82,7 +82,10 @@ const insertCheckoutAnomaly = Effect.gen(function* () { ) }).pipe(Effect.orDie) -/** An anomaly rule over every service, like the default one, whose critical tier pages a webhook. */ +/** + * An anomaly rule over every service in production, whose critical tier pages a webhook. Only Maple + * creates the fully unscoped default, so the environment scope stands in for it here. + */ const createUnscopedAnomalyRule = (alerts: AlertsServiceApi) => Effect.gen(function* () { const destination = yield* createWebhookDestination(alerts, orgId, userId) @@ -91,9 +94,10 @@ const createUnscopedAnomalyRule = (alerts: AlertsServiceApi) => userId, adminRoles, new AlertRuleUpsertRequest({ - name: "Anomalies", + name: "Production anomalies", enabled: true, signalType: "anomaly", + environments: ["production"], comparator: "gte", windowMinutes: 5, thresholds: [ From 090189743b803dddf9206a43df29ca68ef4cf8d8 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:11:17 +0100 Subject: [PATCH 09/28] docs(domain): keep the hold reason comment on its schema --- packages/domain/src/http/alerts.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/domain/src/http/alerts.ts b/packages/domain/src/http/alerts.ts index 5811fae7aa..bc308ade05 100644 --- a/packages/domain/src/http/alerts.ts +++ b/packages/domain/src/http/alerts.ts @@ -128,13 +128,6 @@ export const AlertIncidentStatus = Schema.Literals(["open", "resolved"]).annotat }) export type AlertIncidentStatus = Schema.Schema.Type -/** - * Why an open incident is waiting on telemetry instead of resolving. The - * breach stopped appearing in evaluations, but the liveness probe could not - * prove the service's data was still flowing, so the all-clear is deferred. - * `status` stays `"open"` while held — a hold is a sub-state of open, not a - * third status, so every consumer of `open`/`resolved` keeps working. - */ /** Facts about what an incident is about, for rules whose group key does not say (anomaly rules). */ export const AlertIncidentLabels = Schema.Struct({ service: Schema.optionalKey(Schema.String), @@ -142,6 +135,13 @@ export const AlertIncidentLabels = Schema.Struct({ }) export type AlertIncidentLabels = Schema.Schema.Type +/** + * Why an open incident is waiting on telemetry instead of resolving. The + * breach stopped appearing in evaluations, but the liveness probe could not + * prove the service's data was still flowing, so the all-clear is deferred. + * `status` stays `"open"` while held — a hold is a sub-state of open, not a + * third status, so every consumer of `open`/`resolved` keeps working. + */ export const AlertIncidentHoldReason = Schema.Literals([ "no_data", "volume_collapsed", From 720e8cf0cc81bd3451675ca00c8df5ca8152ce5e Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:13:55 +0100 Subject: [PATCH 10/28] fix(alerts): templates show an anomaly's severity as a word --- .../src/services/alerts/AlertDeliveryDispatch.test.ts | 10 ++++++++++ .../backend/src/services/alerts/alert-formatting.ts | 1 + .../src/services/alerts/alert-signal-display.ts | 6 ++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts b/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts index c7e703ebbd..622c740eee 100644 --- a/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts +++ b/packages/backend/src/services/alerts/AlertDeliveryDispatch.test.ts @@ -264,6 +264,16 @@ describe("anomaly notifications", () => { assert.strictEqual(buildTemplateContext(keyedById, LINK, CHAT).group, "all") }) + it("renders the severity code as a word in a custom template", () => { + const context = buildTemplateContext(anomalyContext, LINK, CHAT) + assert.strictEqual(context.observed, "Critical") + assert.strictEqual(context.threshold, "Critical") + assert.strictEqual( + buildTemplateContext({ ...anomalyContext, value: 1 }, LINK, CHAT).observed, + "Warning", + ) + }) + it("says the anomaly ended on resolve", () => { const resolved = { ...anomalyContext, diff --git a/packages/backend/src/services/alerts/alert-formatting.ts b/packages/backend/src/services/alerts/alert-formatting.ts index 11964ffa85..d83bd9049f 100644 --- a/packages/backend/src/services/alerts/alert-formatting.ts +++ b/packages/backend/src/services/alerts/alert-formatting.ts @@ -151,6 +151,7 @@ export const formatSignalMetric = (value: number | null, display: SignalDisplay) Match.when("rpm", () => `${grouped(metric)} rpm`), Match.whenOr("count", "plain", () => grouped(metric)), Match.when("burn", () => `${round(metric, 1)}x`), + Match.when("severity", () => (metric >= 2 ? "Critical" : metric >= 1 ? "Warning" : "Normal")), Match.exhaustive, ), }) diff --git a/packages/backend/src/services/alerts/alert-signal-display.ts b/packages/backend/src/services/alerts/alert-signal-display.ts index f1a602ac5c..7a9e70539c 100644 --- a/packages/backend/src/services/alerts/alert-signal-display.ts +++ b/packages/backend/src/services/alerts/alert-signal-display.ts @@ -20,7 +20,8 @@ import type { ChartUnit } from "@maple/widgets/chart/static-chart" import { Array as Arr, String as Str } from "effect" /** `burn`: an SLO burn rate, a multiple of the sustainable rate shown as `14.4x`. */ -export type SignalUnit = "ratio" | "ms" | "rpm" | "apdex" | "count" | "plain" | "burn" +/** `severity` is an anomaly rule's code: 0 normal, 1 warning, 2 critical. */ +export type SignalUnit = "ratio" | "ms" | "rpm" | "apdex" | "count" | "plain" | "burn" | "severity" export interface SignalDisplay { /** Human-readable name of the measured quantity, e.g. `sum(db.query.duration)`. */ @@ -47,7 +48,7 @@ const PRESET_SIGNALS: Record = { throughput: { label: "Throughput", unit: "rpm" }, uptime: { label: "Uptime check", unit: "plain" }, slo_burn_rate: { label: "SLO burn rate", unit: "burn" }, - anomaly: { label: "Anomaly", unit: "plain" }, + anomaly: { label: "Anomaly", unit: "severity" }, } satisfies Record /** @@ -150,6 +151,7 @@ export const mapSignalUnit = (unit: SignalUnit): ChartUnit => { case "count": case "plain": case "burn": + case "severity": return "number" } } From 62a23d0fca4da452f7157652928f51d3109a2a0b Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:16:22 +0100 Subject: [PATCH 11/28] fix(web): the anomalies open view reads open incidents only --- .../page/alerts/hub/hub-anomalies.scene.test.ts | 15 +++++++++++++++ apps/web/src/page/alerts/hub/view-anomalies.ts | 11 ++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts index 2a03c45004..45fc208ac9 100644 --- a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts +++ b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts @@ -155,6 +155,21 @@ describe("alerts hub: anomalies", () => { ) }) + test("with only resolved anomalies the open view still says there are none", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly" }, [RESOLVED_CHECKOUT])), + expectView(text("No anomalies")).toExist(), + expectView(text(/^No rule fired/)).not.toExist(), + ) + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", anomalyStatus: "all" }, [RESOLVED_CHECKOUT])), + expectView(role("link", { name: "Anomaly on checkout (staging)" })).toExist(), + expectView(text(/^No rule fired/)).not.toExist(), + ) + }) + test("the rules list marks the default anomaly rule, reads scopes, and offers no way to turn it off", () => { scene( { update: pageUpdate, view }, diff --git a/apps/web/src/page/alerts/hub/view-anomalies.ts b/apps/web/src/page/alerts/hub/view-anomalies.ts index 6ba0b5cdde..69cbb1156e 100644 --- a/apps/web/src/page/alerts/hub/view-anomalies.ts +++ b/apps/web/src/page/alerts/hub/view-anomalies.ts @@ -204,14 +204,15 @@ export const anomalyInbox = ( const now = data.timelineRange.max const hasOpenView = options.status !== "resolved" const hasResolvedView = options.status !== "open" - const isEmpty = - (hasOpenView ? data.incidents.length === 0 : true) && (hasResolvedView ? resolved.length === 0 : true) - if (isEmpty) return anomaliesEmpty(h, options.status, options.serviceName) + // `incidents` also holds recently resolved ones, so the open side reads `openIncidents`. + const showOpen = hasOpenView && data.openIncidents.length > 0 + const showResolved = hasResolvedView && resolved.length > 0 + if (!showOpen && !showResolved) return anomaliesEmpty(h, options.status, options.serviceName) return h.div( [h.Class("space-y-8")], [ - ...(hasOpenView ? [inboxTab(h, data, options.acknowledge)] : []), - ...(hasResolvedView && resolved.length > 0 ? [resolvedList(h, resolved, now)] : []), + ...(showOpen ? [inboxTab(h, data, options.acknowledge)] : []), + ...(showResolved ? [resolvedList(h, resolved, now)] : []), ], ) } From c62cebf8f8d18bf90bc9b309afbc14bde98b5c08 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:22:10 +0100 Subject: [PATCH 12/28] fix(alerts): the default anomaly rule message names actions that exist --- packages/backend/src/services/alerts/anomaly/anomaly-rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts index c15cba56b6..8224cff022 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -36,7 +36,7 @@ export const DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE = export const DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE = "The default anomaly rule covers every anomaly and cannot be scoped; create a scoped anomaly rule instead" export const DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE = - "The default anomaly rule cannot be deleted or disabled; turn anomaly detection off in anomaly settings instead" + "The default anomaly rule cannot be deleted or disabled. Leave it with no destinations to keep anomalies in the app only, or turn detection off with PATCH /v2/anomalies/settings (enabled: false)" /** What an anomaly rule pages on. Each empty list matches everything. */ export interface AnomalyRuleScope { From 68df7517d99f3225e179deb571499fac4c4998df Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:27:35 +0100 Subject: [PATCH 13/28] fix(alerts): anomaly rule test runs and MCP text --- apps/ai/src/mcp/lib/alert-rules.ts | 70 +++++++++++++++---- .../mcp/tools/__tests__/alert-tools.test.ts | 39 +++++++++++ apps/ai/src/mcp/tools/get-alert-rule.ts | 61 +++++++++------- .../alerts/AlertsService.anomaly.test.ts | 34 +++++++++ .../src/services/alerts/AlertsService.ts | 30 +++++++- .../alerts/anomaly/AnomalyAlertSource.ts | 48 ++++++++++++- .../services/alerts/anomaly/anomaly-rule.ts | 11 ++- 7 files changed, 248 insertions(+), 45 deletions(-) diff --git a/apps/ai/src/mcp/lib/alert-rules.ts b/apps/ai/src/mcp/lib/alert-rules.ts index 475025366d..9b45fb396e 100644 --- a/apps/ai/src/mcp/lib/alert-rules.ts +++ b/apps/ai/src/mcp/lib/alert-rules.ts @@ -21,6 +21,7 @@ import { rawAlertSampleCountWarning } from "@maple/domain/raw-sql" import { alertRuleIntervalMinutes } from "@maple/query-engine/datetime" import { McpInvalidInputError } from "../tools/types" import { doc, type ToolDoc } from "./tool-doc" +import { anomalySignalName } from "@maple/backend/services/alerts/anomaly/anomaly-rule" export const ALERT_SEVERITIES = AlertSeverity.literals /** `uptime` rules are made and edited through uptime monitors, so the rule tools never offer it. */ @@ -73,16 +74,56 @@ export const formatCondition = (rule: { ? `${rule.comparator} ${rule.threshold} and ${rule.thresholdUpper}` : `${comparatorLabel(rule.comparator)} ${rule.threshold}` -/** An anomaly rule's signal scope for display; undefined on other rules. */ -export const formatAnomalySignals = (rule: { +const isAnomalySignal = Schema.is(AnomalySignalType) + +/** "a", "a and b", "a, b and c". */ +const joinWithAnd = (items: ReadonlyArray): string => + items.length <= 2 + ? Arr.join(items, " and ") + : `${Arr.join(items.slice(0, -1), ", ")} and ${items[items.length - 1]}` + +/** + * What an anomaly rule pages on, in one phrase: "checkout, production, error rate and p95 latency", + * or "every anomaly" for the unscoped default. Undefined on other rules. + */ +export const formatAnomalyScope = (rule: { readonly signalType: string + readonly serviceNames: ReadonlyArray + readonly environments: ReadonlyArray readonly anomalySignals?: ReadonlyArray | undefined -}): string | undefined => - rule.signalType !== "anomaly" - ? undefined - : rule.anomalySignals === undefined || rule.anomalySignals.length === 0 - ? "All signals" - : Arr.join(rule.anomalySignals, ", ") +}): string | undefined => { + if (rule.signalType !== "anomaly") return undefined + const signals = Arr.map(rule.anomalySignals ?? [], (signal) => + isAnomalySignal(signal) ? anomalySignalName(signal) : signal, + ) + const parts = Arr.filter( + [joinWithAnd(rule.serviceNames), joinWithAnd(rule.environments), joinWithAnd(signals)], + (part) => part.length > 0, + ) + return parts.length === 0 ? "every anomaly" : Arr.join(parts, ", ") +} + +/** The condition rows of a rule's text: comparison and window, or an anomaly rule's scope and cadence. */ +export const ruleConditionFields = ( + rule: RuleCadence & { + readonly signalType: string + readonly comparator: string + readonly threshold: number + readonly thresholdUpper?: number | null | undefined + readonly serviceNames: ReadonlyArray + readonly environments: ReadonlyArray + readonly anomalySignals?: ReadonlyArray | undefined + }, +): ReadonlyArray => + rule.signalType === "anomaly" + ? [ + ["Scope", formatAnomalyScope(rule)], + ["Checked", formatCheckInterval(rule)], + ] + : [ + ["Condition", formatCondition(rule)], + ["Window", `${rule.windowMinutes}m, checked ${formatCheckInterval(rule)}`], + ] export const toAlertRuleRow = (rule: AlertRuleDocument): typeof AlertRuleRow.Type => ({ id: rule.id, @@ -278,19 +319,22 @@ export const renderRuleWrite = ( ["Name", rule.name], [ "Service Names", - rule.serviceNames.length > 0 ? Arr.join(rule.serviceNames, ", ") : undefined, + rule.serviceNames.length > 0 && rule.signalType !== "anomaly" + ? Arr.join(rule.serviceNames, ", ") + : undefined, ], ["Severity", rule.severity], ["Signal", rule.signalType], - ["Condition", formatCondition(rule)], - ["Window", `${rule.windowMinutes}m, checked ${formatCheckInterval(rule)}`], + // An anomaly rule's comparison and window are pinned; its scope is what it means. + ...ruleConditionFields(rule), ["Enabled", rule.enabled ? "Yes" : "No"], ["Destinations", rule.destinationIds.length], [ "Environments", - rule.environments.length > 0 ? Arr.join(rule.environments, ", ") : undefined, + rule.environments.length > 0 && rule.signalType !== "anomaly" + ? Arr.join(rule.environments, ", ") + : undefined, ], - ["Anomaly signals", formatAnomalySignals(rule)], ]), ], next: [ diff --git a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts index b17299dd48..ee06683421 100644 --- a/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts +++ b/apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts @@ -14,6 +14,7 @@ import { AlertRulePreviewResponse, AlertRulesListResponse, AlertRuleUpsertRequest, + type AnomalySignalType, AlertValidationError, OrgId, UserId, @@ -179,6 +180,14 @@ const layer = (seen: Seen) => Schema.decodeSync(AlertRuleDocument)({ ...rule, signalType: request.signalType, + ...(request.signalType === "anomaly" + ? { + comparator: "gte", + threshold: 2, + evaluationIntervalMinutes: 1, + anomalySignals: request.anomalySignals ?? [], + } + : undefined), rawQuerySql: request.rawQuerySql ?? null, minimumSampleCount: request.minimumSampleCount ?? 0, }), @@ -577,6 +586,36 @@ describe("alert tools", () => { // No query window exists, so the model is told why instead of "returned no series". expect(text(result)).toContain("there is no query window to preview") expect(text(result)).not.toContain("returned no series") + // Its comparison and window are pinned, so the text names the scope instead. + expect(text(result)).toContain("Scope: checkout, production, error rate and p95 latency") + expect(text(result)).toContain("Checked: every 1m") + expect(text(result)).not.toContain(">=") + expect(text(result)).not.toContain("Window") + }) + + it("get_alert_rule shows an anomaly rule's scope, not its pinned comparison or window", async () => { + const anomalyRule = (anomalySignals: ReadonlyArray, scoped: boolean) => + Schema.decodeSync(AlertRuleDocument)({ + ...rule, + serviceNames: scoped ? ["checkout"] : [], + environments: scoped ? ["production"] : [], + signalType: "anomaly", + comparator: "gte", + threshold: 2, + evaluationIntervalMinutes: 1, + anomalySignals, + }) + const scoped = text( + await ok("get_alert_rule", { rule_id: RULE_ID }, { rules: [anomalyRule(["error_rate"], true)] }), + ) + expect(scoped).toContain("Scope: checkout, production, error rate") + expect(scoped).toContain("Checked: every 1m") + expect(scoped).not.toContain(">=") + expect(scoped).not.toContain("Window") + const fallback = text( + await ok("get_alert_rule", { rule_id: RULE_ID }, { rules: [anomalyRule([], false)] }), + ) + expect(fallback).toContain("Scope: every anomaly") }) it("update_alert_rule replaces an anomaly rule's signals only when asked", () => { diff --git a/apps/ai/src/mcp/tools/get-alert-rule.ts b/apps/ai/src/mcp/tools/get-alert-rule.ts index 49ec5c0bf4..07bf31a94e 100644 --- a/apps/ai/src/mcp/tools/get-alert-rule.ts +++ b/apps/ai/src/mcp/tools/get-alert-rule.ts @@ -6,9 +6,7 @@ import { CurrentMcpTenant } from "../lib/query-warehouse" import { AlertRulesService } from "@maple/backend/services/alerts/AlertRulesService" import { AlertsService } from "@maple/backend/services/alerts/AlertsService" import { - formatAnomalySignals, - formatCheckInterval, - formatCondition, + ruleConditionFields, ruleConfigWarnings, ruleNotFound, slowedNotice, @@ -104,8 +102,7 @@ export function registerGetAlertRuleTool(server: McpToolRegistrar) { ["Status", rule.enabled ? "Enabled" : "Disabled"], ["Severity", rule.severity], ["Signal", rule.signalType], - ["Condition", formatCondition(rule)], - ["Window", `${rule.windowMinutes}m, checked ${formatCheckInterval(rule)}`], + ...ruleConditionFields(rule), ["Slowed down at", rule.slowedAt ?? undefined], ["Last evaluated", rule.lastEvaluatedAt ?? undefined], ["Last evaluation error", rule.lastEvaluationError ?? undefined], @@ -116,28 +113,38 @@ export function registerGetAlertRuleTool(server: McpToolRegistrar) { : (rule.lastEvaluationErrorKind ?? undefined), ], ]), - doc.heading("Scope"), - doc.fields([ - [ - "Service Names", - rule.serviceNames.length > 0 ? Arr.join(rule.serviceNames, ", ") : "All services", - ], - [ - "Exclude", - rule.excludeServiceNames.length > 0 - ? Arr.join(rule.excludeServiceNames, ", ") - : undefined, - ], - [ - "Environments", - rule.environments.length > 0 ? Arr.join(rule.environments, ", ") : "All environments", - ], - ["Anomaly signals", formatAnomalySignals(rule)], - [ - "Group By", - rule.groupBy && rule.groupBy.length > 0 ? Arr.join(rule.groupBy, ", ") : undefined, - ], - ]), + // An anomaly rule's scope is already its Scope row above. + ...(rule.signalType === "anomaly" + ? [] + : [ + doc.heading("Scope"), + doc.fields([ + [ + "Service Names", + rule.serviceNames.length > 0 + ? Arr.join(rule.serviceNames, ", ") + : "All services", + ], + [ + "Exclude", + rule.excludeServiceNames.length > 0 + ? Arr.join(rule.excludeServiceNames, ", ") + : undefined, + ], + [ + "Environments", + rule.environments.length > 0 + ? Arr.join(rule.environments, ", ") + : "All environments", + ], + [ + "Group By", + rule.groupBy && rule.groupBy.length > 0 + ? Arr.join(rule.groupBy, ", ") + : undefined, + ], + ]), + ]), doc.heading("Evaluation"), doc.fields([ ["Minimum Sample Count", rule.minimumSampleCount], diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts index dc3869df6f..6aa4c1e2c2 100644 --- a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts +++ b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts @@ -520,4 +520,38 @@ describe("AlertsService anomaly rules", () => { assert.deepStrictEqual(states?.keys, [errorRateSpike.id]) }).pipe(Effect.provide(layer)) }) + + it.effect("a rule test reads the open anomalies in an unsaved rule's scope", () => { + const { layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) + const testScope = (serviceNames: ReadonlyArray) => + alerts.testRule( + orgId, + userId, + adminRoles, + anomalyRule({ + name: "Unsaved", + serviceNames, + thresholds: criticalRouted(destination.id), + }), + ) + + const checkout = yield* testScope(["checkout"]) + expect(checkout).toMatchObject({ + status: "breached", + value: 2, + reason: "error rate 4.2% vs baseline 0.3% in checkout (production)", + }) + const payments = yield* testScope(["payments"]) + expect(payments).toMatchObject({ + status: "healthy", + value: 0, + reason: "No open anomaly in this rule's scope", + }) + }).pipe(Effect.provide(layer)) + }) }) diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index 5455396ab5..0eb4e81892 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -1699,8 +1699,34 @@ export class AlertsService extends Context.Service 1 && normalized.signalType !== "anomaly") { + // An unsaved anomaly rule has no anomalies assigned yet, so it reads every open one in + // its scope; the most severe is the result, and none in scope is healthy, not no-data. + if (normalized.signalType === "anomaly") { + const observations = yield* anomalyAlerts.observeScope(orgId, normalized) + const worst = Arr.head( + Arr.sort( + observations, + Order.flip( + Order.mapInput( + Order.Number, + (observed: AnomalyAlertObservation) => observed.value, + ), + ), + ), + ) + evaluation = applyEvaluationLogic( + normalized, + { + value: Option.match(worst, { onNone: () => 0, onSome: (o) => o.value }), + sampleCount: 1, + hasData: true, + }, + Option.match(worst, { + onNone: () => "No open anomaly in this rule's scope", + onSome: (o) => o.detail, + }), + ) + } else if (normalized.serviceNames.length > 1) { const serviceRules = yield* perServiceRules(normalized) const results = yield* Effect.forEach( serviceRules, diff --git a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts index f6e3f213b0..5d4c55183a 100644 --- a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts +++ b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts @@ -21,6 +21,7 @@ import { makeDbExecute } from "@maple/backend/platform/db-execute" import { lockOrgAlertRules, makePersistenceError } from "../alert-persistence" import { anomalyGroupLabel, + anomalyScopeCovers, anomalySeverityCode, assignAnomalyRule, DEFAULT_ANOMALY_RULE_FALLBACK_NAME, @@ -29,6 +30,7 @@ import { describeAnomaly, isDefaultAnomalyScope, storedAnomalyScope, + type AnomalyRuleScope, type AnomalySeverityCode, } from "./anomaly-rule" @@ -63,6 +65,14 @@ export interface AnomalyAlertSourceApi { * {@link MAX_DEFAULT_RULE_BACKFILL} per call. Idempotent; returns how many it created. */ readonly backfillDefaultRules: Effect.Effect + /** + * The open anomalies inside `scope`, whichever rule pages them: what an unsaved rule would + * see, for a rule test. + */ + readonly observeScope: ( + orgId: OrgId, + scope: AnomalyRuleScope, + ) => Effect.Effect, AlertPersistenceError> } const MAX_DEFAULT_RULE_BACKFILL = 100 @@ -354,7 +364,43 @@ export class AnomalyAlertSource extends Context.Service + db.run( + PG.from(AnomalyIncidents) + .select( + "id", + "signalType", + "serviceName", + "deploymentEnv", + "severity", + "lastObservedValue", + "baselineMedian", + "errorIssueId", + ) + .where(($) => [$.orgId.eq(orgId), $.status.eq("open")]), + ), + ) + const inScope = Arr.filter(anomalies, (incident) => anomalyScopeCovers(scope, incident)) + yield* Effect.annotateCurrentSpan("maple.alert.observation_count", inScope.length) + return Arr.map(inScope, (incident): AnomalyAlertObservation => ({ + groupKey: incident.id, + value: anomalySeverityCode(incident.severity), + detail: describeAnomaly(incident), + anomalyIncidentId: incident.id, + errorIssueId: incident.errorIssueId, + serviceName: incident.serviceName, + deploymentEnv: incident.deploymentEnv, + signalType: incident.signalType, + groupLabel: anomalyGroupLabel(incident), + })) + }) + + return { observe, backfillDefaultRules, observeScope } satisfies AnomalyAlertSourceApi }), }, ) { diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts index 8224cff022..cbb03c8dc9 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -76,7 +76,11 @@ export interface AnomalyIncidentScope { const inScope = (values: ReadonlyArray, value: A): boolean => values.length === 0 || Arr.contains(values, value) -const covers = (rule: AnomalyRuleScope, incident: AnomalyIncidentScope): boolean => +/** Whether an anomaly falls inside a rule's scope; each empty list matches everything. */ +export const anomalyScopeCovers = ( + rule: AnomalyRuleScope, + incident: Pick, +): boolean => inScope(rule.serviceNames, incident.serviceName) && inScope(rule.environments, incident.deploymentEnv) && inScope(rule.anomalySignals, incident.signalType) @@ -105,7 +109,7 @@ export const assignAnomalyRule = ( ): Option.Option => { const code = anomalySeverityCode(incident.severity) const qualifying = Arr.sort( - Arr.filter(rules, (rule) => covers(rule, incident) && rule.lowestSeverityCode <= code), + Arr.filter(rules, (rule) => anomalyScopeCovers(rule, incident) && rule.lowestSeverityCode <= code), candidateOrder(), ) const best = Arr.head(qualifying) @@ -122,6 +126,9 @@ const SIGNAL_NAMES = { error_spike: "error spike", } satisfies Record +/** A detector signal as people say it, e.g. "p95 latency". */ +export const anomalySignalName = (signalType: AnomalySignalType): string => SIGNAL_NAMES[signalType] + /** How an anomaly's series is named where a group would be, e.g. "checkout (production)". */ export const anomalyGroupLabel = (incident: { readonly serviceName: string From 6b795654c6b64c19d57bd58445399268214bbbcf Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:28:10 +0100 Subject: [PATCH 14/28] fix(alerts): default rule message says scoped rules still page --- packages/backend/src/services/alerts/anomaly/anomaly-rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts index cbb03c8dc9..52e75b21b3 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -36,7 +36,7 @@ export const DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE = export const DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE = "The default anomaly rule covers every anomaly and cannot be scoped; create a scoped anomaly rule instead" export const DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE = - "The default anomaly rule cannot be deleted or disabled. Leave it with no destinations to keep anomalies in the app only, or turn detection off with PATCH /v2/anomalies/settings (enabled: false)" + "The default anomaly rule cannot be deleted or disabled. Leave it with no destinations so the anomalies it handles only show in the app (scoped anomaly rules still page), or turn detection off with PATCH /v2/anomalies/settings (enabled: false)" /** What an anomaly rule pages on. Each empty list matches everything. */ export interface AnomalyRuleScope { From 395c4c420a2d225f97aa2bb7ca9ded3663573166 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:29:40 +0100 Subject: [PATCH 15/28] fix(web): anomaly view counts and copy follow the service filter; no preview for anomaly incidents --- apps/web/src/page/alerts/hub/derive.ts | 2 +- .../page/alerts/hub/hub-anomalies.scene.test.ts | 14 ++++++++++++++ apps/web/src/page/alerts/hub/view-anomalies.ts | 2 +- apps/web/src/page/alerts/hub/view.ts | 7 ++++++- apps/web/src/page/alerts/incident/derive.ts | 2 +- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/web/src/page/alerts/hub/derive.ts b/apps/web/src/page/alerts/hub/derive.ts index 4791abcf55..d60fb79a67 100644 --- a/apps/web/src/page/alerts/hub/derive.ts +++ b/apps/web/src/page/alerts/hub/derive.ts @@ -45,7 +45,7 @@ export const overviewOf = (model: Model): Option.Option => overviewFor([model.rules, model.states, model.incidents, model.deliveries.data, model.nowMs]) /** An incident is about a service: its labels say so, else its stored "service (env)" label. */ -const isAboutService = (incident: AlertIncidentDocument, serviceName: string): boolean => +export const isAboutService = (incident: AlertIncidentDocument, serviceName: string): boolean => incident.labels?.service !== undefined ? incident.labels.service === serviceName : incident.groupLabel === serviceName || diff --git a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts index 45fc208ac9..b5485f355d 100644 --- a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts +++ b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts @@ -155,6 +155,20 @@ describe("alerts hub: anomalies", () => { ) }) + test("narrowed to a service, the chip counts and the empty state speak for that service", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", serviceName: "checkout" })), + expectView(role("button", { name: /^Anomalies\s*1$/ })).toExist(), + ) + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", serviceName: "payments" })), + expectView(text(/on payments, and opens an anomaly/)).toExist(), + expectView(text(/every service/)).not.toExist(), + ) + }) + test("with only resolved anomalies the open view still says there are none", () => { scene( { update: pageUpdate, view }, diff --git a/apps/web/src/page/alerts/hub/view-anomalies.ts b/apps/web/src/page/alerts/hub/view-anomalies.ts index 69cbb1156e..1910b64bb7 100644 --- a/apps/web/src/page/alerts/hub/view-anomalies.ts +++ b/apps/web/src/page/alerts/hub/view-anomalies.ts @@ -96,7 +96,7 @@ const anomaliesEmpty = (h: H, status: IncidentStatusFilter, serviceName: string ...(serviceName === undefined ? [] : [` on ${serviceName}`]), ]), emptyDescription(h, [ - "Maple watches error rate, latency, throughput, error spikes and log volume on every service, and opens an anomaly here when one looks unusual against its baseline.", + `Maple watches error rate, latency, throughput, error spikes and log volume on ${serviceName ?? "every service"}, and opens an anomaly here when one looks unusual against its baseline.`, ]), ]), ], diff --git a/apps/web/src/page/alerts/hub/view.ts b/apps/web/src/page/alerts/hub/view.ts index 4f59192910..c9b0b37348 100644 --- a/apps/web/src/page/alerts/hub/view.ts +++ b/apps/web/src/page/alerts/hub/view.ts @@ -20,6 +20,7 @@ import { type AlertsTab, anomalyOverviewOf, destinationsOf, + isAboutService, overviewOf, resolveIncidentStatus, resolveSignal, @@ -300,9 +301,13 @@ const hubBody = ( signal, status: resolveIncidentStatus(model.search.anomalyStatus), serviceName: model.search.serviceName, + // Counted under the same service filter the list applies. openAnomalies: Arr.filter( data.openIncidents, - (incident) => incident.signalType === "anomaly", + (incident) => + incident.signalType === "anomaly" && + (model.search.serviceName === undefined || + isAboutService(incident, model.search.serviceName)), ).length, }) const body = diff --git a/apps/web/src/page/alerts/incident/derive.ts b/apps/web/src/page/alerts/incident/derive.ts index 4d1a566dc4..8a7171bcd2 100644 --- a/apps/web/src/page/alerts/incident/derive.ts +++ b/apps/web/src/page/alerts/incident/derive.ts @@ -99,7 +99,7 @@ const deriveIncidentView = (model: ViewKey): Option.Option => { }), // A monitor's or SLO's rule has no query to replay: its signal is the checks or the burn rate. payload: - rule === null || isManagedSignalType(incident.signalType) + rule === null || isManagedSignalType(incident.signalType) || incident.signalType === "anomaly" ? Option.none() : previewPayload(ruleToFormState(rule), { startTime: formatWarehouseDateTime(range.start), From 48582b666f2a9335302f681d5be820ac05e677bd Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:35:55 +0100 Subject: [PATCH 16/28] chore(db): merge the anomaly rules and escalation lease migrations --- .../migration.sql | 2 + .../snapshot.json | 19346 ++++++++++++++++ 2 files changed, 19348 insertions(+) create mode 100644 packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/migration.sql create mode 100644 packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/snapshot.json diff --git a/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/migration.sql b/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/migration.sql new file mode 100644 index 0000000000..83f3392e3e --- /dev/null +++ b/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/migration.sql @@ -0,0 +1,2 @@ +-- Custom SQL migration. Separate statements with a line holding only: +-- --> statement-breakpoint diff --git a/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/snapshot.json b/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/snapshot.json new file mode 100644 index 0000000000..5ab6982242 --- /dev/null +++ b/packages/db/drizzle/20261106203538_merge_anomaly_rules_escalation_lease/snapshot.json @@ -0,0 +1,19346 @@ +{ + "version": "1", + "dialect": "postgres", + "id": "4c8ba36f4314b95b0eb0a3501615bc288b0028df9841e8215a20ec6802d9601b", + "prevIds": [ + "821f37945cb8306d87f3ba44b0d3519a19db6b8ed2656ff04126126e4c28c648", + "02e2840f10a2adcc176610d397711485961c812639c4e7a1184ee78d75e0de95" + ], + "entities": [ + { + "kind": "table", + "name": "actors", + "primaryKey": { + "name": "actors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "agent_feedback", + "primaryKey": { + "name": "agent_feedback_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "ai_triage_settings", + "primaryKey": { + "name": "ai_triage_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "alert_delivery_events", + "primaryKey": { + "name": "alert_delivery_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_destinations", + "primaryKey": { + "name": "alert_destinations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incident_events", + "primaryKey": { + "name": "alert_incident_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incidents", + "primaryKey": { + "name": "alert_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_linear_issues", + "primaryKey": { + "name": "alert_linear_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_claims", + "primaryKey": { + "name": "alert_rule_claims_pkey", + "columns": [ + "rule_id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_states", + "primaryKey": { + "name": "alert_rule_states_org_id_rule_id_group_key_pk", + "columns": [ + "org_id", + "rule_id", + "group_key" + ] + } + }, + { + "kind": "table", + "name": "alert_rules", + "primaryKey": { + "name": "alert_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_silences", + "primaryKey": { + "name": "alert_silences_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_settings", + "primaryKey": { + "name": "anomaly_detector_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_states", + "primaryKey": { + "name": "anomaly_detector_states_org_id_detector_key_pk", + "columns": [ + "org_id", + "detector_key" + ] + } + }, + { + "kind": "table", + "name": "anomaly_incidents", + "primaryKey": { + "name": "anomaly_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "api_keys", + "primaryKey": { + "name": "api_keys_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "aws_connections", + "primaryKey": { + "name": "aws_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cancellation_reviews", + "primaryKey": { + "name": "cancellation_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_identities", + "primaryKey": { + "name": "chat_identities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_workspaces", + "primaryKey": { + "name": "chat_workspaces_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cli_device_authorizations", + "primaryKey": { + "name": "cli_device_authorizations_pkey", + "columns": [ + "device_code_hash" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_analytics_state", + "primaryKey": { + "name": "cloudflare_analytics_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_hyperdrive_configs", + "primaryKey": { + "name": "cloudflare_hyperdrive_configs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_logpush_connectors", + "primaryKey": { + "name": "cloudflare_logpush_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_zone_settings", + "primaryKey": { + "name": "cloudflare_zone_settings_pkey", + "columns": [ + "org_id", + "account_id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_shares", + "primaryKey": { + "name": "dashboard_shares_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_versions", + "primaryKey": { + "name": "dashboard_versions_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboards", + "primaryKey": { + "name": "dashboards_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_findings", + "primaryKey": { + "name": "dependency_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scan_matches", + "primaryKey": { + "name": "dependency_scan_matches_pkey", + "columns": [ + "scan_id", + "lockfile_path", + "ecosystem", + "package_name", + "version", + "advisory_id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scans", + "primaryKey": { + "name": "dependency_scans_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_credentials", + "primaryKey": { + "name": "device_credentials_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_proof_replays", + "primaryKey": { + "name": "device_proof_replays_pkey", + "columns": [ + "key" + ] + } + }, + { + "kind": "table", + "name": "digest_subscriptions", + "primaryKey": { + "name": "digest_subscriptions_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_fingerprint_candidates", + "primaryKey": { + "name": "error_fingerprint_candidates_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_incidents", + "primaryKey": { + "name": "error_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_events", + "primaryKey": { + "name": "error_issue_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_fingerprints", + "primaryKey": { + "name": "error_issue_fingerprints_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_issue_pull_requests", + "primaryKey": { + "name": "error_issue_pull_requests_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_states", + "primaryKey": { + "name": "error_issue_states_org_id_issue_id_pk", + "columns": [ + "org_id", + "issue_id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_verifications", + "primaryKey": { + "name": "error_issue_verifications_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issues", + "primaryKey": { + "name": "error_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_deliveries", + "primaryKey": { + "name": "error_notification_deliveries_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_policies", + "primaryKey": { + "name": "error_notification_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "error_tick_states", + "primaryKey": { + "name": "error_tick_states_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "gcp_connectors", + "primaryKey": { + "name": "gcp_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "gcp_resources", + "primaryKey": { + "name": "gcp_resources_pkey", + "columns": [ + "connector_id", + "name" + ] + } + }, + { + "kind": "table", + "name": "investigations", + "primaryKey": { + "name": "investigations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalation_policies", + "primaryKey": { + "name": "issue_escalation_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalations", + "primaryKey": { + "name": "issue_escalations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "live_activities", + "primaryKey": { + "name": "live_activities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_authorizations", + "primaryKey": { + "name": "mcp_oauth_authorizations_pkey", + "columns": [ + "request_id_hash" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_clients", + "primaryKey": { + "name": "mcp_oauth_clients_pkey", + "columns": [ + "client_id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_refresh_tokens", + "primaryKey": { + "name": "mcp_oauth_refresh_tokens_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mobile_devices", + "primaryKey": { + "name": "mobile_devices_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oauth_auth_states", + "primaryKey": { + "name": "oauth_auth_states_pkey", + "columns": [ + "state" + ] + } + }, + { + "kind": "table", + "name": "oauth_connections", + "primaryKey": { + "name": "oauth_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_billing_links", + "primaryKey": { + "name": "org_billing_links_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_schema_apply_runs", + "primaryKey": { + "name": "org_clickhouse_schema_apply_runs_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_settings", + "primaryKey": { + "name": "org_clickhouse_settings_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_attribute_mappings", + "primaryKey": { + "name": "org_ingest_attribute_mappings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_keys", + "primaryKey": { + "name": "org_ingest_keys_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_settings", + "primaryKey": { + "name": "org_ingest_rule_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_stats", + "primaryKey": { + "name": "org_ingest_rule_stats_pkey", + "columns": [ + "org_id", + "rule_id", + "bucket", + "signal" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rules", + "primaryKey": { + "name": "org_ingest_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_sampling_policies", + "primaryKey": { + "name": "org_ingest_sampling_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_onboarding_state", + "primaryKey": { + "name": "org_onboarding_state_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_openrouter_keys", + "primaryKey": { + "name": "org_openrouter_keys_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_recommendation_issues", + "primaryKey": { + "name": "org_recommendation_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_support_channels", + "primaryKey": { + "name": "org_support_channels_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_connections", + "primaryKey": { + "name": "planetscale_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_databases", + "primaryKey": { + "name": "planetscale_databases_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_events", + "primaryKey": { + "name": "planetscale_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_issue_receipts", + "primaryKey": { + "name": "planetscale_issue_receipts_org_id_event_id_pk", + "columns": [ + "org_id", + "event_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_poll_state", + "primaryKey": { + "name": "planetscale_poll_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_edits", + "primaryKey": { + "name": "pr_review_edits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_finding_embeddings", + "primaryKey": { + "name": "pr_review_finding_embeddings_pkey", + "columns": [ + "finding_id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_findings", + "primaryKey": { + "name": "pr_review_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_merge_steps", + "primaryKey": { + "name": "pr_review_merge_steps_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_replies", + "primaryKey": { + "name": "pr_review_replies_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_settings", + "primaryKey": { + "name": "pr_review_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "pr_reviews", + "primaryKey": { + "name": "pr_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_connections", + "primaryKey": { + "name": "railway_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_environments", + "primaryKey": { + "name": "railway_environments_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_target_checks", + "primaryKey": { + "name": "scrape_target_checks_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_targets", + "primaryKey": { + "name": "scrape_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_events", + "primaryKey": { + "name": "security_audit_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_findings", + "primaryKey": { + "name": "security_audit_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_reservations", + "primaryKey": { + "name": "security_audit_org_reservations_pkey", + "columns": [ + "run_id", + "agent_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_settings", + "primaryKey": { + "name": "security_audit_org_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_run_findings", + "primaryKey": { + "name": "security_audit_run_findings_pkey", + "columns": [ + "run_id", + "fingerprint" + ] + } + }, + { + "kind": "table", + "name": "security_audit_runs", + "primaryKey": { + "name": "security_audit_runs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_targets", + "primaryKey": { + "name": "security_audit_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_units", + "primaryKey": { + "name": "security_audit_units_pkey", + "columns": [ + "run_id", + "coverage_id" + ] + } + }, + { + "kind": "table", + "name": "slos", + "primaryKey": { + "name": "slos_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "sources", + "primaryKey": { + "name": "sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_cleanups", + "primaryKey": { + "name": "status_page_cleanups_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_components", + "primaryKey": { + "name": "status_page_components_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_components", + "primaryKey": { + "name": "status_page_incident_components_pk", + "columns": [ + "incident_id", + "component_id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_updates", + "primaryKey": { + "name": "status_page_incident_updates_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incidents", + "primaryKey": { + "name": "status_page_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_pages", + "primaryKey": { + "name": "status_pages_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_connections", + "primaryKey": { + "name": "supabase_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_project_insights", + "primaryKey": { + "name": "supabase_project_insights_pkey", + "columns": [ + "project_id" + ] + } + }, + { + "kind": "table", + "name": "supabase_projects", + "primaryKey": { + "name": "supabase_projects_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_daily", + "primaryKey": { + "name": "uptime_monitor_daily_pk", + "columns": [ + "monitor_id", + "day" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_states", + "primaryKey": { + "name": "uptime_monitor_states_pkey", + "columns": [ + "monitor_id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitors", + "primaryKey": { + "name": "uptime_monitors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_commits", + "primaryKey": { + "name": "vcs_commits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_installations", + "primaryKey": { + "name": "vcs_installations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repositories", + "primaryKey": { + "name": "vcs_repositories_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repository_branches", + "primaryKey": { + "name": "vcs_repository_branches_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "column", + "table": "actors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "user_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "agent_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "model", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "capabilities_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "last_active_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "impact", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "summary", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "reason", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "details", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "related_to", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_type", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_name", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_model", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_version", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_runs_per_day", + "position": 2, + "type": "integer", + "notNull": true, + "default": "20", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_passes_per_day", + "position": 3, + "type": "integer", + "notNull": true, + "default": "90", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_by", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "rule_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "destination_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "delivery_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempt_number", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "scheduled_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claim_expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_by", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempted_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_message", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_reference", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "response_code", + "position": 16, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "error_message", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "silence_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "payload_json", + "position": 19, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "config_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_tested_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_test_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "consecutive_failures", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_failure_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "source", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "incident_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_key", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "signal_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "severity", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "peak_severity", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "comparator", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold_upper", + "position": 12, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "first_triggered_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_triggered_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "resolved_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_observed_value", + "position": 16, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_sample_count", + "position": 17, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_evaluated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "dedupe_key", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_delivered_event_type", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_notified_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "hold_reason", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "held_since", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_user_id", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_name", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_via", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "error_issue_id", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "anomaly_incident_id", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_label", + "position": 30, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silence_id", + "position": 31, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silenced_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "created_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "updated_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "thread_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_team_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_issue_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_identifier", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "rule_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "last_scheduled_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "consecutive_limit_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "group_key", + "position": 2, + "type": "text", + "notNull": true, + "default": "'__total__'", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_breaches", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_healthy", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "history_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_status", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_value", + "position": 7, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_sample_count", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_evaluated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error_kind", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notes", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notification_template_json", + "position": 4, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "severity", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "service_names_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "exclude_service_names_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "environments_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "anomaly_signals_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "tags_json", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "signal_type", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "comparator", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold_upper", + "position": 15, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "thresholds_json", + "position": 16, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "window_minutes", + "position": 17, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "evaluation_interval_minutes", + "position": 18, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_interval_minutes", + "position": 19, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "minimum_sample_count", + "position": 21, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_breaches_required", + "position": 22, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "breaches_out_of", + "position": 23, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_healthy_required", + "position": 24, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "renotify_interval_minutes", + "position": 25, + "type": "integer", + "notNull": true, + "default": "30", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "apdex_threshold_ms", + "position": 26, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_builder_draft_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "raw_query_sql", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "group_by", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "destination_ids_json", + "position": 30, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_spec_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "reducer", + "position": 32, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "sample_count_strategy", + "position": 33, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "no_data_behavior", + "position": 34, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "last_scheduled_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_at", + "position": 36, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_at", + "position": 37, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_by", + "position": 38, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_by", + "position": 39, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "reason", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "matchers_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "starts_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "ends_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_by", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "sensitivity", + "position": 2, + "type": "text", + "notNull": true, + "default": "'normal'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "muted_signals_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "last_tick_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "detector_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "signal_type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "service_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "deployment_env", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "fingerprint_hash", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_breaches", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_healthy", + "position": 7, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_value", + "position": 9, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "baseline_median", + "position": 10, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_sample_count", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_evaluated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "open_incident_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_resolved_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_incident_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "detector_key", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "signal_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "deployment_env", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprint_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "error_issue_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "opened_value", + "position": 10, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_median", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_sigma", + "position": 12, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "threshold_value", + "position": 13, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_observed_value", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_sample_count", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "first_triggered_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_triggered_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolved_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolve_reason", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "triage_status", + "position": 20, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "dedupe_key", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprints_json", + "position": 22, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "reopen_count", + "position": 23, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_reopened_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "created_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "updated_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_prefix", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "last_used_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "metadata_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "scopes", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "kind", + "position": 12, + "type": "text", + "notNull": true, + "default": "'standard'", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by_email", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "account_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "modules", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "granted_modules", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "template_version", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "verified_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_checked_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_check_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "check_lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream_changed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_hash", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_received_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "plan_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "subscription_started_at", + "position": 3, + "type": "bigint", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "canceled_at", + "position": 4, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "rule_reason", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "model_read", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "posted_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "external_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "display_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "external_workspace_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "settings", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_code_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "user_code_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_org_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_roles", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_email", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "api_key_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_ciphertext", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_iv", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_tag", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "denied_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "dataset", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_id", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "watermark_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backfill_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_json", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_fetched_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "quantiles_available", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "discovered_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "live_scripts_json", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "grant_call_cap", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_success_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "config_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_host", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_port", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_scheme", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_database", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_user", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "deleted_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "zone_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "service_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "dataset", + "position": 6, + "type": "text", + "notNull": true, + "default": "'http_requests'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_ciphertext", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_iv", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_tag", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_hash", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "enabled", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_rotated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "account_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "watch_new_zones", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "zone_overrides", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "widget_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "mode", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_hash", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_suffix", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "version_number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "snapshot_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_summary", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "source_version_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "payload_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "version", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "lockfile_path", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "ecosystem", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "package_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "advisory_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "aliases", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "severity", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "summary", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "versions", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "relation", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_scan_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_scan_id", + "position": 15, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_sha", + "position": 16, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_sha", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_scan_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_sha", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_reason", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_by", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "scan_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "lockfile_path", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "ecosystem", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "package_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "version", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "relation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "advisory_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "aliases", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "summary", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "commit_sha", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "ref", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "trigger", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "default_branch_head", + "position": 6, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'running'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "lockfiles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "package_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "vulnerable_count", + "position": 10, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "started_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "finished_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "public_jwk", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "jkt", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "roles", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "scopes", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attested", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attestation", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "last_used_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "expires_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "hard_expires_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "key", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "expires_at", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "email", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "opted_out_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "day_of_week", + "position": 6, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "timezone", + "position": 7, + "type": "text", + "notNull": true, + "default": "'UTC'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "send_hour", + "position": 8, + "type": "integer", + "notNull": true, + "default": "9", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "namespaces_json", + "position": 9, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "environments_json", + "position": 10, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_attempted_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_opted_out_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_sent_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_attempted_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_message", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "error_label", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "top_frame", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "first_seen_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "last_seen_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "reason", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "first_triggered_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "last_triggered_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "resolved_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "actor_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "type", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "from_state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "to_state", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "payload_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "source", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "origin_issue_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "repo_full_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "number", + "position": 6, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "title", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "author_login", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "state", + "position": 10, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merged_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merge_commit_sha", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "link_source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "linked_by_actor_id", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "issue_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_observed_occurrence_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_evaluated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "open_incident_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "pull_request_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "status", + "position": 4, + "type": "text", + "notNull": true, + "default": "'waiting'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "merged_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verify_after", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_rate_per_hour", + "position": 9, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "investigation_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict_note", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "post_merge_occurrence_count", + "position": 13, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "attempt", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "kind", + "position": 2, + "type": "text", + "notNull": true, + "default": "'error'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "source_ref_json", + "position": 3, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_version", + "position": 5, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "service_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_type", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_message", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "error_label", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "top_frame", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "workflow_state", + "position": 11, + "type": "text", + "notNull": true, + "default": "'triage'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "priority", + "position": 12, + "type": "integer", + "notNull": true, + "default": "3", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity_source", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "assigned_actor_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_holder_actor_id", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "claimed_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "notes", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "first_seen_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "occurrence_count", + "position": 22, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_by_actor_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_resolved_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_regressed_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "regression_count", + "position": 27, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "seen_versions_json", + "position": 28, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_versions_json", + "position": 29, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "snooze_until", + "position": 30, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "archived_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "merged_into_issue_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "created_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "updated_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "delivery_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "payload_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "status", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempt_count", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "scheduled_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claim_expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempted_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "error_message", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "destination_ids_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_first_seen", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_regression", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_resolve", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_in_review", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_done", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_claim", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "min_occurrence_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "severity", + "position": 10, + "type": "text", + "notNull": true, + "default": "'warning'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "processed_through", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "bootstrap_completed", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_token", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_expires_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "project_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "logs_enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_ciphertext", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_iv", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_tag", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_hash", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_logs_enabled", + "position": 14, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_metrics_enabled", + "position": 15, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "setup_reported_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_watermark_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_received_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_lease_until", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "resources_synced_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_resources_error", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_by", + "position": 23, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "updated_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "connector_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "asset_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "project_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "location", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "display_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "labels", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "details", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "last_seen_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'investigating'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "seeded_by", + "position": 3, + "type": "text", + "notNull": true, + "default": "'user'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "subject_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_kind", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "issue_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "report_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "progress_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "severity", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "confidence", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "input_tokens", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "output_tokens", + "position": 15, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "started_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "autonomous_turns", + "position": 18, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "diagnosed_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "updated_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "rules_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "severity", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "source", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "reason", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "run_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "investigation_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "payload_json", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "delivery_results_json", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "attempts", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "dedupe_key", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "processed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "device_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "incident_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "activity_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "push_token", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_reason", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "request_id_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "redirect_uri", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "state", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "resource", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "scopes", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "code_challenge", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "authorization_code_hash", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_org_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_roles", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_email", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "denied_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "used_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_name", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "redirect_uris", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_uri", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "token_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "client_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "resource", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "scopes", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "org_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "roles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_email", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "access_key_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "replaced_by_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "revoked_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_expires_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "platform", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "token", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "environment", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "bundle_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "app_version", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "live_activity_start_token", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "device_name", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "preferences", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_reason", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_pushed_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_seen_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "state", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "initiated_by_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "redirect_uri", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "return_to", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "code_verifier", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "github_installation_ids", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_email", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_account_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "granted_accounts_json", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "connected_by_user_id", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "scope", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_ciphertext", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_iv", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_tag", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "expires_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "revoked_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "billing_org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "linked_by", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "created_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "workflow_instance_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "phase", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "current_migration", + "position": 4, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_total", + "position": 5, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_done", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "applied_versions", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "skipped", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "error_message", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "started_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "finished_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_url", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_user", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_ciphertext", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_iv", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_tag", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_database", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "sync_status", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "schema_version", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_context", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "target_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "operation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key_hash", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_hash", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_rotated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_rotated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "hash_salt", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "created_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "bucket", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "signal", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "matched", + "position": 4, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped", + "position": 5, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped_bytes", + "position": 6, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "redacted_values", + "position": 7, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "enabled", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "position", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "spec_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "trace_sample_ratio", + "position": 1, + "type": "double precision", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_error_spans", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_slow_spans_ms", + "position": 3, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "user_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "email", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "role", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "demo_data_requested", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "onboarding_completed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "checklist_dismissed_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "first_data_received_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "welcome_email_sent_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "connect_nudge_email_sent_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "stalled_email_sent_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "activation_email_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_reserved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_ciphertext", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_iv", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_tag", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_hint", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "number", + "position": 2, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "recommendation_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "source_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "canonical_key", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "usage_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "opened_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "resolved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_name", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reservation_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reserved_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_by_user_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "ps_organization", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "connected_by_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "scrape_target_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "detected_permissions_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": "'mysql'", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "region", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "plan", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "branches_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_ciphertext", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_iv", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_tag", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "deleted_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "branch_name", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "category", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "external_id", + "position": 8, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "title", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "source", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "actor_login", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "payload_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "occurred_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "event_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "processed_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "dataset", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "database_id", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "watermark_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_success_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "lease_until", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "reply_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "seq", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "path", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "old_text", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "new_text", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "finding_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "embedding", + "position": 4, + "type": "real[]", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "review_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "handle", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "path", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "line", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "category", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "title", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "status", + "position": 11, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "comment_id", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "resolved_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_up", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_down", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "updated_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "title", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "source", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "path", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "reminded_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "first_review_id", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "last_review_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "comment_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "surface", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "thread_root_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "author_login", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "command", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "head_sha", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "reply_url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "commit_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "model", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "defaults", + "position": 2, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "head_sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "base_sha", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "url", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "title", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "skip_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_attempt", + "position": 12, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "report_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "score", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "check_run_url", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_url", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "review_url", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "input_tokens", + "position": 21, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "output_tokens", + "position": 22, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "started_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "finished_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merged_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "telemetry_json", + "position": 26, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merge_commit_sha", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_status", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_after", + "position": 29, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_json", + "position": 30, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "created_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "updated_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "workspace_names", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "connected_by_user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "auth_failed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "discovered_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_success_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "services_json", + "position": 7, + "type": "text", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "enabled", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "watermark_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_success_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "id", + "position": 0, + "type": "integer", + "notNull": true, + "default": null, + "identity": "by default" + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "target_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "sub_target_key", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "error", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "duration_ms", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_scraped", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_post_relabel", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "url", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "target_type", + "position": 6, + "type": "text", + "notNull": true, + "default": "'prometheus'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "discovery_config_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "scrape_interval_seconds", + "position": 8, + "type": "integer", + "notNull": true, + "default": "15", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "labels_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_type", + "position": 10, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "managed_by", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_ciphertext", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_iv", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_tag", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_error", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "run_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "role", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "agent_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "outcome", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "detail", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "input_tokens", + "position": 9, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "output_tokens", + "position": 10, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fingerprint", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "verdict", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "overall_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "estimated_severity", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "record", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_run_id", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_run_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_ref", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_ref", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fixed_run_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_by", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "agent_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "cost_usd", + "position": 3, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "sandbox_fee_usd", + "position": 4, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "monthly_spend_limit_usd", + "position": 1, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "discovery_model", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "validation_model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "fingerprint", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "verdict", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "overall_severity", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "estimated_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "record", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "source_ref", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "profile", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "scope_paths", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "budget", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "metadata", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'in_progress'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "incomplete_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "architecture_md", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "reports", + "position": 12, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "execution_profile", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "invocations_spent", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "input_tokens", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "output_tokens", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "cost_usd", + "position": 17, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "sandbox_usd", + "position": 18, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "discovery_model", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "validation_model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "workflow_instance_id", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "started_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "finished_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "error", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "repository", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "vcs_repository_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "branch", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "profile", + "position": 6, + "type": "text", + "notNull": true, + "default": "'deep'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "scope_paths", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "slice_interval_hours", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "revalidate_on_push", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "budget_per_run", + "position": 10, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "monthly_invocation_budget", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "discovery_model", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "validation_model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "coverage_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "wave", + "position": 4, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "agent_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "unit", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "environments_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "sli_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "target_percent", + "position": 7, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "window_days", + "position": 8, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "alert_rule_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "platform", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_prefix", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "allowed_signals", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rate_limit_rps", + "position": 9, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "resource_attributes", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_until", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_used_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejected_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejection", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_ok_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "disconnected_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "poll_resources", + "position": 21, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rolled_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_by", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_by", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "delete_snapshot", + "position": 4, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "group_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "position", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "monitor_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "manual_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "show_uptime", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "incident_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "component_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "body", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "source", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "overlay_entry_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "title", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "impact", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "published", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "alert_incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_start", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_end", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "started_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "resolved_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "website_url", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "logo_url", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "privacy_url", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "terms_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "theme", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'{\"preset\":\"default\",\"appearance\":\"auto\",\"accent\":null,\"background\":null,\"text\":null,\"card\":null,\"border\":null,\"radius\":\"md\",\"font\":\"sans\"}'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "show_powered_by", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "auto_incidents", + "position": 11, + "type": "text", + "notNull": true, + "default": "'off'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_sha256", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_suffix", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "draft", + "position": 14, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "revision", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_revision", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "last_publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_hash", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "publish_attempted_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_by", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "updated_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_slug", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "connected_by_user_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "discovery_lease_until", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "project_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "top_queries_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_checked_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_error", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "lease_until", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "project_ref", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "region", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "scrape_target_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_enabled", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_source_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_json", + "position": 13, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_checked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_watermark_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_boundary_ids", + "position": 17, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_last_polled_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_error", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "removed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "day", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "checks", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "failures", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "down_checks", + "position": 5, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "last_check_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_successes", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status_changed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_check_json", + "position": 6, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "recent_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_ping_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "config_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "interval_seconds", + "position": 5, + "type": "integer", + "notNull": true, + "default": "60", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "failures_to_down", + "position": 6, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "service_name", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "alert_rule_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "heartbeat_token", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "enabled", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "message", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_email", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_avatar_url", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "authored_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "committed_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "html_url", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_installation_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_login", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_account_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_avatar_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_selection", + "position": 9, + "type": "text", + "notNull": true, + "default": "'all'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "suspended_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "ci_traces_enabled", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_discovery", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "installed_by_user_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "owner", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "full_name", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "default_branch", + "position": 8, + "type": "text", + "notNull": true, + "default": "'main'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "tracked_branch", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "html_url", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_private", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_archived", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "sync_status", + "position": 14, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_synced_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_sync_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_enabled", + "position": 17, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_config", + "position": 18, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "is_default", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "head_sha", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_agent_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"agent_name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_kind_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"kind\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"claim_expires_at\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_delivery_attempt_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"attempt_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_incident_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"incident_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_open_group_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"", + "\"group_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_rule_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_thread_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"destination_id\"", + "\"thread_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_claims", + "name": "alert_rule_claims_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_states", + "name": "alert_rule_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_silences", + "name": "alert_silences_org_ends_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"ends_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_evaluated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"last_evaluated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_open_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"open_incident_id\"" + ], + "where": "\"open_incident_id\" is not null" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_open_detector_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_detector_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_status_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"", + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_org_account_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_stream_secret_hash_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"stream_secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cancellation_reviews", + "name": "cancellation_reviews_subscription_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"plan_id\"", + "\"subscription_started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"external_user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connector\"", + "\"external_workspace_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_user_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"user_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_account_dataset_zone_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"", + "\"dataset\"", + "\"zone_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_config_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"config_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_live_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "coalesce(widget_id, '')" + ], + "where": "revoked_at is null" + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_token_hash_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_version_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_name_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"lockfile_path\"", + "\"ecosystem\"", + "\"package_name\"", + "\"advisory_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scan_matches", + "name": "dependency_scan_matches_org_scan_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scan_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_commit_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"commit_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_jkt_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"jkt\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_live_installation_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"installation_id\"" + ], + "where": "\"revoked_at\" IS NULL" + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_proof_replays", + "name": "device_proof_replays_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_fingerprint_candidates", + "name": "error_fingerprint_candidates_last_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_actor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"actor_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_fingerprints", + "name": "error_issue_fingerprints_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_pr_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"verify_after\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_open_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": "\"status\" in ('waiting', 'running')" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_lease_expiry_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"lease_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_archived_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"archived_at\"" + ], + "where": "\"archived_at\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_assignee_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"assigned_actor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_version_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_version\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_live_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"", + "\"id\"" + ], + "where": "\"archived_at\" is null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_merged_into_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"merged_into_issue_id\"" + ], + "where": "\"merged_into_issue_id\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_severity_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"severity\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_workflow_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"workflow_state\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"", + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_key_destination_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"destination_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_tick_states", + "name": "error_tick_states_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_org_scope_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scope_type\"", + "\"scope_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_incident_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_kind\"", + "\"incident_id\"" + ], + "where": "\"incident_id\" is not null" + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"dedupe_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_device_incident_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"device_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"authorization_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_family_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"family_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_platform_token_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"platform\"", + "\"token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_auth_states", + "name": "oauth_auth_states_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_provider_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_billing_links", + "name": "org_billing_links_billing_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"billing_org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_attribute_mappings", + "name": "org_ingest_attribute_mappings_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_private_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"private_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rule_stats", + "name": "org_ingest_rule_stats_org_bucket_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"bucket\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rules", + "name": "org_ingest_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"recommendation_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"event_type\"", + "\"external_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_db_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_issue_receipts", + "name": "planetscale_issue_receipts_processed_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"processed_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_dataset_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dataset\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_edits", + "name": "pr_review_edits_reply_seq_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"reply_id\"", + "\"seq\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_finding_embeddings", + "name": "pr_review_finding_embeddings_org_model_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"model\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_pr_handle_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"handle\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_pr_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_repo_comment_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"comment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_post_merge_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"post_merge_after\"" + ], + "where": "\"post_merge_status\" = 'waiting'" + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_head_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"head_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_connection_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"connection_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_org_env_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"environment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_checked_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"checked_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_events", + "name": "security_audit_events_run_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"run_id\"", + "\"at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_target_fingerprint_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"fingerprint\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_org_reservations", + "name": "security_audit_org_reservations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_one_running_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"" + ], + "where": "\"finished_at\" IS NULL" + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_target_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_repo_branch_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"repository\"", + "\"branch\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_name_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_default_kind_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": "\"kind\" = 'default'" + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_cleanups", + "name": "status_page_cleanups_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_monitor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"monitor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"position\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_components", + "name": "status_page_incident_components_component_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"component_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_overlay_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"overlay_entry_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_alert_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"alert_incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_slug_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"slug\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_unpublished_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"revision\"", + "\"published_revision\"" + ], + "where": "revision > published_revision" + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_project_insights", + "name": "supabase_project_insights_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_connection_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connection_id\"", + "\"project_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"log_drain_source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_enabled_kind_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"kind\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_heartbeat_token_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"heartbeat_token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_org_sha_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_repo_sha_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_provider_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"provider\"", + "\"external_installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_installation_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_repo_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"external_repo_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_repo_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "foreign_key", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_linear_issues", + "name": "alert_linear_issues_destination_id_alert_destinations_id_fk", + "columns": [ + "destination_id" + ], + "foreignTable": "alert_destinations", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "aws_connections", + "name": "aws_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "dashboard_shares", + "name": "dashboard_shares_dashboard_fk", + "columns": [ + "org_id", + "dashboard_id" + ], + "foreignTable": "dashboards", + "foreignColumns": [ + "org_id", + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_connectors", + "name": "gcp_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_resources", + "name": "gcp_resources_connector_id_gcp_connectors_id_fk", + "columns": [ + "connector_id" + ], + "foreignTable": "gcp_connectors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oauth_connections", + "name": "oauth_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "planetscale_connections", + "name": "planetscale_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "railway_connections", + "name": "railway_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_id_scrape_targets_id_fk", + "columns": [ + "target_id" + ], + "foreignTable": "scrape_targets", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_targets", + "name": "scrape_targets_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_Bnqlt6wwKJty_fk", + "columns": [ + "component_id" + ], + "foreignTable": "status_page_components", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_tBn6mEz4FenY_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_3LDDUAhznHPM_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incidents", + "name": "status_page_incidents_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_connections", + "name": "supabase_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_project_insights", + "name": "supabase_project_insights_project_id_supabase_projects_id_fk", + "columns": [ + "project_id" + ], + "foreignTable": "supabase_projects", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_connection_id_supabase_connections_id_fk", + "columns": [ + "connection_id" + ], + "foreignTable": "supabase_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_id_sources_id_fk", + "columns": [ + "log_drain_source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vcs_installations", + "name": "vcs_installations_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + } + ] +} From e40ff81287be09bd2ab3fd07be0bb71089c304c3 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:36:14 +0100 Subject: [PATCH 17/28] chore(db): merge the incident labels migration with main --- .../migration.sql | 2 + .../snapshot.json | 19356 ++++++++++++++++ 2 files changed, 19358 insertions(+) create mode 100644 packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/migration.sql create mode 100644 packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/snapshot.json diff --git a/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/migration.sql b/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/migration.sql new file mode 100644 index 0000000000..83f3392e3e --- /dev/null +++ b/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/migration.sql @@ -0,0 +1,2 @@ +-- Custom SQL migration. Separate statements with a line holding only: +-- --> statement-breakpoint diff --git a/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/snapshot.json b/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/snapshot.json new file mode 100644 index 0000000000..d68469183b --- /dev/null +++ b/packages/db/drizzle/20261106203611_merge_incident_labels_escalation_lease/snapshot.json @@ -0,0 +1,19356 @@ +{ + "version": "1", + "dialect": "postgres", + "id": "de89fe57ed4daf8d23c41708dff07ed402ecd6c814287d6273b59411b41c8031", + "prevIds": [ + "739d574878ec6441261d585e9850a90950750552456730812038a7f04f57b4d2", + "4c8ba36f4314b95b0eb0a3501615bc288b0028df9841e8215a20ec6802d9601b" + ], + "entities": [ + { + "kind": "table", + "name": "actors", + "primaryKey": { + "name": "actors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "agent_feedback", + "primaryKey": { + "name": "agent_feedback_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "ai_triage_settings", + "primaryKey": { + "name": "ai_triage_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "alert_delivery_events", + "primaryKey": { + "name": "alert_delivery_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_destinations", + "primaryKey": { + "name": "alert_destinations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incident_events", + "primaryKey": { + "name": "alert_incident_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incidents", + "primaryKey": { + "name": "alert_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_linear_issues", + "primaryKey": { + "name": "alert_linear_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_claims", + "primaryKey": { + "name": "alert_rule_claims_pkey", + "columns": [ + "rule_id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_states", + "primaryKey": { + "name": "alert_rule_states_org_id_rule_id_group_key_pk", + "columns": [ + "org_id", + "rule_id", + "group_key" + ] + } + }, + { + "kind": "table", + "name": "alert_rules", + "primaryKey": { + "name": "alert_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_silences", + "primaryKey": { + "name": "alert_silences_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_settings", + "primaryKey": { + "name": "anomaly_detector_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_states", + "primaryKey": { + "name": "anomaly_detector_states_org_id_detector_key_pk", + "columns": [ + "org_id", + "detector_key" + ] + } + }, + { + "kind": "table", + "name": "anomaly_incidents", + "primaryKey": { + "name": "anomaly_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "api_keys", + "primaryKey": { + "name": "api_keys_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "aws_connections", + "primaryKey": { + "name": "aws_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cancellation_reviews", + "primaryKey": { + "name": "cancellation_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_identities", + "primaryKey": { + "name": "chat_identities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_workspaces", + "primaryKey": { + "name": "chat_workspaces_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cli_device_authorizations", + "primaryKey": { + "name": "cli_device_authorizations_pkey", + "columns": [ + "device_code_hash" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_analytics_state", + "primaryKey": { + "name": "cloudflare_analytics_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_hyperdrive_configs", + "primaryKey": { + "name": "cloudflare_hyperdrive_configs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_logpush_connectors", + "primaryKey": { + "name": "cloudflare_logpush_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_zone_settings", + "primaryKey": { + "name": "cloudflare_zone_settings_pkey", + "columns": [ + "org_id", + "account_id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_shares", + "primaryKey": { + "name": "dashboard_shares_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_versions", + "primaryKey": { + "name": "dashboard_versions_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboards", + "primaryKey": { + "name": "dashboards_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_findings", + "primaryKey": { + "name": "dependency_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scan_matches", + "primaryKey": { + "name": "dependency_scan_matches_pkey", + "columns": [ + "scan_id", + "lockfile_path", + "ecosystem", + "package_name", + "version", + "advisory_id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scans", + "primaryKey": { + "name": "dependency_scans_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_credentials", + "primaryKey": { + "name": "device_credentials_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_proof_replays", + "primaryKey": { + "name": "device_proof_replays_pkey", + "columns": [ + "key" + ] + } + }, + { + "kind": "table", + "name": "digest_subscriptions", + "primaryKey": { + "name": "digest_subscriptions_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_fingerprint_candidates", + "primaryKey": { + "name": "error_fingerprint_candidates_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_incidents", + "primaryKey": { + "name": "error_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_events", + "primaryKey": { + "name": "error_issue_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_fingerprints", + "primaryKey": { + "name": "error_issue_fingerprints_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_issue_pull_requests", + "primaryKey": { + "name": "error_issue_pull_requests_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_states", + "primaryKey": { + "name": "error_issue_states_org_id_issue_id_pk", + "columns": [ + "org_id", + "issue_id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_verifications", + "primaryKey": { + "name": "error_issue_verifications_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issues", + "primaryKey": { + "name": "error_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_deliveries", + "primaryKey": { + "name": "error_notification_deliveries_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_policies", + "primaryKey": { + "name": "error_notification_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "error_tick_states", + "primaryKey": { + "name": "error_tick_states_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "gcp_connectors", + "primaryKey": { + "name": "gcp_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "gcp_resources", + "primaryKey": { + "name": "gcp_resources_pkey", + "columns": [ + "connector_id", + "name" + ] + } + }, + { + "kind": "table", + "name": "investigations", + "primaryKey": { + "name": "investigations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalation_policies", + "primaryKey": { + "name": "issue_escalation_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalations", + "primaryKey": { + "name": "issue_escalations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "live_activities", + "primaryKey": { + "name": "live_activities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_authorizations", + "primaryKey": { + "name": "mcp_oauth_authorizations_pkey", + "columns": [ + "request_id_hash" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_clients", + "primaryKey": { + "name": "mcp_oauth_clients_pkey", + "columns": [ + "client_id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_refresh_tokens", + "primaryKey": { + "name": "mcp_oauth_refresh_tokens_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mobile_devices", + "primaryKey": { + "name": "mobile_devices_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oauth_auth_states", + "primaryKey": { + "name": "oauth_auth_states_pkey", + "columns": [ + "state" + ] + } + }, + { + "kind": "table", + "name": "oauth_connections", + "primaryKey": { + "name": "oauth_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_billing_links", + "primaryKey": { + "name": "org_billing_links_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_schema_apply_runs", + "primaryKey": { + "name": "org_clickhouse_schema_apply_runs_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_settings", + "primaryKey": { + "name": "org_clickhouse_settings_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_attribute_mappings", + "primaryKey": { + "name": "org_ingest_attribute_mappings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_keys", + "primaryKey": { + "name": "org_ingest_keys_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_settings", + "primaryKey": { + "name": "org_ingest_rule_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_stats", + "primaryKey": { + "name": "org_ingest_rule_stats_pkey", + "columns": [ + "org_id", + "rule_id", + "bucket", + "signal" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rules", + "primaryKey": { + "name": "org_ingest_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_sampling_policies", + "primaryKey": { + "name": "org_ingest_sampling_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_onboarding_state", + "primaryKey": { + "name": "org_onboarding_state_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_openrouter_keys", + "primaryKey": { + "name": "org_openrouter_keys_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_recommendation_issues", + "primaryKey": { + "name": "org_recommendation_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_support_channels", + "primaryKey": { + "name": "org_support_channels_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_connections", + "primaryKey": { + "name": "planetscale_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_databases", + "primaryKey": { + "name": "planetscale_databases_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_events", + "primaryKey": { + "name": "planetscale_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_issue_receipts", + "primaryKey": { + "name": "planetscale_issue_receipts_org_id_event_id_pk", + "columns": [ + "org_id", + "event_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_poll_state", + "primaryKey": { + "name": "planetscale_poll_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_edits", + "primaryKey": { + "name": "pr_review_edits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_finding_embeddings", + "primaryKey": { + "name": "pr_review_finding_embeddings_pkey", + "columns": [ + "finding_id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_findings", + "primaryKey": { + "name": "pr_review_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_merge_steps", + "primaryKey": { + "name": "pr_review_merge_steps_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_replies", + "primaryKey": { + "name": "pr_review_replies_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_settings", + "primaryKey": { + "name": "pr_review_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "pr_reviews", + "primaryKey": { + "name": "pr_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_connections", + "primaryKey": { + "name": "railway_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_environments", + "primaryKey": { + "name": "railway_environments_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_target_checks", + "primaryKey": { + "name": "scrape_target_checks_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_targets", + "primaryKey": { + "name": "scrape_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_events", + "primaryKey": { + "name": "security_audit_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_findings", + "primaryKey": { + "name": "security_audit_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_reservations", + "primaryKey": { + "name": "security_audit_org_reservations_pkey", + "columns": [ + "run_id", + "agent_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_settings", + "primaryKey": { + "name": "security_audit_org_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_run_findings", + "primaryKey": { + "name": "security_audit_run_findings_pkey", + "columns": [ + "run_id", + "fingerprint" + ] + } + }, + { + "kind": "table", + "name": "security_audit_runs", + "primaryKey": { + "name": "security_audit_runs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_targets", + "primaryKey": { + "name": "security_audit_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_units", + "primaryKey": { + "name": "security_audit_units_pkey", + "columns": [ + "run_id", + "coverage_id" + ] + } + }, + { + "kind": "table", + "name": "slos", + "primaryKey": { + "name": "slos_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "sources", + "primaryKey": { + "name": "sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_cleanups", + "primaryKey": { + "name": "status_page_cleanups_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_components", + "primaryKey": { + "name": "status_page_components_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_components", + "primaryKey": { + "name": "status_page_incident_components_pk", + "columns": [ + "incident_id", + "component_id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_updates", + "primaryKey": { + "name": "status_page_incident_updates_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incidents", + "primaryKey": { + "name": "status_page_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_pages", + "primaryKey": { + "name": "status_pages_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_connections", + "primaryKey": { + "name": "supabase_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_project_insights", + "primaryKey": { + "name": "supabase_project_insights_pkey", + "columns": [ + "project_id" + ] + } + }, + { + "kind": "table", + "name": "supabase_projects", + "primaryKey": { + "name": "supabase_projects_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_daily", + "primaryKey": { + "name": "uptime_monitor_daily_pk", + "columns": [ + "monitor_id", + "day" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_states", + "primaryKey": { + "name": "uptime_monitor_states_pkey", + "columns": [ + "monitor_id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitors", + "primaryKey": { + "name": "uptime_monitors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_commits", + "primaryKey": { + "name": "vcs_commits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_installations", + "primaryKey": { + "name": "vcs_installations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repositories", + "primaryKey": { + "name": "vcs_repositories_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repository_branches", + "primaryKey": { + "name": "vcs_repository_branches_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "column", + "table": "actors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "user_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "agent_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "model", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "capabilities_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "last_active_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "impact", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "summary", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "reason", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "details", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "related_to", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_type", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_name", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_model", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_version", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_runs_per_day", + "position": 2, + "type": "integer", + "notNull": true, + "default": "20", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_passes_per_day", + "position": 3, + "type": "integer", + "notNull": true, + "default": "90", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_by", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "rule_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "destination_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "delivery_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempt_number", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "scheduled_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claim_expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_by", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempted_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_message", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_reference", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "response_code", + "position": 16, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "error_message", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "silence_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "payload_json", + "position": 19, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "config_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_tested_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_test_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "consecutive_failures", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_failure_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "source", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "incident_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_key", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "signal_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "severity", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "peak_severity", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "comparator", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold_upper", + "position": 12, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "first_triggered_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_triggered_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "resolved_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_observed_value", + "position": 16, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_sample_count", + "position": 17, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_evaluated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "dedupe_key", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_delivered_event_type", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_notified_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "hold_reason", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "held_since", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_user_id", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_name", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_via", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "error_issue_id", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "anomaly_incident_id", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_label", + "position": 30, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "labels_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silence_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silenced_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "created_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "updated_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "thread_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_team_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_issue_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_identifier", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "rule_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "last_scheduled_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "consecutive_limit_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "group_key", + "position": 2, + "type": "text", + "notNull": true, + "default": "'__total__'", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_breaches", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_healthy", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "history_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_status", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_value", + "position": 7, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_sample_count", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_evaluated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error_kind", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notes", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notification_template_json", + "position": 4, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "severity", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "service_names_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "exclude_service_names_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "environments_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "anomaly_signals_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "tags_json", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "signal_type", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "comparator", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold_upper", + "position": 15, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "thresholds_json", + "position": 16, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "window_minutes", + "position": 17, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "evaluation_interval_minutes", + "position": 18, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_interval_minutes", + "position": 19, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "minimum_sample_count", + "position": 21, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_breaches_required", + "position": 22, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "breaches_out_of", + "position": 23, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_healthy_required", + "position": 24, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "renotify_interval_minutes", + "position": 25, + "type": "integer", + "notNull": true, + "default": "30", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "apdex_threshold_ms", + "position": 26, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_builder_draft_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "raw_query_sql", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "group_by", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "destination_ids_json", + "position": 30, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_spec_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "reducer", + "position": 32, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "sample_count_strategy", + "position": 33, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "no_data_behavior", + "position": 34, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "last_scheduled_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_at", + "position": 36, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_at", + "position": 37, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_by", + "position": 38, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_by", + "position": 39, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "reason", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "matchers_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "starts_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "ends_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_by", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "sensitivity", + "position": 2, + "type": "text", + "notNull": true, + "default": "'normal'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "muted_signals_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "last_tick_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "detector_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "signal_type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "service_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "deployment_env", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "fingerprint_hash", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_breaches", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_healthy", + "position": 7, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_value", + "position": 9, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "baseline_median", + "position": 10, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_sample_count", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_evaluated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "open_incident_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_resolved_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_incident_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "detector_key", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "signal_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "deployment_env", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprint_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "error_issue_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "opened_value", + "position": 10, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_median", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_sigma", + "position": 12, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "threshold_value", + "position": 13, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_observed_value", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_sample_count", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "first_triggered_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_triggered_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolved_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolve_reason", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "triage_status", + "position": 20, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "dedupe_key", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprints_json", + "position": 22, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "reopen_count", + "position": 23, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_reopened_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "created_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "updated_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_prefix", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "last_used_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "metadata_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "scopes", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "kind", + "position": 12, + "type": "text", + "notNull": true, + "default": "'standard'", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by_email", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "account_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "modules", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "granted_modules", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "template_version", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "verified_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_checked_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_check_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "check_lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream_changed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_hash", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_received_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "plan_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "subscription_started_at", + "position": 3, + "type": "bigint", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "canceled_at", + "position": 4, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "rule_reason", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "model_read", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "posted_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "external_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "display_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "external_workspace_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "settings", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_code_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "user_code_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_org_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_roles", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_email", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "api_key_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_ciphertext", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_iv", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_tag", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "denied_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "dataset", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_id", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "watermark_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backfill_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_json", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_fetched_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "quantiles_available", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "discovered_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "live_scripts_json", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "grant_call_cap", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_success_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "config_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_host", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_port", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_scheme", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_database", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_user", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "deleted_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "zone_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "service_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "dataset", + "position": 6, + "type": "text", + "notNull": true, + "default": "'http_requests'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_ciphertext", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_iv", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_tag", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_hash", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "enabled", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_rotated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "account_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "watch_new_zones", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "zone_overrides", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "widget_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "mode", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_hash", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_suffix", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "version_number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "snapshot_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_summary", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "source_version_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "payload_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "version", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "lockfile_path", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "ecosystem", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "package_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "advisory_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "aliases", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "severity", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "summary", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "versions", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "relation", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_scan_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_scan_id", + "position": 15, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_sha", + "position": 16, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_sha", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_scan_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_sha", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_reason", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_by", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "scan_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "lockfile_path", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "ecosystem", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "package_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "version", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "relation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "advisory_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "aliases", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "summary", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "commit_sha", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "ref", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "trigger", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "default_branch_head", + "position": 6, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'running'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "lockfiles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "package_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "vulnerable_count", + "position": 10, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "started_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "finished_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "public_jwk", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "jkt", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "roles", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "scopes", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attested", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attestation", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "last_used_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "expires_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "hard_expires_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "key", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "expires_at", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "email", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "opted_out_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "day_of_week", + "position": 6, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "timezone", + "position": 7, + "type": "text", + "notNull": true, + "default": "'UTC'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "send_hour", + "position": 8, + "type": "integer", + "notNull": true, + "default": "9", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "namespaces_json", + "position": 9, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "environments_json", + "position": 10, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_attempted_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_opted_out_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_sent_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_attempted_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_message", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "error_label", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "top_frame", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "first_seen_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "last_seen_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "reason", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "first_triggered_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "last_triggered_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "resolved_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "actor_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "type", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "from_state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "to_state", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "payload_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "source", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "origin_issue_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "repo_full_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "number", + "position": 6, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "title", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "author_login", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "state", + "position": 10, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merged_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merge_commit_sha", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "link_source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "linked_by_actor_id", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "issue_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_observed_occurrence_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_evaluated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "open_incident_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "pull_request_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "status", + "position": 4, + "type": "text", + "notNull": true, + "default": "'waiting'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "merged_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verify_after", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_rate_per_hour", + "position": 9, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "investigation_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict_note", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "post_merge_occurrence_count", + "position": 13, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "attempt", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "kind", + "position": 2, + "type": "text", + "notNull": true, + "default": "'error'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "source_ref_json", + "position": 3, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_version", + "position": 5, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "service_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_type", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_message", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "error_label", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "top_frame", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "workflow_state", + "position": 11, + "type": "text", + "notNull": true, + "default": "'triage'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "priority", + "position": 12, + "type": "integer", + "notNull": true, + "default": "3", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity_source", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "assigned_actor_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_holder_actor_id", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "claimed_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "notes", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "first_seen_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "occurrence_count", + "position": 22, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_by_actor_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_resolved_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_regressed_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "regression_count", + "position": 27, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "seen_versions_json", + "position": 28, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_versions_json", + "position": 29, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "snooze_until", + "position": 30, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "archived_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "merged_into_issue_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "created_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "updated_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "delivery_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "payload_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "status", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempt_count", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "scheduled_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claim_expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempted_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "error_message", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "destination_ids_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_first_seen", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_regression", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_resolve", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_in_review", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_done", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_claim", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "min_occurrence_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "severity", + "position": 10, + "type": "text", + "notNull": true, + "default": "'warning'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "processed_through", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "bootstrap_completed", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_token", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_expires_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "project_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "logs_enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_ciphertext", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_iv", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_tag", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_hash", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_logs_enabled", + "position": 14, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_metrics_enabled", + "position": 15, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "setup_reported_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_watermark_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_received_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_lease_until", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "resources_synced_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_resources_error", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_by", + "position": 23, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "updated_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "connector_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "asset_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "project_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "location", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "display_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "labels", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "details", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "last_seen_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'investigating'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "seeded_by", + "position": 3, + "type": "text", + "notNull": true, + "default": "'user'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "subject_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_kind", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "issue_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "report_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "progress_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "severity", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "confidence", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "input_tokens", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "output_tokens", + "position": 15, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "started_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "autonomous_turns", + "position": 18, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "diagnosed_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "updated_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "rules_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "severity", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "source", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "reason", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "run_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "investigation_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "payload_json", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "delivery_results_json", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "attempts", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "dedupe_key", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "processed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "device_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "incident_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "activity_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "push_token", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_reason", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "request_id_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "redirect_uri", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "state", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "resource", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "scopes", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "code_challenge", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "authorization_code_hash", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_org_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_roles", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_email", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "denied_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "used_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_name", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "redirect_uris", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_uri", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "token_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "client_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "resource", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "scopes", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "org_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "roles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_email", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "access_key_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "replaced_by_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "revoked_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_expires_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "platform", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "token", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "environment", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "bundle_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "app_version", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "live_activity_start_token", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "device_name", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "preferences", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_reason", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_pushed_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_seen_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "state", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "initiated_by_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "redirect_uri", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "return_to", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "code_verifier", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "github_installation_ids", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_email", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_account_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "granted_accounts_json", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "connected_by_user_id", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "scope", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_ciphertext", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_iv", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_tag", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "expires_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "revoked_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "billing_org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "linked_by", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "created_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "workflow_instance_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "phase", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "current_migration", + "position": 4, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_total", + "position": 5, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_done", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "applied_versions", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "skipped", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "error_message", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "started_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "finished_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_url", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_user", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_ciphertext", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_iv", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_tag", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_database", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "sync_status", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "schema_version", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_context", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "target_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "operation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key_hash", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_hash", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_rotated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_rotated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "hash_salt", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "created_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "bucket", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "signal", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "matched", + "position": 4, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped", + "position": 5, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped_bytes", + "position": 6, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "redacted_values", + "position": 7, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "enabled", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "position", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "spec_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "trace_sample_ratio", + "position": 1, + "type": "double precision", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_error_spans", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_slow_spans_ms", + "position": 3, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "user_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "email", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "role", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "demo_data_requested", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "onboarding_completed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "checklist_dismissed_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "first_data_received_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "welcome_email_sent_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "connect_nudge_email_sent_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "stalled_email_sent_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "activation_email_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_reserved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_ciphertext", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_iv", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_tag", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_hint", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "number", + "position": 2, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "recommendation_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "source_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "canonical_key", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "usage_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "opened_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "resolved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_name", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reservation_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reserved_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_by_user_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "ps_organization", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "connected_by_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "scrape_target_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "detected_permissions_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": "'mysql'", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "region", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "plan", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "branches_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_ciphertext", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_iv", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_tag", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "deleted_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "branch_name", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "category", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "external_id", + "position": 8, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "title", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "source", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "actor_login", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "payload_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "occurred_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "event_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "processed_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "dataset", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "database_id", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "watermark_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_success_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "lease_until", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "reply_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "seq", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "path", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "old_text", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "new_text", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "finding_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "embedding", + "position": 4, + "type": "real[]", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "review_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "handle", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "path", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "line", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "category", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "title", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "status", + "position": 11, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "comment_id", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "resolved_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_up", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_down", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "updated_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "title", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "source", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "path", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "reminded_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "first_review_id", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "last_review_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "comment_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "surface", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "thread_root_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "author_login", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "command", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "head_sha", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "reply_url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "commit_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "model", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "defaults", + "position": 2, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "head_sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "base_sha", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "url", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "title", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "skip_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_attempt", + "position": 12, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "report_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "score", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "check_run_url", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_url", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "review_url", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "input_tokens", + "position": 21, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "output_tokens", + "position": 22, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "started_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "finished_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merged_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "telemetry_json", + "position": 26, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merge_commit_sha", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_status", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_after", + "position": 29, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_json", + "position": 30, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "created_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "updated_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "workspace_names", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "connected_by_user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "auth_failed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "discovered_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_success_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "services_json", + "position": 7, + "type": "text", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "enabled", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "watermark_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_success_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "id", + "position": 0, + "type": "integer", + "notNull": true, + "default": null, + "identity": "by default" + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "target_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "sub_target_key", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "error", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "duration_ms", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_scraped", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_post_relabel", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "url", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "target_type", + "position": 6, + "type": "text", + "notNull": true, + "default": "'prometheus'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "discovery_config_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "scrape_interval_seconds", + "position": 8, + "type": "integer", + "notNull": true, + "default": "15", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "labels_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_type", + "position": 10, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "managed_by", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_ciphertext", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_iv", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_tag", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_error", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "run_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "role", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "agent_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "outcome", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "detail", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "input_tokens", + "position": 9, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "output_tokens", + "position": 10, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fingerprint", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "verdict", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "overall_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "estimated_severity", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "record", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_run_id", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_run_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_ref", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_ref", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fixed_run_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_by", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "agent_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "cost_usd", + "position": 3, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "sandbox_fee_usd", + "position": 4, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "monthly_spend_limit_usd", + "position": 1, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "discovery_model", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "validation_model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "fingerprint", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "verdict", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "overall_severity", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "estimated_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "record", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "source_ref", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "profile", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "scope_paths", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "budget", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "metadata", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'in_progress'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "incomplete_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "architecture_md", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "reports", + "position": 12, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "execution_profile", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "invocations_spent", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "input_tokens", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "output_tokens", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "cost_usd", + "position": 17, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "sandbox_usd", + "position": 18, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "discovery_model", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "validation_model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "workflow_instance_id", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "started_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "finished_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "error", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "repository", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "vcs_repository_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "branch", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "profile", + "position": 6, + "type": "text", + "notNull": true, + "default": "'deep'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "scope_paths", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "slice_interval_hours", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "revalidate_on_push", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "budget_per_run", + "position": 10, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "monthly_invocation_budget", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "discovery_model", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "validation_model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "coverage_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "wave", + "position": 4, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "agent_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "unit", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "environments_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "sli_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "target_percent", + "position": 7, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "window_days", + "position": 8, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "alert_rule_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "platform", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_prefix", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "allowed_signals", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rate_limit_rps", + "position": 9, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "resource_attributes", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_until", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_used_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejected_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejection", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_ok_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "disconnected_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "poll_resources", + "position": 21, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rolled_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_by", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_by", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "delete_snapshot", + "position": 4, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "group_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "position", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "monitor_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "manual_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "show_uptime", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "incident_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "component_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "body", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "source", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "overlay_entry_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "title", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "impact", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "published", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "alert_incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_start", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_end", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "started_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "resolved_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "website_url", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "logo_url", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "privacy_url", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "terms_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "theme", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'{\"preset\":\"default\",\"appearance\":\"auto\",\"accent\":null,\"background\":null,\"text\":null,\"card\":null,\"border\":null,\"radius\":\"md\",\"font\":\"sans\"}'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "show_powered_by", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "auto_incidents", + "position": 11, + "type": "text", + "notNull": true, + "default": "'off'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_sha256", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_suffix", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "draft", + "position": 14, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "revision", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_revision", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "last_publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_hash", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "publish_attempted_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_by", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "updated_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_slug", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "connected_by_user_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "discovery_lease_until", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "project_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "top_queries_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_checked_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_error", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "lease_until", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "project_ref", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "region", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "scrape_target_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_enabled", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_source_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_json", + "position": 13, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_checked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_watermark_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_boundary_ids", + "position": 17, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_last_polled_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_error", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "removed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "day", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "checks", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "failures", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "down_checks", + "position": 5, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "last_check_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_successes", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status_changed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_check_json", + "position": 6, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "recent_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_ping_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "config_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "interval_seconds", + "position": 5, + "type": "integer", + "notNull": true, + "default": "60", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "failures_to_down", + "position": 6, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "service_name", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "alert_rule_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "heartbeat_token", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "enabled", + "position": 10, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "message", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_email", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_avatar_url", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "authored_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "committed_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "html_url", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_installation_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_login", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_account_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_avatar_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_selection", + "position": 9, + "type": "text", + "notNull": true, + "default": "'all'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "suspended_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "ci_traces_enabled", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_discovery", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "installed_by_user_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "owner", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "full_name", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "default_branch", + "position": 8, + "type": "text", + "notNull": true, + "default": "'main'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "tracked_branch", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "html_url", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_private", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_archived", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "sync_status", + "position": 14, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_synced_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_sync_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_enabled", + "position": 17, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_config", + "position": 18, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "is_default", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "head_sha", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_agent_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"agent_name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_kind_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"kind\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"claim_expires_at\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_delivery_attempt_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"attempt_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_incident_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"incident_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_open_group_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"", + "\"group_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_rule_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_thread_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"destination_id\"", + "\"thread_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_claims", + "name": "alert_rule_claims_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_states", + "name": "alert_rule_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_silences", + "name": "alert_silences_org_ends_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"ends_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_evaluated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"last_evaluated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_open_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"open_incident_id\"" + ], + "where": "\"open_incident_id\" is not null" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_open_detector_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_detector_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_status_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"", + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_org_account_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_stream_secret_hash_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"stream_secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cancellation_reviews", + "name": "cancellation_reviews_subscription_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"plan_id\"", + "\"subscription_started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"external_user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connector\"", + "\"external_workspace_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_user_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"user_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_account_dataset_zone_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"", + "\"dataset\"", + "\"zone_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_config_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"config_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_live_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "coalesce(widget_id, '')" + ], + "where": "revoked_at is null" + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_token_hash_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_version_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_name_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"lockfile_path\"", + "\"ecosystem\"", + "\"package_name\"", + "\"advisory_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scan_matches", + "name": "dependency_scan_matches_org_scan_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scan_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_commit_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"commit_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_jkt_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"jkt\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_live_installation_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"installation_id\"" + ], + "where": "\"revoked_at\" IS NULL" + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_proof_replays", + "name": "device_proof_replays_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_fingerprint_candidates", + "name": "error_fingerprint_candidates_last_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_actor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"actor_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_fingerprints", + "name": "error_issue_fingerprints_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_pr_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"verify_after\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_open_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": "\"status\" in ('waiting', 'running')" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_lease_expiry_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"lease_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_archived_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"archived_at\"" + ], + "where": "\"archived_at\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_assignee_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"assigned_actor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_version_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_version\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_live_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"", + "\"id\"" + ], + "where": "\"archived_at\" is null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_merged_into_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"merged_into_issue_id\"" + ], + "where": "\"merged_into_issue_id\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_severity_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"severity\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_workflow_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"workflow_state\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"", + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_key_destination_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"destination_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_tick_states", + "name": "error_tick_states_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_org_scope_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scope_type\"", + "\"scope_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_incident_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_kind\"", + "\"incident_id\"" + ], + "where": "\"incident_id\" is not null" + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"dedupe_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_device_incident_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"device_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"authorization_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_family_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"family_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_platform_token_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"platform\"", + "\"token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_auth_states", + "name": "oauth_auth_states_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_provider_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_billing_links", + "name": "org_billing_links_billing_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"billing_org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_attribute_mappings", + "name": "org_ingest_attribute_mappings_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_private_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"private_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rule_stats", + "name": "org_ingest_rule_stats_org_bucket_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"bucket\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rules", + "name": "org_ingest_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"recommendation_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"event_type\"", + "\"external_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_db_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_issue_receipts", + "name": "planetscale_issue_receipts_processed_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"processed_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_dataset_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dataset\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_edits", + "name": "pr_review_edits_reply_seq_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"reply_id\"", + "\"seq\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_finding_embeddings", + "name": "pr_review_finding_embeddings_org_model_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"model\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_pr_handle_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"handle\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_pr_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_repo_comment_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"comment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_post_merge_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"post_merge_after\"" + ], + "where": "\"post_merge_status\" = 'waiting'" + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_head_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"head_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_connection_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"connection_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_org_env_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"environment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_checked_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"checked_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_events", + "name": "security_audit_events_run_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"run_id\"", + "\"at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_target_fingerprint_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"fingerprint\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_org_reservations", + "name": "security_audit_org_reservations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_one_running_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"" + ], + "where": "\"finished_at\" IS NULL" + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_target_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_repo_branch_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"repository\"", + "\"branch\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_name_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_default_kind_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": "\"kind\" = 'default'" + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_cleanups", + "name": "status_page_cleanups_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_monitor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"monitor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"position\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_components", + "name": "status_page_incident_components_component_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"component_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_overlay_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"overlay_entry_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_alert_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"alert_incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_slug_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"slug\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_unpublished_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"revision\"", + "\"published_revision\"" + ], + "where": "revision > published_revision" + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_project_insights", + "name": "supabase_project_insights_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_connection_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connection_id\"", + "\"project_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"log_drain_source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_enabled_kind_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"kind\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_heartbeat_token_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"heartbeat_token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_org_sha_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_repo_sha_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_provider_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"provider\"", + "\"external_installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_installation_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_repo_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"external_repo_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_repo_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "foreign_key", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_linear_issues", + "name": "alert_linear_issues_destination_id_alert_destinations_id_fk", + "columns": [ + "destination_id" + ], + "foreignTable": "alert_destinations", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "aws_connections", + "name": "aws_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "dashboard_shares", + "name": "dashboard_shares_dashboard_fk", + "columns": [ + "org_id", + "dashboard_id" + ], + "foreignTable": "dashboards", + "foreignColumns": [ + "org_id", + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_connectors", + "name": "gcp_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_resources", + "name": "gcp_resources_connector_id_gcp_connectors_id_fk", + "columns": [ + "connector_id" + ], + "foreignTable": "gcp_connectors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oauth_connections", + "name": "oauth_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "planetscale_connections", + "name": "planetscale_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "railway_connections", + "name": "railway_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_id_scrape_targets_id_fk", + "columns": [ + "target_id" + ], + "foreignTable": "scrape_targets", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_targets", + "name": "scrape_targets_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_Bnqlt6wwKJty_fk", + "columns": [ + "component_id" + ], + "foreignTable": "status_page_components", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_tBn6mEz4FenY_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_3LDDUAhznHPM_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incidents", + "name": "status_page_incidents_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_connections", + "name": "supabase_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_project_insights", + "name": "supabase_project_insights_project_id_supabase_projects_id_fk", + "columns": [ + "project_id" + ], + "foreignTable": "supabase_projects", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_connection_id_supabase_connections_id_fk", + "columns": [ + "connection_id" + ], + "foreignTable": "supabase_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_id_sources_id_fk", + "columns": [ + "log_drain_source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vcs_installations", + "name": "vcs_installations_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + } + ] +} From 6ada386aa65d958a54b6eca7e41afdc881c48a11 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 21:43:33 +0100 Subject: [PATCH 18/28] fix(web): the default anomaly rule's empty routing is not a warning --- apps/web/src/page/alerts/hub/view-rules.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/src/page/alerts/hub/view-rules.ts b/apps/web/src/page/alerts/hub/view-rules.ts index 97d1e3825f..076400dc95 100644 --- a/apps/web/src/page/alerts/hub/view-rules.ts +++ b/apps/web/src/page/alerts/hub/view-rules.ts @@ -340,7 +340,7 @@ const ruleRow = (h: H, model: Model, row: RowInputs): Html => { ), headroomMeter(h, rule, row.derived, row.lastValue), stateRibbon(h, ruleRibbon(row.incidents, row.ribbonRange, RIBBON_CELLS)), - routesTo(h, row.destinations, rule.enabled), + routesTo(h, row.destinations, rule.enabled && !isDefault), h.span( [ h.Class( @@ -449,10 +449,15 @@ const stateRibbon = (h: H, cells: ReadonlyArray): Html => Arr.map(cells, (cell) => h.span([h.Class(cn("flex-1 rounded-2xs", RIBBON_CLASS.get(cell)))])), ) -const routesTo = (h: H, destinations: ReadonlyArray, isEnabled: boolean): Html => { +/** `warnWhenEmpty` is off for rules that may sit unrouted, like the default anomaly rule. */ +const routesTo = ( + h: H, + destinations: ReadonlyArray, + warnWhenEmpty: boolean, +): Html => { if (destinations.length === 0) return h.span( - [h.Class(cn("text-xs", isEnabled ? "text-severity-warn" : "text-muted-foreground"))], + [h.Class(cn("text-xs", warnWhenEmpty ? "text-severity-warn" : "text-muted-foreground"))], ["Nobody"], ) const names = Arr.join( From bd86ddc32d426c36ff8588eb4a8bc158c91744c0 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 22:09:13 +0100 Subject: [PATCH 19/28] fix(alerts): anomaly rules record that they ran --- .../src/page/alerts/rule/view-diagnosis.ts | 3 +- .../alerts/AlertsService.anomaly.test.ts | 80 +++++++++++++++-- .../src/services/alerts/AlertsService.ts | 86 ++++++++++++++++--- .../web-data/src/alerts/rule-status.test.ts | 20 +++++ packages/web-data/src/alerts/rule-status.ts | 11 ++- 5 files changed, 175 insertions(+), 25 deletions(-) diff --git a/apps/web/src/page/alerts/rule/view-diagnosis.ts b/apps/web/src/page/alerts/rule/view-diagnosis.ts index 3ef0fb0ba8..ba53fe803a 100644 --- a/apps/web/src/page/alerts/rule/view-diagnosis.ts +++ b/apps/web/src/page/alerts/rule/view-diagnosis.ts @@ -60,7 +60,8 @@ export type DiagnosisInputs = Readonly<{ export const diagnosisPanel = (h: H, model: Model, inputs: DiagnosisInputs): Html => { const keys = [...new Set(Arr.map(inputs.states, (s) => s.group_key))].sort() - const groupKeys = keys.length > 1 ? keys : [] + // An anomaly rule's keys are a heartbeat and incident ids, not groups anyone picks. + const groupKeys = keys.length > 1 && inputs.rule.signalType !== "anomaly" ? keys : [] const activeGroup = model.selectedGroup ?? worstState(inputs.states)?.group_key ?? null const stages = buildDiagnosis({ rule: inputs.rule, diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts index 6aa4c1e2c2..499359b557 100644 --- a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts +++ b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts @@ -6,6 +6,7 @@ import { AlertRuleUpsertRequest, AnomalyIncidentId, ErrorIssueId, + UNGROUPED_GROUP_KEY, type AlertDestinationId, type AlertThresholds, type AnomalyIncidentSeverity, @@ -165,6 +166,16 @@ const anomalyRule = (input: { ...(input.serviceNames === undefined ? undefined : { serviceNames: input.serviceNames }), }) +/** The org's state row keys, heartbeat first. */ +const stateKeys = (testDb: TestDb) => + Effect.promise(() => + queryFirstRow<{ keys: Array | null }>( + testDb, + "select array_agg(group_key order by group_key = $2 desc, group_key) as keys from alert_rule_states where org_id = $1", + [orgId, UNGROUPED_GROUP_KEY], + ), + ).pipe(Effect.map((row) => row?.keys ?? [])) + const nextTick = Effect.gen(function* () { yield* TestClock.adjust(Duration.minutes(1)) const alerts = yield* AlertsService @@ -509,15 +520,8 @@ describe("AlertsService anomaly rules", () => { Arr.map(incidents, (incident) => [incident.id, incident.status]), [[opened?.id, "open"]], ) - // Housekeeping treats the rule as grouped, so its per-anomaly state survives each tick. - const states = yield* Effect.promise(() => - queryFirstRow<{ keys: Array }>( - testDb, - "select array_agg(group_key) as keys from alert_rule_states where org_id = $1", - [orgId], - ), - ) - assert.deepStrictEqual(states?.keys, [errorRateSpike.id]) + // Housekeeping keeps the per-anomaly state beside the heartbeat on each tick. + assert.deepStrictEqual(yield* stateKeys(testDb), [UNGROUPED_GROUP_KEY, errorRateSpike.id]) }).pipe(Effect.provide(layer)) }) @@ -554,4 +558,62 @@ describe("AlertsService anomaly rules", () => { }) }).pipe(Effect.provide(layer)) }) + + it.effect("an idle rule records that it ran, without opening anything", () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + const rule = yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ + name: "Checkout anomalies", + serviceNames: ["checkout"], + thresholds: criticalRouted(destination.id), + }), + ) + assert.isNull(rule.lastEvaluatedAt) + + yield* alerts.runSchedulerTick() + + // The dashboard reads "last evaluated" from the rule's state rows. + const heartbeat = yield* Effect.promise(() => + queryFirstRow<{ last_evaluated_at: string | null; last_status: string }>( + testDb, + "select last_evaluated_at, last_status from alert_rule_states where rule_id = $1 and group_key = $2", + [rule.id, UNGROUPED_GROUP_KEY], + ), + ) + assert.isNotNull(heartbeat?.last_evaluated_at ?? null) + assert.strictEqual(heartbeat?.last_status, "healthy") + assert.deepStrictEqual((yield* alerts.listIncidents(orgId)).incidents, []) + assert.deepStrictEqual(yield* stateKeys(testDb), [UNGROUPED_GROUP_KEY]) + }).pipe(Effect.provide(layer)) + }) + + it.effect( + "drops an ended anomaly's state row once its incident has closed, keeping the heartbeat", + () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* insertAnomaly(errorRateSpike) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + yield* alerts.runSchedulerTick() + assert.deepStrictEqual(yield* stateKeys(testDb), [UNGROUPED_GROUP_KEY, errorRateSpike.id]) + + yield* updateAnomaly(testDb, errorRateSpike.id, "status = 'resolved'") + yield* nextTick + expect((yield* alerts.listIncidents(orgId)).incidents[0]).toMatchObject({ + status: "resolved", + }) + yield* nextTick + assert.deepStrictEqual(yield* stateKeys(testDb), [UNGROUPED_GROUP_KEY]) + }).pipe(Effect.provide(layer)) + }, + ) }) diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index 0eb4e81892..b422731159 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -1089,17 +1089,30 @@ export class AlertsService extends Context.Service ({ - evaluation: { - ...applyEvaluationLogic( - rule, - { value: observed.value, sampleCount: 1, hasData: true }, - observed.detail, - ), - anomaly: observed, - }, - groupKey: observed.groupKey, - })) + // A healthy heartbeat on the rule's ungrouped key, so an idle rule still records that it + // ran. Value 0 sits below every tier (1 and 2), so it can never open an incident. + const heartbeat = { + evaluation: applyEvaluationLogic( + rule, + { value: 0, sampleCount: 1, hasData: true }, + "No open anomaly", + ), + groupKey: UNGROUPED_GROUP_KEY, + } + return Arr.append( + Arr.map(observations, (observed) => ({ + evaluation: { + ...applyEvaluationLogic( + rule, + { value: observed.value, sampleCount: 1, hasData: true }, + observed.detail, + ), + anomaly: observed, + }, + groupKey: observed.groupKey, + })), + heartbeat, + ) } // Snapped, not raw `now`: excludes the still-filling current minute and // lets rules over the same signal share bucket reads within a tick. See @@ -4519,6 +4532,8 @@ export class AlertsService extends Context.Service ReadonlyArray + /** The group keys the rule had state rows for when the tick began. */ + readonly stateKeysForRule: (orgId: OrgId, ruleId: AlertRuleId) => ReadonlyArray } const EMPTY_INCIDENTS: ReadonlyArray = [] @@ -4542,6 +4557,7 @@ export class AlertsService extends Context.Service null, openIncidentFor: () => null, openIncidentsForRule: () => EMPTY_INCIDENTS, + stateKeysForRule: () => [], } satisfies TickPrefetch } @@ -4596,6 +4612,9 @@ export class AlertsService extends Context.Service ruleCacheKey(incident.orgId, incident.ruleId), ) + const stateKeysByRule = Arr.groupBy(stateRows, (state) => + ruleCacheKey(state.orgId, state.ruleId), + ) return { stateFor: (orgId, ruleId, groupKey) => @@ -4610,6 +4629,13 @@ export class AlertsService extends Context.Service EMPTY_INCIDENTS), ), + stateKeysForRule: (orgId, ruleId) => + Record.get(stateKeysByRule, ruleCacheKey(orgId, ruleId)).pipe( + Option.match({ + onNone: () => [], + onSome: (states) => Arr.map(states, (state) => state.groupKey), + }), + ), } satisfies TickPrefetch }) @@ -4636,6 +4662,8 @@ export class AlertsService extends Context.Service | undefined }>, clearError: ReadonlyArray<{ readonly orgId: OrgId @@ -4655,10 +4683,30 @@ export class AlertsService extends Context.Service t.orgId)) + // Anomaly rules own both a heartbeat row and per-anomaly rows, so they only drop the + // groups they no longer report. Zero statements unless an anomaly has gone. + yield* Effect.forEach( + Arr.filter( + selfHeal, + (t) => t.staleGroupKeys !== undefined && t.staleGroupKeys.length > 0, + ), + (target) => + dbExecute((db) => + db.run( + PG.deleteFrom(AlertRuleStates).where(($) => [ + $.orgId.eq(target.orgId), + $.ruleId.eq(target.ruleId), + $.groupKey.in_(...(target.staleGroupKeys ?? [])), + ]), + ), + ).pipe(bestEffort("Failed to drop state rows for ended anomalies")), + { discard: true }, + ) + const keyedRules = Arr.filter(selfHeal, (t) => t.staleGroupKeys === undefined) + const selfHealOrgIds = Arr.dedupe(Arr.map(keyedRules, (t) => t.orgId)) // One pass: since Effect 4.0.0 `partition` returns [passes, fails], so the // `Result` success arm carries the grouped ids and the failure arm the ungrouped. - const [groupedRuleIds, ungroupedRuleIds] = Arr.partition(selfHeal, (t) => + const [groupedRuleIds, ungroupedRuleIds] = Arr.partition(keyedRules, (t) => t.grouped ? Result.succeed(t.ruleId) : Result.fail(t.ruleId), ) @@ -4867,6 +4915,7 @@ export class AlertsService extends Context.Service | undefined }>(), ) const clearErrorTargets = yield* Ref.make( @@ -5307,9 +5356,18 @@ export class AlertsService extends Context.Service !HashSet.has(evaluatedGroups, key), + ) + : undefined yield* Ref.update( selfHealTargets, - Chunk.append({ orgId: row.orgId, ruleId: row.id, grouped }), + Chunk.append({ orgId: row.orgId, ruleId: row.id, grouped, staleGroupKeys }), ) yield* Metric.update( diff --git a/packages/web-data/src/alerts/rule-status.test.ts b/packages/web-data/src/alerts/rule-status.test.ts index b582a14c4a..af7db636ee 100644 --- a/packages/web-data/src/alerts/rule-status.test.ts +++ b/packages/web-data/src/alerts/rule-status.test.ts @@ -96,6 +96,26 @@ describe("deriveRuleStatus", () => { expect(derive().status).toBe("healthy") }) + it("reads an idle default anomaly rule with only its heartbeat state as healthy", () => { + const anomalyRule = makeRule({ + signalType: "anomaly", + comparator: "gte", + threshold: 1, + evaluationIntervalMinutes: 1, + destinationIds: [], + thresholds: [ + { severity: "warning", value: 1, upper: null, recoveryValue: null, destinationIds: [] }, + ], + lastEvaluatedAt: null, + }) + const heartbeat = makeState({ group_key: "__total__", last_value: 0, last_sample_count: 1 }) + const result = derive({ rule: anomalyRule, states: [heartbeat] }) + expect(result.status).toBe("healthy") + // Unrouted by design, so not a blind spot either. + expect(needsAttention(result)).toBe(false) + expect(derive({ rule: anomalyRule, states: [] }).status).toBe("stale") + }) + it("flags a rule the scheduler slowed down, without calling it unhealthy", () => { const result = derive({ rule: makeRule({ slowedIntervalMinutes: 5, slowedAt: iso(60_000) }) }) expect(result.status).toBe("healthy") diff --git a/packages/web-data/src/alerts/rule-status.ts b/packages/web-data/src/alerts/rule-status.ts index c77c521719..3fca5af508 100644 --- a/packages/web-data/src/alerts/rule-status.ts +++ b/packages/web-data/src/alerts/rule-status.ts @@ -32,6 +32,14 @@ export interface DerivedRuleStatus { * scheduler outages for up to 3x the window (72h on a daily rule). * Shared with the diagnosis panel so both surfaces agree on "stale". */ +const isDefaultAnomalyRule = ( + rule: Pick, +): boolean => + rule.signalType === "anomaly" && + rule.serviceNames.length === 0 && + rule.environments.length === 0 && + (rule.anomalySignals ?? []).length === 0 + export const staleThresholdMs = ( rule: Pick, ): number => 3 * Math.max(ALERT_MAX_EVALUATION_INTERVAL_MINUTES, alertRuleIntervalMinutes(rule)) * 60_000 @@ -93,7 +101,8 @@ export function deriveRuleStatus(input: { const latestDelivery = deliveryEvents[0] ?? null const attention: RuleAttention = { - noDestinations: rule.enabled && rule.destinationIds.length === 0, + // The default anomaly rule (unscoped) is unrouted by design: it shows anomalies in the app only. + noDestinations: rule.enabled && rule.destinationIds.length === 0 && !isDefaultAnomalyRule(rule), recentDeliveryFailure: latestDelivery?.status === "failed", slowedDown: rule.enabled && rule.slowedIntervalMinutes !== null, } From 620494074fa98a55857f8090d98e572ddc5d74e7 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 22:12:19 +0100 Subject: [PATCH 20/28] fix(alerts): unrouted anomaly tiers never push; disabling detection resolves --- .../alerts/AlertsService.anomaly.test.ts | 85 ++++++++++++++++++- .../src/services/alerts/AlertsService.ts | 11 ++- .../src/services/alerts/alert-thresholds.ts | 2 +- .../alerts/alerts-service-test-support.ts | 32 +++++-- .../alerts/anomaly/AnomalyAlertSource.ts | 68 ++++++++++----- .../services/alerts/anomaly/anomaly-rule.ts | 2 +- 6 files changed, 162 insertions(+), 38 deletions(-) diff --git a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts index 499359b557..eb20792424 100644 --- a/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts +++ b/packages/backend/src/services/alerts/AlertsService.anomaly.test.ts @@ -16,6 +16,7 @@ import { Array as Arr, Clock, Duration, Effect, Layer, Result, Schema } from "ef import { TestClock } from "effect/testing" import { Database } from "@maple/backend/platform/DatabaseLive" import { fakeChatSessions } from "@maple/backend/platform/chat-sessions-fake" +import type { IncidentPushEvent } from "@maple/backend/services/push/MobilePushService" import { cleanupTestDbs, createTestDb, @@ -62,14 +63,18 @@ const harness = () => { return { cursor: 0, messageId: input.messageId, turnMessageId: input.messageId } }, })) + const pushes: Array = [] const layer = Layer.mergeAll( - makeLayer(testDb, makeWarehouseStub({}), { fetch: fetchImpl, chatSessions }), + makeLayer(testDb, makeWarehouseStub({}), { fetch: fetchImpl, chatSessions, pushes }), AnomalyAlertSource.layer.pipe(Layer.provide(testDb.layer)), testDb.layer, ) - return { testDb, bodies, turns, layer } + return { testDb, bodies, turns, pushes, layer } } +const pushedEvents = (pushes: ReadonlyArray) => + Arr.map(pushes, (push) => `${push.eventType}:${push.severity}`) + interface SeededAnomaly { readonly id: string readonly signalType: AnomalySignalType @@ -223,8 +228,24 @@ describe("AlertsService anomaly rules", () => { }).pipe(Effect.provide(layer)) }) + it.effect("an unrouted tier never pushes to phones, even for a critical anomaly", () => { + const { pushes, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + yield* insertAnomaly({ ...errorRateSpike, severity: "critical" }) + yield* AnomalyAlertSource.use((source) => source.backfillDefaultRules) + + yield* alerts.runSchedulerTick() + yield* nextTick + + expect((yield* alerts.listIncidents(orgId)).incidents[0]).toMatchObject({ severity: "critical" }) + assert.deepStrictEqual(pushes, []) + }).pipe(Effect.provide(layer)) + }) + it.effect("a routed critical tier pages its destination with the anomaly's own sentence", () => { - const { bodies, layer } = harness() + const { bodies, pushes, layer } = harness() return Effect.gen(function* () { yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) const alerts = yield* AlertsService @@ -250,6 +271,7 @@ describe("AlertsService anomaly rules", () => { Arr.map(events, (event) => [event.eventType, event.destinationId]), [["trigger", destination.id]], ) + assert.deepStrictEqual(pushedEvents(pushes), ["trigger:critical"]) const trigger = Schema.decodeSync( Schema.fromJsonString( Schema.Struct({ @@ -272,7 +294,7 @@ describe("AlertsService anomaly rules", () => { }) it.effect("follows the anomaly from warning to critical and resolves with it", () => { - const { testDb, layer } = harness() + const { testDb, pushes, layer } = harness() return Effect.gen(function* () { yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) const alerts = yield* AlertsService @@ -293,6 +315,8 @@ describe("AlertsService anomaly rules", () => { const opened = (yield* alerts.listIncidents(orgId)).incidents expect(opened[0]).toMatchObject({ status: "open", severity: "warning" }) assert.strictEqual(yield* countRows(testDb, "alert_delivery_events"), 0) + // The warning tier is unrouted, so the phone stays quiet until it escalates. + assert.deepStrictEqual(pushes, []) yield* updateAnomaly(testDb, errorRateSpike.id, "severity = 'critical'") yield* nextTick @@ -303,6 +327,8 @@ describe("AlertsService anomaly rules", () => { severity: "critical", peakSeverity: "critical", }) + assert.isNotEmpty(pushes) + assert.isTrue(Arr.every(pushes, (push) => push.severity === "critical")) yield* updateAnomaly(testDb, errorRateSpike.id, "status = 'resolved'") yield* nextTick @@ -616,4 +642,55 @@ describe("AlertsService anomaly rules", () => { }).pipe(Effect.provide(layer)) }, ) + + it.effect("turning detection off resolves the alert side, though the anomaly stays open", () => { + const { testDb, layer } = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(DEFAULT_CLOCK_EPOCH_MS) + const alerts = yield* AlertsService + const destination = yield* createWebhookDestination(alerts, orgId, userId) + yield* alerts.createRule( + orgId, + userId, + adminRoles, + anomalyRule({ + name: "Checkout anomalies", + serviceNames: ["checkout"], + thresholds: [ + { ...criticalTier(destination.id), severity: "warning", value: 1 }, + criticalTier(destination.id), + ], + }), + ) + yield* insertAnomaly(errorRateSpike) + yield* alerts.runSchedulerTick() + expect((yield* alerts.listIncidents(orgId)).incidents[0]).toMatchObject({ status: "open" }) + + yield* Effect.promise(() => + executeSql( + testDb, + "insert into anomaly_detector_settings (org_id, enabled, created_at, updated_at) values ($1, false, now(), now())", + [orgId], + ), + ) + // Past the 30 minute reminder, which a still-open incident would have owed. + yield* TestClock.adjust(Duration.minutes(40)) + yield* nextTick + + expect((yield* alerts.listIncidents(orgId)).incidents[0]).toMatchObject({ status: "resolved" }) + const events = Arr.map( + (yield* alerts.listDeliveryEvents(orgId)).events, + (event) => event.eventType, + ) + assert.sameMembers(events, ["trigger", "resolve"]) + const anomaly = yield* Effect.promise(() => + queryFirstRow<{ status: string }>( + testDb, + "select status from anomaly_incidents where id = $1", + [errorRateSpike.id], + ), + ) + assert.strictEqual(anomaly?.status, "open") + }).pipe(Effect.provide(layer)) + }) }) diff --git a/packages/backend/src/services/alerts/AlertsService.ts b/packages/backend/src/services/alerts/AlertsService.ts index b422731159..f472bb9c56 100644 --- a/packages/backend/src/services/alerts/AlertsService.ts +++ b/packages/backend/src/services/alerts/AlertsService.ts @@ -143,7 +143,7 @@ import { import { ChatSessions } from "@maple/backend/platform/bindings" import { Database } from "@maple/backend/platform/DatabaseLive" import { formatComparator, signalCarriesDetail } from "./alert-formatting" -import { ruleAtSeverity } from "./alert-thresholds" +import { destinationsUpTo, ruleAtSeverity } from "./alert-thresholds" import { buildChatDigestBlocks, CHAT_DIGEST_THRESHOLD, type ChatDigestItem } from "./delivery/chat-digest" import { makeIncidentPushBudget, type IncidentPushBudget } from "./alert-push-budget" import { EmailService } from "@maple/backend/platform/EmailService" @@ -1299,6 +1299,15 @@ export class AlertsService extends Context.Service, severity: AlertSeverity, ): ReadonlyArray => diff --git a/packages/backend/src/services/alerts/alerts-service-test-support.ts b/packages/backend/src/services/alerts/alerts-service-test-support.ts index 8790e3917a..2846be5c94 100644 --- a/packages/backend/src/services/alerts/alerts-service-test-support.ts +++ b/packages/backend/src/services/alerts/alerts-service-test-support.ts @@ -3,7 +3,7 @@ import { LiveActivitiesService } from "@maple/backend/services/push/LiveActivitiesService" import { MobileDevicesService } from "@maple/backend/services/push/MobileDevicesService" import { ApnsClient } from "@maple/backend/platform/Apns" -import { MobilePushService } from "@maple/backend/services/push/MobilePushService" +import { MobilePushService, type IncidentPushEvent } from "@maple/backend/services/push/MobilePushService" import { ChatAlertPoster } from "./ChatAlertPoster" import { Cause, @@ -196,6 +196,8 @@ export interface TestOverrides extends Partial { readonly fetch?: typeof fetch /** The investigation agent's port; absent means investigations cannot start. */ readonly chatSessions?: ChatSessionsApi + /** Records every incident push instead of sending it through the real push service. */ + readonly pushes?: Array } // The fixed epoch scheduler tests start TestClock at, mirroring the previous @@ -252,7 +254,25 @@ export const makeLayer = ( emailStub?: (typeof EmailService)["Service"], chatAlertPoster: Layer.Layer = ChatAlertPoster.layer, ) => { - const { fetch: fetchImpl = globalThis.fetch, chatSessions, ...runtimeOverrides } = overrides + const { fetch: fetchImpl = globalThis.fetch, chatSessions, pushes, ...runtimeOverrides } = overrides + const noPushes = { sent: 0, failed: 0, unregistered: 0, skipped: 0 } + const mobilePushLive = + pushes === undefined + ? Layer.effect(MobilePushService, MobilePushService.make).pipe( + Layer.provide( + Layer.mergeAll( + ApnsClient.layer, + MobileDevicesService.layer, + LiveActivitiesService.layer, + ), + ), + ) + : Layer.succeed(MobilePushService, { + notifyIncident: (event) => + Effect.sync(() => pushes.push(event)).pipe(Effect.as(noPushes)), + notifyIncidentDigest: () => Effect.succeed(noPushes), + refreshWidgets: () => Effect.succeed(noPushes), + }) const configLive = makeConfig() const envLive = Env.layer.pipe(Layer.provide(configLive)) const databaseLive = testDb.layer @@ -309,13 +329,7 @@ export const makeLayer = ( const alertsLive = Layer.effect(AlertsService, AlertsService.make).pipe( Layer.provide(FetchHttpClient.layer), Layer.provide(chatAlertPoster), - Layer.provide( - Layer.effect(MobilePushService, MobilePushService.make).pipe( - Layer.provide( - Layer.mergeAll(ApnsClient.layer, MobileDevicesService.layer, LiveActivitiesService.layer), - ), - ), - ), + Layer.provide(mobilePushLive), Layer.provide(envLive), Layer.provide(databaseLive), Layer.provide(queryEngineLive), diff --git a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts index 5d4c55183a..4349cba9a7 100644 --- a/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts +++ b/packages/backend/src/services/alerts/anomaly/AnomalyAlertSource.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto" import * as PG from "@maple-dev/effect-orm/postgres" -import { AlertIncidents, AlertRules, AnomalyIncidents } from "@maple/db/tables" +import { AlertIncidents, AlertRules, AnomalyDetectorSettings, AnomalyIncidents } from "@maple/db/tables" import type { MapleDb } from "@maple/db/client" import { AlertPersistenceError, @@ -92,6 +92,19 @@ const lowestSeverityCode = (rule: { readonly severity: string; readonly threshol }), }) +const DETECTION_OFF_DETAIL = "Anomaly detection is turned off" + +/** The org's detector switch; no settings row means detection is on. */ +const detectionSettings = (db: MapleDb, orgId: OrgId) => + db.run( + PG.from(AnomalyDetectorSettings) + .select("enabled") + .where(($) => [$.orgId.eq(orgId)]), + ) + +const detectionEnabled = (rows: ReadonlyArray<{ readonly enabled: boolean }>): boolean => + rows[0]?.enabled ?? true + /** Names the backfill tries in order, so a user rule holding one never blocks the default. */ const DEFAULT_RULE_NAMES = [ DEFAULT_ANOMALY_RULE_NAME, @@ -115,8 +128,9 @@ export class AnomalyAlertSource extends Context.Service + const { rules, anomalies, openAlertIncidents, settings } = yield* dbExecute((db) => Effect.all({ + settings: detectionSettings(db, orgId), rules: db.run( PG.from(AlertRules) .select( @@ -171,7 +185,9 @@ export class AnomalyAlertSource extends Context.Service + // With detection off, nothing fires and every open alert incident below resolves. + const detectionOn = detectionEnabled(settings) + const assigned = Arr.filter(detectionOn ? anomalies : [], (incident) => Option.exists( assignAnomalyRule( candidates, @@ -235,7 +251,7 @@ export class AnomalyAlertSource extends Context.Service - db.run( - PG.from(AnomalyIncidents) - .select( - "id", - "signalType", - "serviceName", - "deploymentEnv", - "severity", - "lastObservedValue", - "baselineMedian", - "errorIssueId", - ) - .where(($) => [$.orgId.eq(orgId), $.status.eq("open")]), - ), + const { anomalies, settings } = yield* dbExecute((db) => + Effect.all({ + settings: detectionSettings(db, orgId), + anomalies: db.run( + PG.from(AnomalyIncidents) + .select( + "id", + "signalType", + "serviceName", + "deploymentEnv", + "severity", + "lastObservedValue", + "baselineMedian", + "errorIssueId", + ) + .where(($) => [$.orgId.eq(orgId), $.status.eq("open")]), + ), + }), ) - const inScope = Arr.filter(anomalies, (incident) => anomalyScopeCovers(scope, incident)) + const inScope = detectionEnabled(settings) + ? Arr.filter(anomalies, (incident) => anomalyScopeCovers(scope, incident)) + : [] yield* Effect.annotateCurrentSpan("maple.alert.observation_count", inScope.length) return Arr.map(inScope, (incident): AnomalyAlertObservation => ({ groupKey: incident.id, diff --git a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts index 52e75b21b3..e36d48630a 100644 --- a/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts +++ b/packages/backend/src/services/alerts/anomaly/anomaly-rule.ts @@ -36,7 +36,7 @@ export const DEFAULT_ANOMALY_RULE_SYSTEM_MESSAGE = export const DEFAULT_ANOMALY_RULE_SCOPE_MESSAGE = "The default anomaly rule covers every anomaly and cannot be scoped; create a scoped anomaly rule instead" export const DEFAULT_ANOMALY_RULE_PROTECTED_MESSAGE = - "The default anomaly rule cannot be deleted or disabled. Leave it with no destinations so the anomalies it handles only show in the app (scoped anomaly rules still page), or turn detection off with PATCH /v2/anomalies/settings (enabled: false)" + "The default anomaly rule cannot be deleted or disabled. With no destinations it never notifies anyone (scoped anomaly rules still page). To stop anomaly alerts entirely, turn detection off with PATCH /v2/anomalies/settings (enabled: false), which also resolves open anomaly alerts." /** What an anomaly rule pages on. Each empty list matches everything. */ export interface AnomalyRuleScope { From 9ae96abfe61b9c7f30a59057dc55730005c9c9ab Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 22:27:38 +0100 Subject: [PATCH 21/28] fix(web): anomaly rule page and editor fixes from the preview test --- apps/web/src/lab/anomaly-alerts-lab.ts | 113 ++++++++--- .../destination/email-fields.scene.test.ts | 2 +- apps/web/src/page/alerts/editor/actions.ts | 6 +- apps/web/src/page/alerts/editor/dest-steps.ts | 9 +- .../editor/editor-anomaly.scene.test.ts | 57 ++++++ apps/web/src/page/alerts/editor/form-steps.ts | 11 ++ apps/web/src/page/alerts/editor/plan.ts | 2 + .../src/page/alerts/editor/view-details.ts | 15 +- apps/web/src/page/alerts/editor/view.ts | 2 +- .../alerts/hub/hub-anomalies.scene.test.ts | 15 ++ apps/web/src/page/alerts/hub/view.ts | 7 +- .../alerts/rule/rule-anomaly.scene.test.ts | 121 ++++++++++++ apps/web/src/page/alerts/rule/view-anomaly.ts | 179 ++++++++++++++++++ .../web/src/page/alerts/rule/view-overview.ts | 152 ++++++++------- apps/web/src/page/alerts/rule/view.ts | 11 +- packages/views/src/ui/combobox-view.ts | 3 +- packages/views/src/ui/combobox.scene.test.ts | 5 +- .../web-data/src/alerts/diagnosis.test.ts | 18 ++ packages/web-data/src/alerts/diagnosis.ts | 33 +++- 19 files changed, 647 insertions(+), 114 deletions(-) create mode 100644 apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts create mode 100644 apps/web/src/page/alerts/rule/view-anomaly.ts diff --git a/apps/web/src/lab/anomaly-alerts-lab.ts b/apps/web/src/lab/anomaly-alerts-lab.ts index d2ef3333c8..607c828295 100644 --- a/apps/web/src/lab/anomaly-alerts-lab.ts +++ b/apps/web/src/lab/anomaly-alerts-lab.ts @@ -4,7 +4,8 @@ import { Effect, Option, Result, Schema, Array as Arr, String as Str } from "eff import { Command, type Update } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" import { defineMessageUnion } from "foldkit/message" -import { AlertDestinationId } from "@maple/domain/http" +import { AlertDestinationId, AlertRuleId } from "@maple/domain/http" +import { formatWarehouseDateTime } from "@maple/query-engine" import * as Menu from "@maple/views/ui/menu" import * as DestinationDialog from "../page/alerts/destination/dialog" import { menuIdOf } from "../page/alerts/editor/dest-steps" @@ -17,6 +18,10 @@ import * as HubMessage from "../page/alerts/hub/message" import * as HubModel from "../page/alerts/hub/model" import * as HubUpdate from "../page/alerts/hub/update" import { view as hubView } from "../page/alerts/hub/view" +import * as RuleMessage from "../page/alerts/rule/message" +import * as RuleModel from "../page/alerts/rule/model" +import * as RuleUpdate from "../page/alerts/rule/update" +import { breadcrumbsOf as ruleCrumbs, view as ruleView } from "../page/alerts/rule/view" import * as IncidentMessage from "../page/alerts/incident/message" import * as IncidentModel from "../page/alerts/incident/model" import * as IncidentUpdate from "../page/alerts/incident/update" @@ -42,11 +47,12 @@ import type { PageContext } from "../page/contract" import type { Resources } from "../platform/resources" // `/lab/anomaly-alerts`: anomalies paging through alert rules, over fixtures. `?state=` picks one: -// editor-new, editor-scoped, editor-default, editor-critical-destination, incident-warning, incident-critical, incident-spike, -// hub-anomalies, hub-anomalies-resolved, hub-anomalies-empty, hub-rules. +// editor-new, editor-blank, editor-scoped, editor-default, editor-critical-destination, incident-warning, incident-critical, incident-spike, +// hub-anomalies, hub-anomalies-resolved, hub-anomalies-empty, hub-rules, rule-anomaly. export const LabState = Schema.Literals([ "editor-new", + "editor-blank", "editor-scoped", "editor-default", "editor-critical-destination", @@ -57,6 +63,7 @@ export const LabState = Schema.Literals([ "hub-anomalies-resolved", "hub-anomalies-empty", "hub-rules", + "rule-anomaly", ]) export type LabState = typeof LabState.Type @@ -75,6 +82,7 @@ export const Model = Schema.Struct({ hub: HubModel.Model, editor: EditorModel.Model, incident: IncidentModel.Model, + rule: RuleModel.Model, }) export type Model = typeof Model.Type @@ -82,6 +90,7 @@ export const Message = defineMessageUnion({ GotHubMessage: { message: HubMessage.Message }, GotEditorMessage: { message: EditorMessage.Message }, GotIncidentMessage: { message: IncidentMessage.Message }, + GotRuleMessage: { message: RuleMessage.Message }, }) export type Message = typeof Message.Type @@ -302,6 +311,37 @@ const incidentFor = (state: LabState) => { const PAGER_ID = Schema.decodeSync(AlertDestinationId)("0199a001-0000-7000-8000-000000000009") +/** The scoped anomaly rule's page, with its anomalies in the window. */ +const ruleFor = () => { + const rule = scopedAnomalyRuleRow() + const entered = RuleUpdate.init( + { _tag: "AlertRule", ruleId: Schema.decodeSync(AlertRuleId)(rule.id), search: {} }, + CONTEXT, + ).model + return fold( + entered, + [ + RuleMessage.Message.CompletedResolveWindow({ + window: { + startTime: formatWarehouseDateTime(NOW - 24 * 3_600_000), + endTime: formatWarehouseDateTime(NOW), + }, + nowMs: NOW, + mode: "Load", + }), + RuleMessage.Message.ReadDiagnosisClock({ nowMs: NOW }), + RuleMessage.Message.UpdatedRules({ orgId: ORG, rows: RULES }), + RuleMessage.Message.UpdatedRuleStates({ + orgId: ORG, + rows: [stateRow(8, { group_key: "__total__", last_value: 2 })], + }), + RuleMessage.Message.UpdatedIncidents({ orgId: ORG, rows: [...OPEN, ...RESOLVED] }), + RuleMessage.Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + ], + (model, message) => RuleUpdate.update(model, message, CONTEXT), + ) +} + const isEditor = Str.startsWith("editor") const isIncident = Str.startsWith("incident") @@ -311,6 +351,7 @@ export const init = (flags: Flags): Return => ({ hub: hubFor(flags.state), editor: editorFor(flags.state), incident: incidentFor(flags.state), + rule: ruleFor(), }, // The incident page's chart runtime still loads, so its anomaly chart draws. commands: Command.mapMessages( @@ -344,6 +385,15 @@ export const update = (model: Model, message: Message): Return => ), } }, + GotRuleMessage: ({ message: child }) => { + const next = RuleUpdate.update(model.rule, child, CONTEXT) + return { + model: { ...model, rule: next.model }, + commands: Command.mapMessages(keepLocal(next.commands), (inner) => + Message.GotRuleMessage({ message: inner }), + ), + } + }, GotIncidentMessage: ({ message: child }) => { const next = IncidentUpdate.update(model.incident, child, CONTEXT) return { @@ -356,34 +406,45 @@ export const update = (model: Model, message: Message): Return => }) export const breadcrumbs = (model: Model) => - isEditor(model.state) - ? editorCrumbs(model.editor) - : isIncident(model.state) - ? incidentCrumbs(model.incident) - : [{ label: "Alerts" }] + model.state === "rule-anomaly" + ? ruleCrumbs(model.rule) + : isEditor(model.state) + ? editorCrumbs(model.editor) + : isIncident(model.state) + ? incidentCrumbs(model.incident) + : [{ label: "Alerts" }] export const view = (model: Model, h: HtmlBuilder): Html => - isEditor(model.state) + model.state === "rule-anomaly" ? h.submodel({ - slotId: "lab-anomaly-editor", - model: model.editor, - view: editorView, + slotId: "lab-anomaly-rule", + model: model.rule, + view: ruleView, viewInputs: { context: CONTEXT }, - toParentMessage: (message: EditorMessage.Message) => Message.GotEditorMessage({ message }), + toParentMessage: (message: RuleMessage.Message) => Message.GotRuleMessage({ message }), }) - : isIncident(model.state) + : isEditor(model.state) ? h.submodel({ - slotId: "lab-anomaly-incident", - model: model.incident, - view: incidentView, - viewInputs: { context: CONTEXT }, - toParentMessage: (message: IncidentMessage.Message) => - Message.GotIncidentMessage({ message }), - }) - : h.submodel({ - slotId: "lab-anomaly-hub", - model: model.hub, - view: hubView, + slotId: "lab-anomaly-editor", + model: model.editor, + view: editorView, viewInputs: { context: CONTEXT }, - toParentMessage: (message: HubMessage.Message) => Message.GotHubMessage({ message }), + toParentMessage: (message: EditorMessage.Message) => + Message.GotEditorMessage({ message }), }) + : isIncident(model.state) + ? h.submodel({ + slotId: "lab-anomaly-incident", + model: model.incident, + view: incidentView, + viewInputs: { context: CONTEXT }, + toParentMessage: (message: IncidentMessage.Message) => + Message.GotIncidentMessage({ message }), + }) + : h.submodel({ + slotId: "lab-anomaly-hub", + model: model.hub, + view: hubView, + viewInputs: { context: CONTEXT }, + toParentMessage: (message: HubMessage.Message) => Message.GotHubMessage({ message }), + }) diff --git a/apps/web/src/page/alerts/destination/email-fields.scene.test.ts b/apps/web/src/page/alerts/destination/email-fields.scene.test.ts index fb1cf447cb..2fa4743f4d 100644 --- a/apps/web/src/page/alerts/destination/email-fields.scene.test.ts +++ b/apps/web/src/page/alerts/destination/email-fields.scene.test.ts @@ -63,7 +63,7 @@ describe("email member picker", () => { }, given(model), expectView(text("Ada Lovelace")).toExist(), - expectView(role("button", { name: "Remove" })).toExist(), + expectView(role("button", { name: /^Remove / })).toExist(), ) }) }) diff --git a/apps/web/src/page/alerts/editor/actions.ts b/apps/web/src/page/alerts/editor/actions.ts index 1aded2af43..0e6108183b 100644 --- a/apps/web/src/page/alerts/editor/actions.ts +++ b/apps/web/src/page/alerts/editor/actions.ts @@ -50,7 +50,11 @@ export const completeTest = (model: Model, message: Extract ({ model: settled, - outMessage: errorToast("Couldn't preview rule", error.message), + // A failed Send test is a delivery failure, not a preview one: say which. + outMessage: errorToast( + message.isSending ? "Couldn't send test notification" : "Couldn't preview rule", + error.message, + ), }), }) } diff --git a/apps/web/src/page/alerts/editor/dest-steps.ts b/apps/web/src/page/alerts/editor/dest-steps.ts index b3c7b49bda..0be62b8605 100644 --- a/apps/web/src/page/alerts/editor/dest-steps.ts +++ b/apps/web/src/page/alerts/editor/dest-steps.ts @@ -8,7 +8,7 @@ import { PageOutMessage } from "../../contract" import * as DestinationDialog from "../destination/dialog" import * as ChatConnect from "../shared/chat-connect" import { FetchEditorConnectors } from "./command" -import { addId, removeId, updateTierIds } from "./form-steps" +import { assignToTier, removeId, updateTierIds } from "./form-steps" import { Message, type Tier } from "./message" import type { Model } from "./model" import type { Return } from "./steps" @@ -33,7 +33,7 @@ export const liftDialog = (model: Model, step: ReturnType { return Option.match(decodeId(Arr.join(rest, ":")), { onNone: () => ({ model }), onSome: (id) => ({ - model: - model.form === null - ? model - : { ...model, form: updateTierIds(model.form, tier, addId(id)) }, + model: model.form === null ? model : { ...model, form: assignToTier(model.form, tier, id) }, }), }) if (kind === "type") diff --git a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts index 2d501ff38e..4c5b443f2a 100644 --- a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts @@ -47,6 +47,13 @@ const opened = ( Message.UpdatedRules({ orgId: ORG, rows: rules }), ]) +/** A pick from a tier's "Add destination" menu of a destination the org already has. */ +const pickExisting = (tier: "warning" | "critical", id: string) => + Message.GotMenuMessage({ + id: menuIdOf(tier), + message: Menu.Message.ClickedItem({ index: 0, item: { kind: "Item", value: `existing:${id}` } }), + }) + const DESCRIPTION = /^Pages when Maple detects unusual error rate, latency, throughput/ const DEFAULT_NOTE = /^This is the default anomaly rule/ @@ -73,6 +80,7 @@ describe("alert rule editor: anomaly rules", () => { Message.ChangedField({ field: "name", value: "Checkout anomalies" }), Message.ToggledAnomalySignal({ signal: "error_spike" }), Message.ToggledAnomalySignal({ signal: "error_rate" }), + pickExisting("critical", DESTINATION), ]) // Kept in the editor's order, whatever the click order. expect(anomaly.form?.anomalySignals).toEqual(["error_rate", "error_spike"]) @@ -116,6 +124,8 @@ describe("alert rule editor: anomaly rules", () => { themeResolved(), expectView(text("No changes")).toExist(), expectView(text(DEFAULT_NOTE)).not.toExist(), + // Each chip's remove button names its chip. + expectView(role("button", { name: "Remove checkout" })).toExist(), expectView(role("button", { name: "Error rate" })).not.toBeDisabled(), ) }) @@ -173,6 +183,7 @@ describe("alert rule editor: anomaly rules", () => { const NEW_ID = Schema.decodeSync(AlertDestinationId)("0199a001-0000-7000-8000-000000000009") const draft = run(opened({ template: "anomaly", serviceName: "checkout" }), [ Message.ChangedField({ field: "name", value: "Checkout anomalies" }), + pickExisting("warning", DESTINATION), ]) const opening = run(draft, [ Message.GotMenuMessage({ @@ -220,6 +231,7 @@ describe("alert rule editor: anomaly rules", () => { test("a new anomaly rule needs a service, environment or signal; the default rule covers the rest", () => { const unscoped = run(opened({ template: "anomaly" }), [ Message.ChangedField({ field: "name", value: "Everything" }), + pickExisting("critical", DESTINATION), ]) scene( { update: pageUpdate, view }, @@ -242,4 +254,49 @@ describe("alert rule editor: anomaly rules", () => { expectView(placeholder("Checkout anomalies")).toExist(), ) }) + + test("a new anomaly rule pages nobody until picked, and a destination picked for Critical leaves Warning", () => { + const fresh = opened({ template: "anomaly", serviceName: "checkout" }) + // Anomaly paging is opt-in: the org's usual destination is not pre-selected. + expect(fresh.form?.destinationIds).toEqual([]) + expect(fresh.form?.escalation?.destinationIds).toEqual([]) + const onWarning = run(fresh, [pickExisting("warning", DESTINATION)]) + expect(onWarning.form?.destinationIds).toEqual([DESTINATION]) + const moved = run(onWarning, [pickExisting("critical", DESTINATION)]) + expect(moved.form?.destinationIds).toEqual([]) + expect(moved.form?.escalation?.destinationIds).toEqual([DESTINATION]) + }) + + test("a failed Send test says the notification failed, not the preview", () => { + const draft = run(opened({ template: "anomaly", serviceName: "checkout" }), [ + Message.ChangedField({ field: "name", value: "Checkout anomalies" }), + pickExisting("critical", DESTINATION), + ]) + const sending = pageUpdate(draft, Message.ClickedSendTest()) + expect(sending.commands?.[0]?.name).toBe("RunAlertRuleTest") + const failed = pageUpdate( + sending.model, + Message.CompletedTest({ + isSending: true, + key: "", + result: Result.fail({ + title: "Alert provider rejected the request", + message: "The alert provider rejected the request.", + }), + }), + ) + expect(failed.outMessage).toMatchObject({ + title: "Couldn't send test notification", + description: "The alert provider rejected the request.", + }) + const dryRun = pageUpdate( + draft, + Message.CompletedTest({ + isSending: false, + key: "", + result: Result.fail({ title: "Bad", message: "The query timed out." }), + }), + ) + expect(dryRun.outMessage).toMatchObject({ title: "Couldn't preview rule" }) + }) }) diff --git a/apps/web/src/page/alerts/editor/form-steps.ts b/apps/web/src/page/alerts/editor/form-steps.ts index 5be9030d8d..dc09366588 100644 --- a/apps/web/src/page/alerts/editor/form-steps.ts +++ b/apps/web/src/page/alerts/editor/form-steps.ts @@ -98,6 +98,17 @@ export const updateTierIds = ( }, } +/** + * A destination picked for one tier leaves the other: critical pages already reach the warning + * destinations, so being on both only doubles the notification. + */ +export const assignToTier = (form: RuleForm, tier: Tier, id: AlertDestinationId): RuleForm => + updateTierIds( + updateTierIds(form, tier === "warning" ? "critical" : "warning", removeId(id)), + tier, + addId(id), + ) + export const addId = (id: AlertDestinationId) => (ids: ReadonlyArray) => [ ...new Set([...ids, id]), ] diff --git a/apps/web/src/page/alerts/editor/plan.ts b/apps/web/src/page/alerts/editor/plan.ts index e08e3f0cfb..8f3492a457 100644 --- a/apps/web/src/page/alerts/editor/plan.ts +++ b/apps/web/src/page/alerts/editor/plan.ts @@ -103,6 +103,8 @@ export const applyDefaultDestination = (model: Model): Model => { if ( model.isDefaultDestinationApplied || model.form === null || + // Anomaly paging is opt-in: a new anomaly rule pages nobody until someone is picked. + model.form.signalType === "anomaly" || destinations.length === 0 || model.rules === null ) diff --git a/apps/web/src/page/alerts/editor/view-details.ts b/apps/web/src/page/alerts/editor/view-details.ts index 4d9a11dfa0..a3b30a6fb2 100644 --- a/apps/web/src/page/alerts/editor/view-details.ts +++ b/apps/web/src/page/alerts/editor/view-details.ts @@ -16,6 +16,7 @@ import { Message } from "./message" import type { Model } from "./model" import { fieldStateOf, idsOf } from "./view-fields" import { hstack } from "@maple/views/ui/stack" +import { typography } from "@maple/views/ui/typography" // `DetailsSection` and `TagInput`: the rule's name, description and tags. @@ -91,8 +92,8 @@ const nameField = (h: H, model: DetailsView, form: RuleForm): Html => { const state = fieldStateOf(form.name, model.mountedForm?.name) const ids = idsOf("rule-name", false) return field(h, { className: "items-stretch gap-1.5", state }, [ - hstack(h, { gap: "2", justify: "between" }, [ - fieldLabel(h, { ids, state }, "Rule name"), + hstack(h, { gap: "2", justify: "between", className: "min-w-0" }, [ + fieldLabel(h, { ids, state, className: "shrink-0" }, "Rule name"), ...(isSuggesting ? [ button( @@ -100,10 +101,14 @@ const nameField = (h: H, model: DetailsView, form: RuleForm): Html => { { variant: "ghost", size: "xs", - className: "px-1.5 text-2xs", - attributes: [h.OnClick(Message.ClickedSuggestName())], + // A long suggestion truncates instead of widening the card past a phone's width. + className: "min-w-0 shrink px-1.5 text-2xs", + attributes: [ + h.OnClick(Message.ClickedSuggestName()), + h.Title(suggestion ?? ""), + ], }, - ["Suggest: ", suggestion ?? ""], + [typography(h, { truncate: true }, ["Suggest: ", suggestion ?? ""])], ), ] : []), diff --git a/apps/web/src/page/alerts/editor/view.ts b/apps/web/src/page/alerts/editor/view.ts index d0355857b9..8524514476 100644 --- a/apps/web/src/page/alerts/editor/view.ts +++ b/apps/web/src/page/alerts/editor/view.ts @@ -163,7 +163,7 @@ const surface = (h: H, model: Model, form: RuleForm, timeZone: string): Readonly ), ]), h.div( - [h.Class("grid gap-6 border-t pt-8 lg:grid-cols-2 lg:items-start")], + [h.Class("grid grid-cols-1 gap-6 border-t pt-8 lg:grid-cols-2 lg:items-start")], [ ruleStep( h, diff --git a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts index b5485f355d..c5513139e9 100644 --- a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts +++ b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts @@ -243,4 +243,19 @@ describe("alerts hub: anomalies", () => { expectView(text("Search anomalies")).toExist(), ) }) + + test("leaving the Inbox drops the Anomalies view's filters from the tab links", () => { + scene( + { update: pageUpdate, view }, + given(loaded({ signal: "anomaly", anomalyStatus: "resolved", serviceName: "checkout" })), + expectView(role("link", { name: /^Rules/ })).toHaveAttr( + "href", + "/alerts?tab=rules&serviceName=checkout", + ), + expectView(role("link", { name: /^Inbox/ })).toHaveAttr( + "href", + "/alerts?tab=inbox&serviceName=checkout&signal=anomaly&anomalyStatus=resolved", + ), + ) + }) }) diff --git a/apps/web/src/page/alerts/hub/view.ts b/apps/web/src/page/alerts/hub/view.ts index c9b0b37348..fcdce8995f 100644 --- a/apps/web/src/page/alerts/hub/view.ts +++ b/apps/web/src/page/alerts/hub/view.ts @@ -84,7 +84,12 @@ const tabBar = ( }) return { key: tab.value, - href: alertsHref({ ...model.search, tab: tab.value }), + // The Anomalies view belongs to the Inbox: other tabs drop its filters. + href: alertsHref( + tab.value === "inbox" + ? { ...model.search, tab: tab.value } + : { ...model.search, tab: tab.value, signal: undefined, anomalyStatus: undefined }, + ), label: tab.label, count: count !== undefined && count > 0 ? count : undefined, countTone: tab.value === "inbox" ? "error" : "muted", diff --git a/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts b/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts new file mode 100644 index 0000000000..1c734ab820 --- /dev/null +++ b/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts @@ -0,0 +1,121 @@ +import { OrgId } from "@maple/domain" +import { AlertRuleId } from "@maple/domain/http" +import { Schema, Array as Arr } from "effect" +import { expect as expectView, given, Mount, scene, text, withViewInputs } from "foldkit/scene" +import { describe, expect, test } from "vitest" +import { ResolveThemeColors, ThemeMessage } from "@maple/views/charts/theme-colors" +import type { PageContext } from "../../contract" +import { anomalyIncidentRow, anomalyRuleRow, defaultAnomalyRuleRow } from "../shared/anomaly-test-fixtures" +import { DESTINATION_ID, destinationRow, NOW, ORG, stateRow } from "../shared/test-rows" +import { Message } from "./message" +import type { Model } from "./model" +import { init, update } from "./update" +import { view as pageView } from "./view" + +const orgId = Schema.decodeSync(OrgId)(ORG) +const context: PageContext = { orgId, pinnedNamespace: null, timeZone: "UTC" } +const pageUpdate = (model: Model, message: Message) => update(model, message, context) +const view = withViewInputs(pageView, { context })() +const names = (commands: ReadonlyArray<{ readonly name: string }> | undefined) => + Arr.map(commands ?? [], ({ name }) => name) +const window = { startTime: "2026-09-30 12:00:00", endTime: "2026-10-01 12:00:00" } +const themeResolved = () => + Mount.resolve(ResolveThemeColors, ThemeMessage.ResolvedThemeColors({ colors: {} })) + +const SCOPED = anomalyRuleRow(8, { + name: "Demo API anomalies", + service_names_json: ["demo-api"], + anomaly_signals_json: ["error_rate", "latency_p95"], + criticalIds: [DESTINATION_ID], +}) + +const loaded = ( + rule = SCOPED, + incidents = [ + anomalyIncidentRow({ + n: 1, + ruleN: 8, + severity: "critical", + service: "demo-api", + environment: "production", + }), + ], +) => { + const entered = init( + { _tag: "AlertRule", ruleId: Schema.decodeSync(AlertRuleId)(rule.id), search: {} }, + context, + ) + return Arr.reduce( + [ + Message.CompletedResolveWindow({ window, nowMs: NOW, mode: "Load" }), + Message.ReadDiagnosisClock({ nowMs: NOW }), + Message.UpdatedRules({ orgId: ORG, rows: [rule] }), + // The anomaly heartbeat row: its key is the ungrouped one, not a group anyone picked. + Message.UpdatedRuleStates({ + orgId: ORG, + rows: [ + stateRow(0, { + rule_id: rule.id, + group_key: "__total__", + last_value: 2, + }), + ], + }), + Message.UpdatedIncidents({ orgId: ORG, rows: incidents }), + Message.UpdatedDestinations({ orgId: ORG, rows: [destinationRow()] }), + ], + entered.model, + (model, message) => pageUpdate(model, message).model, + ) +} + +describe("alert rule page: anomaly rules", () => { + test("says what the rule pages on, and never asks for a query replay", () => { + const model = loaded() + expect(model.preview.key).toBeNull() + scene( + { update: pageUpdate, view }, + given(model), + themeResolved(), + expectView( + text("Pages on anomalies in demo-api · all environments · Error rate, p95 latency"), + ).toExist(), + expectView(text(/^Anomalies: /)).toExist(), + expectView(text("Anomaly on demo-api (production)")).toExist(), + expectView(text(/^The rule's query, replayed/)).not.toExist(), + expectView(text("Breaches to fire")).not.toExist(), + expectView(text("Min samples")).not.toExist(), + expectView(text("Threshold compared")).not.toExist(), + expectView(text("Group")).not.toExist(), + expectView(text("__total__")).not.toExist(), + ) + }) + + test("configuration lists scope, signals, each tier's destinations and the cadence", () => { + scene( + { update: pageUpdate, view }, + given(loaded()), + themeResolved(), + expectView(text("Signals")).toExist(), + expectView(text("p95 latency")).toExist(), + expectView(text("Nobody")).toExist(), + expectView(text("On-call webhook")).toExist(), + expectView(text("every 1m")).toExist(), + ) + }) + + test("an unrouted default rule reads as opt-in, with no anomalies in the window said plainly", () => { + const fallback = loaded(defaultAnomalyRuleRow(), []) + expect(names(pageUpdate(fallback, Message.ClickedReload()).commands)).not.toContain( + "FetchAlertRulePreview", + ) + scene( + { update: pageUpdate, view }, + given(fallback), + themeResolved(), + expectView(text("No anomalies in this window.")).toExist(), + expectView(text(/fires into the void/)).not.toExist(), + expectView(text("Configure destinations")).not.toExist(), + ) + }) +}) diff --git a/apps/web/src/page/alerts/rule/view-anomaly.ts b/apps/web/src/page/alerts/rule/view-anomaly.ts new file mode 100644 index 0000000000..50982c48fd --- /dev/null +++ b/apps/web/src/page/alerts/rule/view-anomaly.ts @@ -0,0 +1,179 @@ +import type { AlertDestinationDocument, AlertIncidentDocument, AlertRuleDocument } from "@maple/domain/http" +import { ANOMALY_SIGNAL_LABELS } from "@maple/web-data/alerts/anomaly-rule" +import { formatAlertDuration } from "@maple/web-data/alerts/form-utils" +import { incidentTitle } from "@maple/web-data/alerts/overview-derive" +import { formatDateInTimezone, formatTimeInTimezone } from "@maple/web-data/lib/timezone-format" +import { Array as Arr } from "effect" +import type { Html, HtmlBuilder } from "foldkit/html" +import { card, cardPanel } from "@maple/views/ui/card" +import { badge } from "@maple/views/ui/display" +import { eyebrow } from "@maple/views/ui/eyebrow" +import { keyValue, keyValueList } from "@maple/views/ui/key-value" +import { dividedList } from "@maple/views/ui/rule" +import { hstack } from "@maple/views/ui/stack" +import { rowLink } from "@maple/views/ui/table" +import { typography } from "@maple/views/ui/typography" +import { sectionHeading } from "../../kit/section-heading" +import { alertStatusBadge, severityBadge } from "../shared/badges" +import { incidentHref } from "../shared/links" +import { isoMs } from "../shared/time" + +// An anomaly rule's Overview: the anomalies it paged on in the window, and its scope and routing. +// It has no query to replay and no threshold, window or sample count of its own. + +const INCIDENT_LIMIT = 20 + +const overlaps = (incident: AlertIncidentDocument, range: Readonly<{ min: number; max: number }>): boolean => + isoMs(incident.firstTriggeredAt) <= range.max && + (incident.resolvedAt === null || isoMs(incident.resolvedAt) >= range.min) + +/** The rule's anomaly incidents that were open at some point in the window, newest first. */ +export const anomalyIncidentsSection = ( + h: HtmlBuilder, + options: Readonly<{ + incidents: ReadonlyArray + range: Readonly<{ min: number; max: number }> + rangeLabel: string + now: number + timeZone: string + }>, +): Html => { + const inWindow = Arr.take( + Arr.filter(options.incidents, (incident) => overlaps(incident, options.range)), + INCIDENT_LIMIT, + ) + const at = (iso: string) => + `${formatDateInTimezone(iso, { timeZone: options.timeZone })} ${formatTimeInTimezone(iso, { timeZone: options.timeZone })}` + return h.section( + [h.Class("space-y-3")], + [ + h.div( + [h.Class("space-y-1")], + [ + eyebrow(h, { as: "h2", className: "mb-0 block" }, [`Anomalies: ${options.rangeLabel}`]), + typography(h, { size: "xs", tone: "muted", as: "p" }, [ + "What Maple's anomaly detector found in this rule's scope. The detector decides; this rule only routes.", + ]), + ], + ), + inWindow.length === 0 + ? typography(h, { size: "sm", tone: "muted", as: "p" }, ["No anomalies in this window."]) + : dividedList( + h, + { as: "ul" }, + Arr.map(inWindow, (incident) => + h.li( + [ + h.Key(incident.id), + h.Class("relative flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-3"), + ], + [ + severityBadge(h, incident.peakSeverity ?? incident.severity), + rowLink( + h, + { + href: incidentHref(incident.id), + className: "min-w-0 flex-1 truncate font-medium", + }, + [incidentTitle(incident)], + ), + typography(h, { size: "xs", tone: "muted", mono: true, numeric: true }, [ + incident.status === "open" + ? `open since ${at(incident.firstTriggeredAt)}` + : `${at(incident.firstTriggeredAt)} · lasted ${formatAlertDuration(incident.firstTriggeredAt, incident.resolvedAt)}`, + ]), + ], + ), + ), + ), + ], + ) +} + +const valueList = (h: HtmlBuilder, values: ReadonlyArray, all: string): Html => + values.length === 0 + ? typography(h, { weight: "medium", mono: true }, [all]) + : h.div( + [h.Class("flex flex-wrap justify-end gap-1")], + Arr.map(values, (value) => badge(h, { variant: "outline", className: "text-xs" }, [value])), + ) + +const tierDestinations = ( + h: HtmlBuilder, + ids: ReadonlyArray, + destinations: ReadonlyArray, +): Html => { + const byId = new Map( + Arr.map(destinations, (destination) => [destination.id, destination.name]), + ) + const names = Arr.flatMap(ids, (id): ReadonlyArray => { + const name = byId.get(id) + return name === undefined ? [] : [name] + }) + return names.length === 0 ? typography(h, { tone: "muted" }, ["Nobody"]) : valueList(h, names, "") +} + +const row = (h: HtmlBuilder, label: string, children: ReadonlyArray): Html => + keyValue(h, { label, wrap: true, className: "items-center" }, children) + +/** An anomaly rule's configuration: its scope, each tier's destinations and its cadence. */ +export const anomalyConfiguration = ( + h: HtmlBuilder, + rule: AlertRuleDocument, + destinations: ReadonlyArray, +): Html => { + const tier = (severity: "warning" | "critical"): ReadonlyArray => + Arr.flatMap(rule.thresholds, (threshold): ReadonlyArray => + threshold.severity === severity ? threshold.destinationIds : [], + ) + return h.div( + [h.Class("space-y-3")], + [ + sectionHeading(h, { title: "Configuration" }), + card(h, [ + cardPanel( + h, + [ + keyValueList( + h, + { className: "grid grid-cols-1 gap-x-8 gap-y-2 text-sm sm:grid-cols-2" }, + [ + ...(rule.notes + ? [ + row(h, "Notes", [ + h.span([h.Class("whitespace-pre-wrap")], [rule.notes]), + ]), + ] + : []), + row(h, "Signal", [typography(h, { weight: "medium" }, ["Anomaly"])]), + row(h, "Services", [valueList(h, rule.serviceNames, "all")]), + row(h, "Environments", [valueList(h, rule.environments, "all")]), + row(h, "Signals", [ + valueList( + h, + Arr.map( + rule.anomalySignals ?? [], + (signal) => ANOMALY_SIGNAL_LABELS[signal], + ), + "all", + ), + ]), + row(h, "Warning", [tierDestinations(h, tier("warning"), destinations)]), + row(h, "Critical", [tierDestinations(h, tier("critical"), destinations)]), + row(h, "Checked", [typography(h, { weight: "medium" }, ["every 1m"])]), + row(h, "Status", [ + hstack(h, { gap: "2", justify: "end" }, [ + alertStatusBadge(h, rule.enabled ? "ok" : "disabled", { + label: rule.enabled ? "Enabled" : "Disabled", + }), + ]), + ]), + ], + ), + ], + "p-5", + ), + ]), + ], + ) +} diff --git a/apps/web/src/page/alerts/rule/view-overview.ts b/apps/web/src/page/alerts/rule/view-overview.ts index 91576001b5..40eb57d5a7 100644 --- a/apps/web/src/page/alerts/rule/view-overview.ts +++ b/apps/web/src/page/alerts/rule/view-overview.ts @@ -40,6 +40,7 @@ import { } from "./derive" import { Message } from "./message" import type { Model, SignalSource } from "./model" +import { anomalyConfiguration, anomalyIncidentsSection } from "./view-anomaly" import { checksPanel } from "./view-checks" import { diagnosisPanel } from "./view-diagnosis" import { queryTimeCard } from "./view-query-time" @@ -318,6 +319,73 @@ export const overviewTab = (h: H, model: Model, rule: AlertRuleDocument, timeZon const isChartReady = model.themeColors !== null && isChartRuntimeReady(model.chartRuntime) const isChecksLoading = AsyncData.isIdle(model.checks.data) || AsyncData.isLoading(model.checks.data) + const isAnomaly = rule.signalType === "anomaly" + // An anomaly rule has no query to replay: its incidents stand in for the chart. + const signalSection = h.div( + [h.Class("space-y-3")], + [ + h.div( + [h.Class("flex flex-wrap items-end justify-between gap-x-4 gap-y-2")], + [ + h.div( + [h.Class("space-y-1")], + [ + eyebrow(h, { as: "h2", className: "mb-0 block" }, [ + `${signalLabels[rule.signalType]}: ${rangeLabel}`, + ]), + typography(h, { size: "xs", tone: "muted", as: "p" }, [ + SIGNAL_SOURCE_DESCRIPTION.get(model.signalSource) ?? "", + ]), + ], + ), + segmentedSelect(h, { + options: [ + { + value: "preview" as const, + label: SIGNAL_SOURCE_LABEL.preview, + isDisabled: !isPreviewAvailable, + }, + { + value: "checks" as const, + label: SIGNAL_SOURCE_LABEL.checks, + isDisabled: !isChecksAvailable, + }, + ], + value: model.signalSource, + onChange: (source) => Message.ClickedSignalSource({ source }), + size: "sm", + ariaLabel: "Chart data source", + }), + ], + ), + ruleChart(h, { + chartId: "alert-rule-chart", + chart: model.chart, + toChartMessage: (message) => Message.GotChartMessage({ message }), + colors: model.themeColors ?? {}, + timeZone, + preview: preview.preview, + checks: chartChecks, + incidents: ruleIncidents, + threshold: rule.threshold, + thresholdUpper: rule.thresholdUpper, + criticalThreshold: rule.thresholds[1]?.value ?? null, + comparator: rule.comparator, + signalType: rule.signalType, + window: timelineRangeOf(model), + source: model.signalSource, + railCoverage, + selectedBucket: model.selectedBucket?.index ?? null, + onSelectBucket: (index) => Message.ClickedRailBucket({ index }), + isLoading: + preview.isLoading || + !isChartReady || + (rule.signalType === "raw_query" && isChecksLoading), + error: preview.error, + }), + ], + ) + return h.div( [h.Class("space-y-6")], [ @@ -331,71 +399,17 @@ export const overviewTab = (h: H, model: Model, rule: AlertRuleDocument, timeZon now: model.nowMs, timeZone, }), - ...Arr.fromNullishOr(queryTimeCard(h, rule, checks, timeZone)), - h.div( - [h.Class("space-y-3")], - [ - h.div( - [h.Class("flex flex-wrap items-end justify-between gap-x-4 gap-y-2")], - [ - h.div( - [h.Class("space-y-1")], - [ - eyebrow(h, { as: "h2", className: "mb-0 block" }, [ - `${signalLabels[rule.signalType]}: ${rangeLabel}`, - ]), - typography(h, { size: "xs", tone: "muted", as: "p" }, [ - SIGNAL_SOURCE_DESCRIPTION.get(model.signalSource) ?? "", - ]), - ], - ), - segmentedSelect(h, { - options: [ - { - value: "preview" as const, - label: SIGNAL_SOURCE_LABEL.preview, - isDisabled: !isPreviewAvailable, - }, - { - value: "checks" as const, - label: SIGNAL_SOURCE_LABEL.checks, - isDisabled: !isChecksAvailable, - }, - ], - value: model.signalSource, - onChange: (source) => Message.ClickedSignalSource({ source }), - size: "sm", - ariaLabel: "Chart data source", - }), - ], - ), - ruleChart(h, { - chartId: "alert-rule-chart", - chart: model.chart, - toChartMessage: (message) => Message.GotChartMessage({ message }), - colors: model.themeColors ?? {}, - timeZone, - preview: preview.preview, - checks: chartChecks, - incidents: ruleIncidents, - threshold: rule.threshold, - thresholdUpper: rule.thresholdUpper, - criticalThreshold: rule.thresholds[1]?.value ?? null, - comparator: rule.comparator, - signalType: rule.signalType, - window: timelineRangeOf(model), - source: model.signalSource, - railCoverage, - selectedBucket: model.selectedBucket?.index ?? null, - onSelectBucket: (index) => Message.ClickedRailBucket({ index }), - isLoading: - preview.isLoading || - !isChartReady || - (rule.signalType === "raw_query" && isChecksLoading), - error: preview.error, - }), - ], - ), + ...(isAnomaly + ? [ + anomalyIncidentsSection(h, { + incidents: ruleIncidents, + range: timelineRangeOf(model), + rangeLabel, + now: model.nowMs, + timeZone, + }), + ] + : [...Arr.fromNullishOr(queryTimeCard(h, rule, checks, timeZone)), signalSection]), model.incidents === null ? skeleton(h, "h-40 w-full") : overviewIncident === null @@ -425,7 +439,7 @@ export const overviewTab = (h: H, model: Model, rule: AlertRuleDocument, timeZon [icon(h, "ChatBubbleSparkleIcon"), "Open investigation"], ), ]), - configuration(h, rule), + isAnomaly ? anomalyConfiguration(h, rule, destinationsOf(model)) : configuration(h, rule), Option.match(checksFailureOf(model), { onSome: (failure) => h.div( @@ -439,7 +453,11 @@ export const overviewTab = (h: H, model: Model, rule: AlertRuleDocument, timeZon }), ], ), - onNone: () => checksPanel(h, model, { rule, page, isLoading: isChecksLoading, timeZone }), + // An anomaly rule's checks only record a severity code: its incidents say more. + onNone: () => + isAnomaly + ? h.empty + : checksPanel(h, model, { rule, page, isLoading: isChecksLoading, timeZone }), }), ], ) diff --git a/apps/web/src/page/alerts/rule/view.ts b/apps/web/src/page/alerts/rule/view.ts index bc4851c344..d4d69ecbd2 100644 --- a/apps/web/src/page/alerts/rule/view.ts +++ b/apps/web/src/page/alerts/rule/view.ts @@ -1,4 +1,5 @@ import type { AlertIncidentDocument, AlertRuleDocument } from "@maple/domain/http" +import { anomalyScopeSummary } from "@maple/web-data/alerts/anomaly-rule" import { comparatorLabels, formatSignalValue, signalLabels } from "@maple/web-data/alerts/form-utils" import { AsyncData, Submodel } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" @@ -41,6 +42,8 @@ type Tab = (typeof TABS)[number] const activeTabOf = (model: Model): Tab => (model.search.tab === "history" ? "history" : "overview") export const ruleSubtitle = (rule: AlertRuleDocument): string => { + // An anomaly rule has no threshold or window: what it pages on is the whole story. + if (rule.signalType === "anomaly") return `Pages on anomalies in ${anomalyScopeSummary(rule)}` const tiers = Arr.join( Arr.map(rule.thresholds, (tier) => rule.thresholds.length > 1 @@ -71,7 +74,13 @@ const ruleSilence = ( Arr.flatMap(openIncidents, (incident) => Option.toArray( Option.map( - activeSilenceFor(silences, rule, incident.groupKey, model.nowMs), + activeSilenceFor( + silences, + rule, + incident.groupKey, + model.nowMs, + incident.labels ?? null, + ), (silence) => ({ silence, group: incidentGroup(incident), diff --git a/packages/views/src/ui/combobox-view.ts b/packages/views/src/ui/combobox-view.ts index 4ef65f32e4..cbc420a251 100644 --- a/packages/views/src/ui/combobox-view.ts +++ b/packages/views/src/ui/combobox-view.ts @@ -433,7 +433,8 @@ const chip = ( h.button( [ h.AriaDisabled(false), - h.AriaLabel("Remove"), + // Named for its chip, so a screen reader can tell several remove buttons apart. + h.AriaLabel(`Remove ${parts.chipLabel?.(value) ?? value}`), h.Class(C.comboboxChipRemoveStyles), h.DataAttribute("slot", "combobox-chip-remove"), h.Tabindex(-1), diff --git a/packages/views/src/ui/combobox.scene.test.ts b/packages/views/src/ui/combobox.scene.test.ts index a42ea4527f..afa86e5b74 100644 --- a/packages/views/src/ui/combobox.scene.test.ts +++ b/packages/views/src/ui/combobox.scene.test.ts @@ -72,7 +72,8 @@ describe("combobox scene (chips)", () => { chips, given(init("services", "multiple", { selected: ["checkout-svc", "payment-svc"], isOpen: true })), Mount.resolve(MountCombobox, Message.CompletedMountCombobox()), - expectAll(all.role("button", { name: "Remove" })).toHaveCount(2), + expectAll(all.role("button", { name: /^Remove / })).toHaveCount(2), + expect(role("button", { name: "Remove checkout-svc" })).toExist(), expect(option("checkout-svc")).toHaveAttr("aria-selected", "true"), expect(option("api-gateway")).toHaveAttr("aria-selected", "false"), expect(role("listbox")).toHaveAttr("aria-multiselectable", "true"), @@ -100,7 +101,7 @@ describe("combobox scene (chips)", () => { scene( chips, given(init("services", "multiple", { selected: ["checkout-svc", "payment-svc"] })), - click(first(all.role("button", { name: "Remove" }))), + click(first(all.role("button", { name: /^Remove / }))), expectOutMessage(OutMessage.ChangedSelection({ selected: ["payment-svc"] })), ) }) diff --git a/packages/web-data/src/alerts/diagnosis.test.ts b/packages/web-data/src/alerts/diagnosis.test.ts index a764964f66..c69397c4dc 100644 --- a/packages/web-data/src/alerts/diagnosis.test.ts +++ b/packages/web-data/src/alerts/diagnosis.test.ts @@ -312,3 +312,21 @@ describe("scheduled but never evaluated", () => { expect(diagnosisVerdict(stages).status).toBe("warn") }) }) + +describe("buildDiagnosis for anomaly rules", () => { + const anomalyRule = (overrides: Record = {}) => + makeRule({ signalType: "anomaly", comparator: "gte", threshold: 1, destinationIds: [], ...overrides }) + + it("drops the data and threshold stages: the detector decides, not a query or threshold", () => { + const ids = Arr.map(diagnose({ rule: anomalyRule() }), (s) => s.id) + expect(ids).not.toContain("data") + expect(ids).not.toContain("threshold") + }) + + it("an unrouted default rule passes notification as opt-in; an unrouted scoped one still fails", () => { + expect(stage(diagnose({ rule: anomalyRule() }), "notification").status).toBe("pass") + expect( + stage(diagnose({ rule: anomalyRule({ serviceNames: ["checkout"] }) }), "notification").status, + ).toBe("fail") + }) +}) diff --git a/packages/web-data/src/alerts/diagnosis.ts b/packages/web-data/src/alerts/diagnosis.ts index 56c061d00a..e60aa593a1 100644 --- a/packages/web-data/src/alerts/diagnosis.ts +++ b/packages/web-data/src/alerts/diagnosis.ts @@ -16,6 +16,7 @@ import { formatRelativeFrom } from "@maple/ui/lib/time-format" import { countLabel } from "@maple/ui/lib/format" import type { AlertRuleStateRow } from "../collections/alerts" import { comparatorLabels, formatSignalValue } from "./form-utils" +import { isDefaultAnomalyRule } from "./anomaly-rule" import { staleThresholdMs } from "./rule-status" import { formatTimestampInTimezone } from "../lib/timezone-format" @@ -439,7 +440,7 @@ export function buildDiagnosis(input: DiagnosisInput): DiagnosisStage[] { evidence: Arr.map( groupIncidents.slice(0, 5), (i) => - `${i.groupKey ?? "all"} — opened ${relative(now, new Date(i.firstTriggeredAt).getTime())}`, + `${i.groupLabel ?? i.groupKey ?? "all"}, opened ${relative(now, new Date(i.firstTriggeredAt).getTime())}`, ), }) } else { @@ -511,9 +512,37 @@ export function buildDiagnosis(input: DiagnosisInput): DiagnosisStage[] { }) } - return stages + return rule.signalType === "anomaly" ? anomalyStages(rule, stages) : stages } +/** + * An anomaly rule has no query, samples or threshold of its own: the detector decides. Its checks + * only read the detector's open anomalies, and the default rule may stay unrouted (paging is opt-in). + */ +const anomalyStages = ( + rule: DiagnosisInput["rule"], + stages: ReadonlyArray, +): DiagnosisStage[] => + Arr.flatMap(stages, (stage): ReadonlyArray => { + if (stage.id === "data" || stage.id === "threshold") return [] + if (stage.id === "query") return [{ ...stage, label: "Detector read" }] + if (stage.id === "incident" && stage.status === "pass") + return [{ ...stage, summary: "No open anomaly in this rule's scope" }] + if (stage.id === "notification" && rule.destinationIds.length === 0 && isDefaultAnomalyRule(rule)) + return [ + { + id: "notification", + label: "Notification delivered", + status: "pass", + summary: "Not routed: anomaly paging is opt-in", + evidence: [ + "Add destinations to this rule to page someone when Maple detects an anomaly.", + ], + }, + ] + return [stage] + }) + /** The verdict line for the panel header: the first failing/warning stage's summary. */ export function diagnosisVerdict(stages: ReadonlyArray): { readonly status: DiagnosisStageStatus From 8a1707e3933877596bf9fc2bf57a52822f747208 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Fri, 6 Nov 2026 22:34:42 +0100 Subject: [PATCH 22/28] test(web): the unrouted anomaly test removes a destination the rule had --- .../alerts/editor/editor-anomaly.scene.test.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts index 4c5b443f2a..6b31c291f2 100644 --- a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts @@ -100,10 +100,22 @@ describe("alert rule editor: anomaly rules", () => { }) test("a scoped anomaly rule with nobody to page cannot be saved", () => { - const unrouted = run(opened({ template: "anomaly", serviceName: "search" }), [ - Message.ChangedField({ field: "name", value: "Search anomalies" }), - Message.ClickedRemoveDestination({ tier: "warning", id: DESTINATION }), + const routed = run(opened({ template: "anomaly" }), [ + Message.ChangedField({ field: "name", value: "Error rate anomalies" }), + Message.ToggledAnomalySignal({ signal: "error_rate" }), + pickExisting("warning", DESTINATION), ]) + // Scoped and routed: the only thing left to take away is the destination. + scene( + { update: pageUpdate, view }, + given(routed), + themeResolved(), + expectView(role("button", { name: "Create rule" })).toBeEnabled(), + ) + const unrouted = pageUpdate( + routed, + Message.ClickedRemoveDestination({ tier: "warning", id: DESTINATION }), + ).model scene( { update: pageUpdate, view }, given(unrouted), From 1325efac63fba26be0c124ffe0ca7fed4d08e1d7 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:15:59 +0100 Subject: [PATCH 23/28] chore(web-data): keep the shared default anomaly rule helper after the main merge --- packages/web-data/src/alerts/rule-status.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/web-data/src/alerts/rule-status.ts b/packages/web-data/src/alerts/rule-status.ts index 0660c5e969..7b7113da0a 100644 --- a/packages/web-data/src/alerts/rule-status.ts +++ b/packages/web-data/src/alerts/rule-status.ts @@ -33,14 +33,6 @@ export interface DerivedRuleStatus { * scheduler outages for up to 3x the window (72h on a daily rule). * Shared with the diagnosis panel so both surfaces agree on "stale". */ -const isDefaultAnomalyRule = ( - rule: Pick, -): boolean => - rule.signalType === "anomaly" && - rule.serviceNames.length === 0 && - rule.environments.length === 0 && - (rule.anomalySignals ?? []).length === 0 - export const staleThresholdMs = ( rule: Pick, ): number => 3 * Math.max(ALERT_MAX_EVALUATION_INTERVAL_MINUTES, alertRuleIntervalMinutes(rule)) * 60_000 From 6ff79c06b57ed57f6d9ee15ec72040ebc33a008f Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:21:54 +0100 Subject: [PATCH 24/28] chore: rerun CI against main after the base change From 19a62867bb82fcbfeaedc60c9a5a257db6622971 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:32:11 +0100 Subject: [PATCH 25/28] fix(web): anomaly UI fixes from review --- apps/web/src/page/alerts/editor/dest-steps.ts | 19 ++++- .../editor/editor-anomaly.scene.test.ts | 27 +++++++ apps/web/src/page/alerts/editor/form-steps.ts | 9 ++- apps/web/src/page/alerts/editor/init.ts | 1 + apps/web/src/page/alerts/editor/model.ts | 4 +- apps/web/src/page/alerts/editor/plan.ts | 7 +- apps/web/src/page/alerts/editor/update.ts | 16 ++++- .../alerts/hub/hub-anomalies.scene.test.ts | 2 + .../web/src/page/alerts/hub/view-anomalies.ts | 5 +- .../incident/incident-anomaly.scene.test.ts | 24 +++++++ apps/web/src/page/alerts/incident/update.ts | 17 ++++- .../alerts/rule/rule-anomaly.scene.test.ts | 32 ++++++++- apps/web/src/page/alerts/rule/view-anomaly.ts | 12 +++- .../anomalies/detail/anomaly.scene.test.ts | 72 ++++++++++++++++++- apps/web/src/page/anomalies/detail/view.ts | 15 ++-- packages/views/src/ui/combobox-view.ts | 23 +++++- packages/views/src/ui/combobox.scene.test.ts | 25 +++++++ 17 files changed, 284 insertions(+), 26 deletions(-) diff --git a/apps/web/src/page/alerts/editor/dest-steps.ts b/apps/web/src/page/alerts/editor/dest-steps.ts index 0be62b8605..4aff415c1c 100644 --- a/apps/web/src/page/alerts/editor/dest-steps.ts +++ b/apps/web/src/page/alerts/editor/dest-steps.ts @@ -33,7 +33,11 @@ export const liftDialog = (model: Model, step: ReturnType { return Option.match(decodeId(Arr.join(rest, ":")), { onNone: () => ({ model }), onSome: (id) => ({ - model: model.form === null ? model : { ...model, form: assignToTier(model.form, tier, id) }, + model: + model.form === null + ? model + : { ...model, autoDestinationId: null, form: assignToTier(model.form, tier, id) }, }), }) if (kind === "type") @@ -156,4 +163,10 @@ export const menuStep = (model: Model, menuId: string, message: Menu.Message): R export const removeDestination = (model: Model, tier: Tier, id: AlertDestinationId): Return => model.form === null ? { model } - : { model: { ...model, form: updateTierIds(model.form, tier, removeId(id)) } } + : { + model: { + ...model, + autoDestinationId: null, + form: updateTierIds(model.form, tier, removeId(id)), + }, + } diff --git a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts index 6b31c291f2..259d87d7ce 100644 --- a/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/editor/editor-anomaly.scene.test.ts @@ -311,4 +311,31 @@ describe("alert rule editor: anomaly rules", () => { ) expect(dryRun.outMessage).toMatchObject({ title: "Couldn't preview rule" }) }) + + test("a tiered threshold rule keeps one destination on both tiers and saves", () => { + const tiered = run(opened({ template: "slow_p95" }), [ + Message.ChangedField({ field: "name", value: "Slow checkout" }), + Message.ClickedAddCriticalTier(), + pickExisting("critical", DESTINATION), + ]) + expect(tiered.form?.destinationIds).toEqual([DESTINATION]) + expect(tiered.form?.escalation?.destinationIds).toEqual([DESTINATION]) + const saved = pageUpdate(tiered, Message.ClickedSave()) + expect(saved.commands?.[0]?.name).toBe("SaveAlertRule") + }) + + test("switching to Anomaly drops the destination the editor pre-selected, not one the user picked", () => { + const blank = opened({}) + // A new query rule starts on the org's usual destination. + expect(blank.form?.destinationIds).toEqual([DESTINATION]) + const anomaly = pageUpdate(blank, Message.ClickedSignalKind({ kind: "anomaly" })).model + expect(anomaly.form?.destinationIds).toEqual([]) + expect(anomaly.form?.escalation?.destinationIds).toEqual([]) + const chosen = run(blank, [ + Message.ClickedRemoveDestination({ tier: "warning", id: DESTINATION }), + pickExisting("warning", DESTINATION), + Message.ClickedSignalKind({ kind: "anomaly" }), + ]) + expect(chosen.form?.destinationIds).toEqual([DESTINATION]) + }) }) diff --git a/apps/web/src/page/alerts/editor/form-steps.ts b/apps/web/src/page/alerts/editor/form-steps.ts index dc09366588..5dc3de4bf9 100644 --- a/apps/web/src/page/alerts/editor/form-steps.ts +++ b/apps/web/src/page/alerts/editor/form-steps.ts @@ -99,12 +99,15 @@ export const updateTierIds = ( } /** - * A destination picked for one tier leaves the other: critical pages already reach the warning - * destinations, so being on both only doubles the notification. + * A destination picked for a tier joins it. On an anomaly rule it also leaves the other tier: + * critical anomaly pages already reach the warning destinations, so being on both only doubles + * the page. A threshold rule keeps it on both, since each of its tiers needs its own destinations. */ export const assignToTier = (form: RuleForm, tier: Tier, id: AlertDestinationId): RuleForm => updateTierIds( - updateTierIds(form, tier === "warning" ? "critical" : "warning", removeId(id)), + form.signalType === "anomaly" + ? updateTierIds(form, tier === "warning" ? "critical" : "warning", removeId(id)) + : form, tier, addId(id), ) diff --git a/apps/web/src/page/alerts/editor/init.ts b/apps/web/src/page/alerts/editor/init.ts index fa95a2d4d4..2be612fcbe 100644 --- a/apps/web/src/page/alerts/editor/init.ts +++ b/apps/web/src/page/alerts/editor/init.ts @@ -39,6 +39,7 @@ export const init = (route: CreateRoute, _context: PageContext): PageReturn { Arr.map(model.rules, (row) => rowToAlertRuleDocument(row, states)), ) const form = model.form + const isApplied = fallback !== null && form.destinationIds.length === 0 return { ...model, isDefaultDestinationApplied: true, - form: - fallback !== null && form.destinationIds.length === 0 - ? { ...form, destinationIds: [fallback] } - : form, + autoDestinationId: isApplied ? fallback : null, + form: isApplied ? { ...form, destinationIds: [fallback] } : form, } } diff --git a/apps/web/src/page/alerts/editor/update.ts b/apps/web/src/page/alerts/editor/update.ts index 2066af1bdd..f22433d58c 100644 --- a/apps/web/src/page/alerts/editor/update.ts +++ b/apps/web/src/page/alerts/editor/update.ts @@ -121,7 +121,21 @@ export const update = (model: Model, message: Message, context: PageContext): Re ClickedSignalKind: ({ kind }) => { if (model.form === null || isProtectedDefault(model)) return { model } const moved = F.switchKind(model.form, kind) - return editForm({ ...model, kindNotice: moved.notice }, context, () => moved.form) + // Anomaly paging is opt-in: a destination the editor chose, not the user, does not carry over. + const auto = model.autoDestinationId + const form = + kind === "anomaly" && auto !== null + ? F.updateTierIds( + F.updateTierIds(moved.form, "warning", F.removeId(auto)), + "critical", + F.removeId(auto), + ) + : moved.form + return editForm( + { ...model, kindNotice: moved.notice, autoDestinationId: kind === "anomaly" ? null : auto }, + context, + () => form, + ) }, GotRadioMessage: ({ group, message: inner }) => radioStep(model, context, group, inner), ClickedOpenInBuilder: () => { diff --git a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts index c5513139e9..9d6e02aedf 100644 --- a/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts +++ b/apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts @@ -135,6 +135,8 @@ describe("alerts hub: anomalies", () => { given(loaded({ signal: "anomaly", serviceName: "checkout", anomalyStatus: "resolved" })), expectView(role("link", { name: "Anomaly on checkout (staging)" })).toExist(), expectView(role("link", { name: "Anomaly on checkout (production)" })).not.toExist(), + // The hub holds 30 days of incidents, and the heading says so. + expectView(text("Resolved in the last 30 days")).toExist(), expectView(role("link", { name: "Anomaly on search (production)" })).not.toExist(), click(role("button", { name: "Remove checkout" })), expectOutMessage( diff --git a/apps/web/src/page/alerts/hub/view-anomalies.ts b/apps/web/src/page/alerts/hub/view-anomalies.ts index 1910b64bb7..a4e5ab083d 100644 --- a/apps/web/src/page/alerts/hub/view-anomalies.ts +++ b/apps/web/src/page/alerts/hub/view-anomalies.ts @@ -92,7 +92,8 @@ const anomaliesEmpty = (h: H, status: IncidentStatusFilter, serviceName: string emptyHeader(h, [ emptyMedia(h, { variant: "icon" }, [icon(h, "PulseIcon", { size: 16 })]), emptyTitle(h, [ - status === "resolved" ? "No resolved anomalies" : "No anomalies", + // The hub reads the last 30 days of incidents; older resolved anomalies are not here. + status === "resolved" ? "No anomalies resolved in the last 30 days" : "No anomalies", ...(serviceName === undefined ? [] : [` on ${serviceName}`]), ]), emptyDescription(h, [ @@ -146,7 +147,7 @@ const resolvedList = (h: H, incidents: ReadonlyArray, now return h.section( [h.Class("space-y-3")], [ - eyebrow(h, { as: "h3" }, ["Resolved"]), + eyebrow(h, { as: "h3" }, ["Resolved in the last 30 days"]), borderedBox(h, { overflow: "x-auto" }, [ h.div( [h.Class("min-w-[520px]")], diff --git a/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts index 9c8ab2804a..d7e8ea0e77 100644 --- a/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts @@ -181,4 +181,28 @@ describe("alert incident: anomaly rules", () => { expectView(text(/^Silenced until /u)).toExist(), ) }) + + test("the anomaly evidence is read again when its alert incident moves", () => { + const model = synced(SPIKE) + const quiet = pageUpdate( + model, + Message.UpdatedIncidents({ orgId: ORG, rows: [anomalyIncidentRow(SPIKE)] }), + ) + expect(names(quiet.commands)).not.toContain("FetchAlertIncidentAnomaly") + const escalated = pageUpdate( + model, + Message.UpdatedIncidents({ + orgId: ORG, + rows: [ + anomalyIncidentRow(SPIKE, { + last_triggered_at: new Date(NOW + 60_000).toISOString(), + severity: "warning", + }), + ], + }), + ) + expect(names(escalated.commands)).toEqual( + expect.arrayContaining(["FetchAlertIncidentAnomaly", "FetchAlertIncidentAnomalyTimeseries"]), + ) + }) }) diff --git a/apps/web/src/page/alerts/incident/update.ts b/apps/web/src/page/alerts/incident/update.ts index b0acac444f..616d5b610f 100644 --- a/apps/web/src/page/alerts/incident/update.ts +++ b/apps/web/src/page/alerts/incident/update.ts @@ -208,6 +208,13 @@ const followData = (model: Model, context: PageContext): Return => acknowledgedAt: view.incident.acknowledgedAt === null ? null : isoMs(view.incident.acknowledgedAt), }) + // The anomaly's evidence is re-read whenever its alert incident moves (status, severity, a + // new breach): the synced row is the change signal, and the previous answer stays on screen. + const evidenceVersion = { + status: view.incident.status, + severity: view.incident.severity, + lastTriggeredAt: isoMs(view.incident.lastTriggeredAt), + } const steps: ReadonlyArray<{ patch: Partial; commands: Commands }> = [ ...Option.toArray( Option.map( @@ -289,7 +296,10 @@ const followData = (model: Model, context: PageContext): Return => ), ...Option.toArray( Option.flatMap(Option.fromNullishOr(view.incident.anomalyIncidentId), (id) => { - const key = requestKey(context.orgId, "anomalies.getIncident", { id }) + const key = requestKey(context.orgId, "anomalies.getIncident", { + id, + ...evidenceVersion, + }) return Option.map( sameKey(model.anomaly, key) ? Option.none() @@ -303,7 +313,10 @@ const followData = (model: Model, context: PageContext): Return => ), ...Option.toArray( Option.flatMap(Option.fromNullishOr(view.incident.anomalyIncidentId), (id) => { - const key = requestKey(context.orgId, "anomalies.getIncidentTimeseries", { id }) + const key = requestKey(context.orgId, "anomalies.getIncidentTimeseries", { + id, + ...evidenceVersion, + }) return Option.map( sameKey(model.anomalyTimeseries, key) ? Option.none() diff --git a/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts b/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts index 1c734ab820..6602e74ab8 100644 --- a/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/rule/rule-anomaly.scene.test.ts @@ -1,7 +1,17 @@ import { OrgId } from "@maple/domain" import { AlertRuleId } from "@maple/domain/http" import { Schema, Array as Arr } from "effect" -import { expect as expectView, given, Mount, scene, text, withViewInputs } from "foldkit/scene" +import { + all, + expect as expectView, + expectAll, + first, + given, + Mount, + scene, + text, + withViewInputs, +} from "foldkit/scene" import { describe, expect, test } from "vitest" import { ResolveThemeColors, ThemeMessage } from "@maple/views/charts/theme-colors" import type { PageContext } from "../../contract" @@ -118,4 +128,24 @@ describe("alert rule page: anomaly rules", () => { expectView(text("Configure destinations")).not.toExist(), ) }) + + test("lists the window's anomalies newest opening first", () => { + const older = anomalyIncidentRow( + { n: 2, ruleN: 8, severity: "warning", service: "demo-api", environment: "staging" }, + { first_triggered_at: new Date(NOW - 5 * 3_600_000).toISOString() }, + ) + const newer = anomalyIncidentRow( + { n: 3, ruleN: 8, severity: "critical", service: "demo-api", environment: "production" }, + { first_triggered_at: new Date(NOW - 10 * 60_000).toISOString() }, + ) + scene( + { update: pageUpdate, view }, + given(loaded(SCOPED, [older, newer])), + themeResolved(), + expectAll(all.role("link", { name: /^Anomaly on demo-api/ })).toHaveCount(2), + expectView(first(all.role("link", { name: /^Anomaly on demo-api/ }))).toHaveText( + "Anomaly on demo-api (production)", + ), + ) + }) }) diff --git a/apps/web/src/page/alerts/rule/view-anomaly.ts b/apps/web/src/page/alerts/rule/view-anomaly.ts index 50982c48fd..38a87aa5e7 100644 --- a/apps/web/src/page/alerts/rule/view-anomaly.ts +++ b/apps/web/src/page/alerts/rule/view-anomaly.ts @@ -3,7 +3,7 @@ import { ANOMALY_SIGNAL_LABELS } from "@maple/web-data/alerts/anomaly-rule" import { formatAlertDuration } from "@maple/web-data/alerts/form-utils" import { incidentTitle } from "@maple/web-data/alerts/overview-derive" import { formatDateInTimezone, formatTimeInTimezone } from "@maple/web-data/lib/timezone-format" -import { Array as Arr } from "effect" +import { Array as Arr, Order } from "effect" import type { Html, HtmlBuilder } from "foldkit/html" import { card, cardPanel } from "@maple/views/ui/card" import { badge } from "@maple/views/ui/display" @@ -23,6 +23,11 @@ import { isoMs } from "../shared/time" const INCIDENT_LIMIT = 20 +/** The list shows when each anomaly opened, so the newest opening leads. */ +const newestFirst = Order.flip( + Order.mapInput(Order.Number, (incident: AlertIncidentDocument) => isoMs(incident.firstTriggeredAt)), +) + const overlaps = (incident: AlertIncidentDocument, range: Readonly<{ min: number; max: number }>): boolean => isoMs(incident.firstTriggeredAt) <= range.max && (incident.resolvedAt === null || isoMs(incident.resolvedAt) >= range.min) @@ -39,7 +44,10 @@ export const anomalyIncidentsSection = ( }>, ): Html => { const inWindow = Arr.take( - Arr.filter(options.incidents, (incident) => overlaps(incident, options.range)), + Arr.sort( + Arr.filter(options.incidents, (incident) => overlaps(incident, options.range)), + newestFirst, + ), INCIDENT_LIMIT, ) const at = (iso: string) => diff --git a/apps/web/src/page/anomalies/detail/anomaly.scene.test.ts b/apps/web/src/page/anomalies/detail/anomaly.scene.test.ts index 39115ad874..1bffe95ad9 100644 --- a/apps/web/src/page/anomalies/detail/anomaly.scene.test.ts +++ b/apps/web/src/page/anomalies/detail/anomaly.scene.test.ts @@ -4,8 +4,10 @@ import { encodePublicId, PublicIdPrefixes, V2AnomalyIncident, V2ErrorIssue } fro import { errorIssueFromV2 } from "../../../lib/services/error-issues" import { Option, Result, Schema, Array as Arr } from "effect" import { + all, Command, expect as expectView, + expectAll, given, keydown, Mount, @@ -25,7 +27,7 @@ import { incidentOf } from "./derive" import { Message } from "./message" import type { Model } from "./model" import { init, update } from "./update" -import { view as pageView } from "./view" +import { breadcrumbsOf, view as pageView } from "./view" const context: PageContext = { orgId: Schema.decodeSync(OrgId)("org_parity"), @@ -105,6 +107,11 @@ describe("anomaly detail", () => { given(fail(notFound)), themed(), expectView(text("Anomaly not found")).toExist(), + // It may be resolved: the way back lists every anomaly, not only open ones. + expectView(role("link", { name: /Back to anomalies/ })).toHaveAttr( + "href", + "/alerts?signal=anomaly&anomalyStatus=all", + ), ) scene( { update: pageUpdate, view }, @@ -276,3 +283,66 @@ describe("anomaly link dialog by keyboard", () => { ) }) }) + +describe("anomaly detail: the way back and grouped errors", () => { + const withIncident = (wire: typeof incident) => { + const entered = init(route, context) + const clocked = pageUpdate(entered.model, Message.ReadClock({ nowMs: NOW })).model + return pageUpdate( + clocked, + Message.CompletedFetchIncident({ key: clocked.incident.key ?? "", result: Result.succeed(wire) }), + ).model + } + const anomaliesCrumb = (model: Model) => + Arr.findFirst(breadcrumbsOf(model), (crumb) => crumb.label === "Anomalies") + + test("the Anomalies crumb opens on the incident's own status", () => { + expect(Option.map(anomaliesCrumb(withIncident(incident)), (crumb) => crumb.href)).toEqual( + Option.some("/alerts?signal=anomaly"), + ) + const resolved = Schema.decodeSync(V2AnomalyIncident)({ + ...Schema.encodeSync(V2AnomalyIncident)(incident), + status: "resolved", + resolved_at: new Date(NOW - 60_000).toISOString(), + }) + expect(Option.map(anomaliesCrumb(withIncident(resolved)), (crumb) => crumb.href)).toEqual( + Option.some("/alerts?signal=anomaly&anomalyStatus=all"), + ) + }) + + test("each grouped error fingerprint renders once", () => { + const spike = Schema.decodeSync(V2AnomalyIncident)({ + ...Schema.encodeSync(V2AnomalyIncident)(incident), + signal_type: "error_spike", + fingerprint_hash: "fp_primary", + fingerprints: [ + { + fingerprint_hash: "fp_primary", + error_issue_id: null, + opened_value: 40, + last_value: 52, + severity: "critical", + attached_at: new Date(NOW - 3_600_000).toISOString(), + resolved_at: null, + }, + { + fingerprint_hash: "fp_second", + error_issue_id: null, + opened_value: 12, + last_value: 19, + severity: "warning", + attached_at: new Date(NOW - 1_800_000).toISOString(), + resolved_at: null, + }, + ], + }) + scene( + { update: pageUpdate, view }, + given(withIncident(spike)), + themed(), + expectView(text("Grouped errors · 2")).toExist(), + // The second fingerprint's last value appears in its one row only. + expectAll(all.text("19 occurrences / 30m")).toHaveCount(1), + ) + }) +}) diff --git a/apps/web/src/page/anomalies/detail/view.ts b/apps/web/src/page/anomalies/detail/view.ts index b6a9620ce8..0fa63f2ead 100644 --- a/apps/web/src/page/anomalies/detail/view.ts +++ b/apps/web/src/page/anomalies/detail/view.ts @@ -37,8 +37,12 @@ import { Array as Arr } from "effect" type H = HtmlBuilder -/** The anomalies list is the Alerts Inbox's Anomalies view. */ -const ANOMALIES_HREF = alertsHref({ signal: "anomaly" }) +/** + * The anomalies list is the Alerts Inbox's Anomalies view, which opens on open anomalies: a resolved + * one, or one that could not be found, links to all of them so the list can include it. + */ +const anomaliesHref = (status: AnomalyIncidentDocument["status"] | null): string => + alertsHref(status === "open" ? { signal: "anomaly" } : { signal: "anomaly", anomalyStatus: "all" }) const headerActions = (h: H, model: Model, incident: AnomalyIncidentDocument, timeZone: string): Html => { const isOpen = incident.status === "open" @@ -177,7 +181,7 @@ export const view = Submodel.defineView((model, resourceNotFound(h, { title: "Anomaly not found", description: "It may have been pruned, or the link is stale.", - back: { href: ANOMALIES_HREF, label: "Back to anomalies" }, + back: { href: anomaliesHref(null), label: "Back to anomalies" }, }), ]) : state._tag === "Failed" @@ -205,7 +209,10 @@ export const breadcrumbsOf = (model: Model): ReadonlyArray => { const state = pageStateOf(model) return [ { label: "Alerts", href: alertsHref({}) }, - { label: "Anomalies", href: ANOMALIES_HREF }, + { + label: "Anomalies", + href: anomaliesHref(state._tag === "Loaded" ? state.incident.status : null), + }, { label: state._tag === "Loaded" diff --git a/packages/views/src/ui/combobox-view.ts b/packages/views/src/ui/combobox-view.ts index cbc420a251..28819cdbea 100644 --- a/packages/views/src/ui/combobox-view.ts +++ b/packages/views/src/ui/combobox-view.ts @@ -409,12 +409,24 @@ export type ChipsViewInputs = Readonly<{ isUnfiltered?: boolean }> +/** "Remove checkout", or "Remove Ada Lovelace (user_ada)" when another chip has the same label. */ +const removeLabel = ( + value: string, + chipLabel: ChipsViewInputs["chipLabel"], + isLabelShared: boolean, +): string => { + const label = chipLabel?.(value) ?? value + return isLabelShared && label !== value ? `Remove ${label} (${value})` : `Remove ${label}` +} + /** Base UI hides the chips from assistive tech while the list is open. */ const chip = ( h: H, value: string, isOpen: boolean, parts: Pick = {}, + /** Another chip reads the same: the remove button names the value too, so the two differ. */ + isLabelShared = false, ): Html => h.div( [ @@ -434,7 +446,7 @@ const chip = ( [ h.AriaDisabled(false), // Named for its chip, so a screen reader can tell several remove buttons apart. - h.AriaLabel(`Remove ${parts.chipLabel?.(value) ?? value}`), + h.AriaLabel(removeLabel(value, parts.chipLabel, isLabelShared)), h.Class(C.comboboxChipRemoveStyles), h.DataAttribute("slot", "combobox-chip-remove"), h.Tabindex(-1), @@ -463,7 +475,14 @@ export const chipsView = Submodel.defineView((m ...(model.selected.length === 0 ? [] : [h.Role("toolbar")]), ], [ - ...Arr.map(model.selected, (value) => chip(h, value, isOpen, inputs)), + ...Arr.map(model.selected, (value) => { + const label = inputs.chipLabel?.(value) ?? value + const isLabelShared = Arr.some( + model.selected, + (other) => other !== value && (inputs.chipLabel?.(other) ?? other) === label, + ) + return chip(h, value, isOpen, inputs, isLabelShared) + }), ...(isOpen ? [dismissButton(h)] : []), h.input([ h.Class(cn(C.comboboxChipsInputStyles, C.comboboxChipsInputDefaultStyles)), diff --git a/packages/views/src/ui/combobox.scene.test.ts b/packages/views/src/ui/combobox.scene.test.ts index afa86e5b74..78e926812a 100644 --- a/packages/views/src/ui/combobox.scene.test.ts +++ b/packages/views/src/ui/combobox.scene.test.ts @@ -67,6 +67,31 @@ const exited = (id: string, generation: number) => [ ] describe("combobox scene (chips)", () => { + test("chips that read the same name their value on the remove button; distinct ones do not", () => { + const NAMES = new Map([ + ["ada@acme.test", "Ada Lovelace"], + ["ada@other.test", "Ada Lovelace"], + ["grace@acme.test", "Grace Hopper"], + ]) + scene( + { + update, + view: withViewInputs(chipsView, { + items: [...NAMES.keys()], + ariaLabel: "Members", + placeholder: "Add a member", + emptyText: "No members found.", + chipLabel: (value) => NAMES.get(value) ?? value, + toView: (parts) => ih.main([], parts), + })(), + }, + given(init("members", "multiple", { selected: [...NAMES.keys()] })), + expect(role("button", { name: "Remove Ada Lovelace (ada@acme.test)" })).toExist(), + expect(role("button", { name: "Remove Ada Lovelace (ada@other.test)" })).toExist(), + expect(role("button", { name: "Remove Grace Hopper" })).toExist(), + ) + }) + test("the selection shows as chips and as selected options", () => { scene( chips, From 1fb0219f080a270debd8db7e222152ebba12f627 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:42:14 +0100 Subject: [PATCH 26/28] fix(web): anomaly investigations get the detector's readings, not severity codes --- .../incident/incident-anomaly.scene.test.ts | 26 ++++++++++++++ apps/web/src/page/alerts/incident/update.ts | 36 ++++++++++++++----- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts index d7e8ea0e77..40fc95111f 100644 --- a/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts +++ b/apps/web/src/page/alerts/incident/incident-anomaly.scene.test.ts @@ -105,6 +105,32 @@ describe("alert incident: anomaly rules", () => { ) }) + test("an investigation of an anomaly sends the detector's readings, never severity codes", () => { + const model = synced(SPIKE) + const asked = pageUpdate( + model, + Message.CompletedFetchInvestigation({ + key: model.investigation.key ?? "", + result: Result.succeed(null), + }), + ).model + const factsOf = (from: typeof model) => + JSON.stringify(pageUpdate(from, Message.ClickedInvestigate()).commands?.[0]?.args ?? {}) + // Before the evidence loads there are no facts to send rather than "Observed: Critical". + expect(factsOf(asked)).not.toMatch(/Critical|Warning|Threshold/) + const loaded = pageUpdate( + asked, + Message.CompletedFetchAnomaly({ + key: asked.anomaly.key ?? "", + result: Result.succeed(anomalyWire(3, "error_spike", "critical", "checkout")), + }), + ).model + const facts = factsOf(loaded) + expect(facts).toContain("Baseline") + expect(facts).toContain("Error frequency increase") + expect(facts).not.toMatch(/"Threshold"|"Critical"/) + }) + test("a warning anomaly without grouped errors has no fingerprint list or issue link", () => { const scenario = { ...SPIKE, n: 4, severity: "warning" } as const const model = synced(scenario) diff --git a/apps/web/src/page/alerts/incident/update.ts b/apps/web/src/page/alerts/incident/update.ts index 616d5b610f..cca64e737b 100644 --- a/apps/web/src/page/alerts/incident/update.ts +++ b/apps/web/src/page/alerts/incident/update.ts @@ -1,5 +1,10 @@ +import type { AlertIncidentDocument } from "@maple/domain/http" import type { V2InvestigationCreateParams } from "@maple/domain/http/v2" import { formatSignalValue, signalLabels } from "@maple/web-data/alerts/form-utils" +import { + formatSignalValue as formatAnomalyValue, + SIGNAL_LABEL as ANOMALY_SIGNAL_LABEL, +} from "@maple/web-data/anomalies/format" import { incidentGroup } from "@maple/web-data/alerts/incident-timeline" import { AlertIncidentRowSchema, @@ -7,7 +12,7 @@ import { AlertRuleStateRowSchema, } from "@maple/web-data/collections/alerts" import { Option, Result, Schema, Array as Arr } from "effect" -import { Command } from "foldkit" +import { AsyncData, Command } from "foldkit" import * as Chart from "@maple/views/charts/chart" import { LoadChartRuntime } from "@maple/views/charts/chart-runtime" import * as Keyed from "@maple/views/data/keyed" @@ -350,6 +355,26 @@ const followData = (model: Model, context: PageContext): Return => }, }) +const thresholdFacts = (incident: AlertIncidentDocument) => [ + { label: "Signal", value: signalLabels[incident.signalType] }, + { label: "Observed", value: formatSignalValue(incident.signalType, incident.lastObservedValue) }, + { label: "Threshold", value: formatSignalValue(incident.signalType, incident.threshold) }, +] + +/** An anomaly's facts are the detector's readings; its alert values are only severity codes, so nothing is sent until those load. */ +const anomalyFacts = (model: Model) => + Option.match(AsyncData.getData(model.anomaly.data), { + onNone: () => [], + onSome: (anomaly) => [ + { label: "Signal", value: ANOMALY_SIGNAL_LABEL[anomaly.signal_type] }, + { + label: "Observed", + value: formatAnomalyValue(anomaly.signal_type, anomaly.last_observed_value), + }, + { label: "Baseline", value: formatAnomalyValue(anomaly.signal_type, anomaly.baseline_median) }, + ], + }) + const createPayload = (model: Model): Option.Option => Option.map(incidentViewOf(model), ({ incident }) => ({ subject: @@ -366,14 +391,7 @@ const createPayload = (model: Model): Option.Option scope: incidentGroup(incident) ?? "all", status: incident.status, severity: incident.severity === "critical" ? ("critical" as const) : ("medium" as const), - facts: [ - { label: "Signal", value: signalLabels[incident.signalType] }, - { - label: "Observed", - value: formatSignalValue(incident.signalType, incident.lastObservedValue), - }, - { label: "Threshold", value: formatSignalValue(incident.signalType, incident.threshold) }, - ], + facts: incident.signalType === "anomaly" ? anomalyFacts(model) : thresholdFacts(incident), references: incident.errorIssueId ? [{ label: "Issue", url: `/errors/issues/${incident.errorIssueId}` }] : [], From 2de40ce7770700db27cff339e7daecc9c7e86ac1 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:49:37 +0100 Subject: [PATCH 27/28] fix(web): resolved anomalies read "just now"; test both tiers' destinations survive leaving anomaly mode --- apps/web/src/page/alerts/hub/view-anomalies.ts | 6 ++---- packages/web-data/src/alerts/anomaly-rule.test.ts | 12 +++++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/web/src/page/alerts/hub/view-anomalies.ts b/apps/web/src/page/alerts/hub/view-anomalies.ts index a4e5ab083d..ce4fc9c39d 100644 --- a/apps/web/src/page/alerts/hub/view-anomalies.ts +++ b/apps/web/src/page/alerts/hub/view-anomalies.ts @@ -1,5 +1,5 @@ import type { AlertIncidentDocument } from "@maple/domain/http" -import { formatRelativeShortFrom } from "@maple/ui/lib/time-format" +import { formatRelativeFrom } from "@maple/ui/lib/time-format" import { cn } from "@maple/ui/lib/utils" import { formatAlertDuration } from "@maple/web-data/alerts/form-utils" import type { AlertsOverviewReady } from "@maple/web-data/alerts/overview" @@ -133,9 +133,7 @@ const resolvedRow = (h: H, incident: AlertIncidentDocument, now: number): Html = ], ), typography(h, { size: "xs", tone: "muted", mono: true, numeric: true }, [ - incident.resolvedAt === null - ? "" - : `${formatRelativeShortFrom(isoMs(incident.resolvedAt), now)} ago`, + incident.resolvedAt === null ? "" : formatRelativeFrom(isoMs(incident.resolvedAt), now), ]), typography(h, { size: "xs", mono: true, numeric: true }, [ formatAlertDuration(incident.firstTriggeredAt, incident.resolvedAt), diff --git a/packages/web-data/src/alerts/anomaly-rule.test.ts b/packages/web-data/src/alerts/anomaly-rule.test.ts index 2179d64d9e..e223f9eb46 100644 --- a/packages/web-data/src/alerts/anomaly-rule.test.ts +++ b/packages/web-data/src/alerts/anomaly-rule.test.ts @@ -1,3 +1,5 @@ +import { AlertDestinationId } from "@maple/domain/http" + import { Array as Arr } from "effect" import { describe, expect, it } from "vitest" import { anomalyScopeSummary, isDefaultAnomalyRule } from "./anomaly-rule" @@ -36,10 +38,18 @@ describe("anomaly rules", () => { }) it("leaving the anomaly kind starts a query from error rate with both tiers' destinations", () => { - const anomaly = anomalyForm({ serviceNames: ["checkout"] }) + const warning = AlertDestinationId.make("00000000-0000-4000-8000-0000000000a1") + const critical = AlertDestinationId.make("00000000-0000-4000-8000-0000000000b2") + const scoped = anomalyForm({ serviceNames: ["checkout"], destinationIds: [warning] }) + const anomaly = { + ...scoped, + escalation: + scoped.escalation === null ? null : { ...scoped.escalation, destinationIds: [critical] }, + } const left = switchSignalKind(anomaly, "builtin").form expect(left.signalType).toBe("error_rate") expect(left.escalation).toBeNull() + expect(left.destinationIds).toEqual([warning, critical]) expect(left.serviceNames).toEqual(["checkout"]) expect(left.threshold).toBe(defaultRuleForm().threshold) }) From 5301042d237f10f5fca7abd0c28c2fcbbba23c93 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sat, 7 Nov 2026 00:51:03 +0100 Subject: [PATCH 28/28] fix(web): an anomaly investigation never sends readings from evidence still being refreshed --- apps/web/src/page/alerts/incident/update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/page/alerts/incident/update.ts b/apps/web/src/page/alerts/incident/update.ts index cca64e737b..b58d214ea2 100644 --- a/apps/web/src/page/alerts/incident/update.ts +++ b/apps/web/src/page/alerts/incident/update.ts @@ -363,7 +363,8 @@ const thresholdFacts = (incident: AlertIncidentDocument) => [ /** An anomaly's facts are the detector's readings; its alert values are only severity codes, so nothing is sent until those load. */ const anomalyFacts = (model: Model) => - Option.match(AsyncData.getData(model.anomaly.data), { + // Readings kept on screen while newer evidence loads are not sent as facts. + Option.match(model.anomaly.retained === true ? Option.none() : AsyncData.getData(model.anomaly.data), { onNone: () => [], onSome: (anomaly) => [ { label: "Signal", value: ANOMALY_SIGNAL_LABEL[anomaly.signal_type] },