From a9b7c80395c7e2868e87f229b81245c1e0e1162c Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 14:18:02 +0200 Subject: [PATCH 01/10] feat(aws): stream CloudWatch metrics to Maple through Firehose A connection can ask for a metric stream: regions and CloudWatch namespaces. The setup deploys a maple-metrics stack per region (metric stream in the OpenTelemetry 1.0 format, a Firehose posting to ingest, its roles, and a bucket for failed batches) and removes it from every other region. Ingest gets POST /v1/firehose/aws/{connection_id}, authenticated by a per-connection stream secret (stored encrypted, looked up by its hash). It turns each CloudWatch summary into an average, min and max gauge and delta sum and count rows, since the native pipeline drops summaries, and records the stream's health on the connection. --- .../routes/v2/integrations-aws.http.test.ts | 13 + .../src/routes/v2/integrations-aws.http.ts | 8 + apps/ingest/src/aws_metric_stream.rs | 1050 ++ apps/ingest/src/lib.rs | 1 + apps/ingest/src/main.rs | 1058 +- .../src/content/docs/integrations/aws.md | 19 +- .../src/page/integrations/aws.scene.test.ts | 5 + .../page/sources/detail/detail.scene.test.ts | 31 +- .../integrations/AwsConnectionService.test.ts | 82 +- .../integrations/AwsConnectionService.ts | 130 +- .../src/services/integrations/aws/setup.ts | 226 +- .../migration.sql | 23 + .../snapshot.json | 14610 ++++++++++++++++ packages/db/src/tables/aws-connections.ts | 20 +- packages/domain/src/aws.ts | 25 +- .../domain/src/http/v2/integrations-aws.ts | 44 +- packages/primitives/src/index.ts | 23 + 17 files changed, 17331 insertions(+), 37 deletions(-) create mode 100644 apps/ingest/src/aws_metric_stream.rs create mode 100644 packages/db/drizzle/20261022120643_aws_metric_stream/migration.sql create mode 100644 packages/db/drizzle/20261022120643_aws_metric_stream/snapshot.json diff --git a/apps/api/src/routes/v2/integrations-aws.http.test.ts b/apps/api/src/routes/v2/integrations-aws.http.test.ts index 277b9fc40d..d79bb34e0e 100644 --- a/apps/api/src/routes/v2/integrations-aws.http.test.ts +++ b/apps/api/src/routes/v2/integrations-aws.http.test.ts @@ -159,6 +159,9 @@ describe("v2 aws integration over HTTP", () => { verified_at: null, last_checked_at: null, last_check_error: null, + metric_stream: null, + metrics_last_received_at: null, + metrics_last_error: null, created_at: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T.*Z$/), }) const id: string = created.body.id @@ -217,6 +220,16 @@ describe("v2 aws integration over HTTP", () => { modules: ["cost"], }) expect(planned.status).toBe(400) + const elsewhere = await harness.request("POST", CONNECTIONS, admin.secret, { + account_id: "123456789012", + metric_stream: { regions: ["mars-north-1a"], namespaces: ["AWS/ECS"] }, + }) + expect(elsewhere.status).toBe(400) + const nothing = await harness.request("POST", CONNECTIONS, admin.secret, { + account_id: "123456789012", + metric_stream: { regions: ["us-east-1"], namespaces: [] }, + }) + expect(nothing.status).toBe(400) await harness.dispose() }) diff --git a/apps/api/src/routes/v2/integrations-aws.http.ts b/apps/api/src/routes/v2/integrations-aws.http.ts index 2428d54236..b518080f73 100644 --- a/apps/api/src/routes/v2/integrations-aws.http.ts +++ b/apps/api/src/routes/v2/integrations-aws.http.ts @@ -32,12 +32,16 @@ const toV2Connection = (connection: AwsConnection): V2AwsConnection => ({ verified_at: isoTimestampOrNull(connection.verifiedAt), last_checked_at: isoTimestampOrNull(connection.lastCheckedAt), last_check_error: connection.lastCheckError, + metric_stream: connection.metricStream, + metrics_last_received_at: isoTimestampOrNull(connection.metricsLastReceivedAt), + metrics_last_error: connection.metricsLastError, created_at: isoTimestamp(connection.createdAt), }) const auditMetadata = (connection: AwsConnection) => ({ account_id: connection.accountId, modules: connection.modules, + metric_stream: connection.metricStream, }) export const HttpV2AwsIntegrationsLive = HttpApiBuilder.group(MapleApiV2, "awsIntegration", (handlers) => @@ -64,6 +68,7 @@ export const HttpV2AwsIntegrationsLive = HttpApiBuilder.group(MapleApiV2, "awsIn const connection = yield* aws.create(tenant.orgId, tenant.userId, { accountId: payload.account_id, modules: payload.modules ?? [], + metricStream: payload.metric_stream ?? null, }) yield* recordHttpAudit("aws_connection.created", { resourceId: connection.id, @@ -78,6 +83,7 @@ export const HttpV2AwsIntegrationsLive = HttpApiBuilder.group(MapleApiV2, "awsIn yield* requireAdmin(tenant.roles, adminOnly) const connection = yield* aws.update(tenant.orgId, params.id, { modules: payload.modules, + metricStream: payload.metric_stream, }) yield* recordHttpAudit("aws_connection.updated", { resourceId: connection.id, @@ -99,6 +105,8 @@ export const HttpV2AwsIntegrationsLive = HttpApiBuilder.group(MapleApiV2, "awsIn cleanup_script: setup.cleanupScript, cloudformation_template: setup.cloudformationTemplate, terraform: setup.terraform, + metrics_stack_name: setup.metricsStackName, + metrics_cloudformation_template: setup.metricsCloudformationTemplate, } }), ) diff --git a/apps/ingest/src/aws_metric_stream.rs b/apps/ingest/src/aws_metric_stream.rs new file mode 100644 index 0000000000..38ccea3c6f --- /dev/null +++ b/apps/ingest/src/aws_metric_stream.rs @@ -0,0 +1,1050 @@ +//! CloudWatch Metric Streams (OpenTelemetry 1.0 output, delivered by Amazon +//! Data Firehose's HTTP endpoint destination) to an OTLP metrics request Maple +//! stores. +//! +//! CloudWatch sends every metric as a Summary, which the native encoder drops, +//! so each summary point is expanded into gauges and delta sums. + +use std::collections::HashMap; + +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceRequest; +use opentelemetry_proto::tonic::common::v1::{any_value, AnyValue, InstrumentationScope, KeyValue}; +use opentelemetry_proto::tonic::metrics::v1::{ + metric, number_data_point, AggregationTemporality, Gauge, Metric, NumberDataPoint, + ResourceMetrics, ScopeMetrics, Sum, SummaryDataPoint, +}; +use opentelemetry_proto::tonic::resource::v1::Resource; +use prost::Message; +use serde::Deserialize; + +pub const INGEST_SOURCE: &str = "aws-metric-stream"; + +const SCOPE_NAME: &str = "aws.cloudwatch.metric_stream"; + +// What a connection's `stream_last_error` holds. The customer reads it on the +// connection: one sentence of cause, one of what to do. + +/// A delivery that is not a Firehose request of OpenTelemetry 1.0 records. +pub const UNREADABLE_DELIVERY: &str = "Maple could not read a metric stream delivery. Check \ + that the stream's output format is OpenTelemetry 1.0. Metrics sent meanwhile are lost."; + +/// A delivery over the decoded size Maple accepts. +pub const DELIVERY_TOO_LARGE: &str = "A metric stream delivery was larger than Maple accepts. \ + Lower the Firehose stream's buffer size. Metrics sent meanwhile are lost."; + +/// A delivery Maple accepted but could not hand to the pipeline. +pub const DELIVERY_NOT_STORED: &str = "Maple could not store a delivery just now. Firehose \ + retries it. Nothing to do."; + +/// A delivery refused because the organization is over its plan limit. +pub const OVER_PLAN_LIMIT: &str = "This Maple organization is over its plan limit, so Maple \ + refuses new metrics. Firehose retries for a while. See Settings, Billing."; + +/// The connection a delivery arrived on, stamped on every resource. +#[derive(Clone, Copy, Debug)] +pub struct Connection<'a> { + pub id: &'a str, + pub org_id: &'a str, + /// The integration source's public `src_` id. + pub source_id: Option<&'a str>, +} + +/// The body Firehose's HTTP endpoint destination posts. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Delivery { + pub request_id: Option, + records: Vec, +} + +#[derive(Debug, Deserialize)] +struct DeliveryRecord { + /// Base64 of one or more length-delimited `ExportMetricsServiceRequest`s. + data: String, +} + +/// Why a delivery's records could not be read. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RecordError { + Unreadable, + /// The records decode to more than the allowed bytes. + TooLarge, +} + +/// `None` when the body is not a Firehose request. +pub fn parse_delivery(payload: &[u8]) -> Option { + serde_json::from_slice(payload).ok() +} + +impl Delivery { + /// Every request in the records, in order. Fails once the base64-decoded + /// records add up to more than `max_decoded` bytes. + pub fn metric_requests( + &self, + max_decoded: usize, + ) -> Result, RecordError> { + let mut decoded_total = 0_usize; + let mut requests = Vec::new(); + for record in &self.records { + let bytes = STANDARD + .decode(record.data.as_bytes()) + .map_err(|_| RecordError::Unreadable)?; + decoded_total = decoded_total.saturating_add(bytes.len()); + if decoded_total > max_decoded { + return Err(RecordError::TooLarge); + } + let mut buffer = bytes.as_slice(); + while !buffer.is_empty() { + let request = ExportMetricsServiceRequest::decode_length_delimited(&mut buffer) + .map_err(|_| RecordError::Unreadable)?; + requests.push(request); + } + } + Ok(requests) + } +} + +/// `aws.` + namespace + `.` + metric name, each snake_cased: +/// (`AWS/ECS`, `CPUUtilization`) is `aws.ecs.cpu_utilization`. +pub fn metric_base_name(namespace: &str, metric_name: &str) -> String { + format!( + "aws.{}.{}", + normalize_namespace(namespace), + snake_case(metric_name) + ) +} + +/// `AWS/ECS` is `ecs`, `ECS/ContainerInsights` is `ecs.container_insights`. +pub fn normalize_namespace(namespace: &str) -> String { + let namespace = namespace.strip_prefix("AWS/").unwrap_or(namespace); + namespace + .split('/') + .map(snake_case) + .filter(|part| !part.is_empty()) + .collect::>() + .join(".") +} + +/// `CPUUtilization` is `cpu_utilization`. A digit does not start a new word, so +/// `HTTPCode_Target_5XX_Count` is `http_code_target_5xx_count`. +pub fn snake_case(name: &str) -> String { + let chars: Vec = name.chars().collect(); + let mut spaced = String::with_capacity(name.len() + 4); + for (index, ¤t) in chars.iter().enumerate() { + if current.is_ascii_uppercase() && index > 0 { + let previous = chars[index - 1]; + let next_is_lower = chars.get(index + 1).is_some_and(char::is_ascii_lowercase); + if previous.is_ascii_lowercase() || (previous.is_ascii_uppercase() && next_is_lower) { + spaced.push('_'); + } + } + spaced.push(current.to_ascii_lowercase()); + } + + let mut snake = String::with_capacity(spaced.len()); + for current in spaced.chars() { + let current = if current.is_ascii_lowercase() || current.is_ascii_digit() { + current + } else { + '_' + }; + if current == '_' && (snake.is_empty() || snake.ends_with('_')) { + continue; + } + snake.push(current); + } + while snake.ends_with('_') { + snake.pop(); + } + snake +} + +/// Dimensions naming the resource a metric is about, most specific first. The +/// first one present becomes `service.name`. +const SERVICE_DIMENSIONS: &[&str] = &[ + "ServiceName", + "FunctionName", + "DBInstanceIdentifier", + "DBClusterIdentifier", + "LoadBalancer", + "QueueName", + "InstanceId", +]; + +/// One CloudWatch metric's identity, read off a summary point. +#[derive(Debug)] +struct CloudWatchMetric { + namespace: String, + metric_name: String, + dimensions: Vec<(String, String)>, +} + +impl CloudWatchMetric { + /// From the point's `Namespace`, `MetricName` and `Dimensions` attributes, + /// falling back to the `amazonaws.com//` metric name. + fn of(metric: &Metric, point: &SummaryDataPoint) -> Option { + let mut namespace = None; + let mut metric_name = None; + let mut dimensions = Vec::new(); + for attribute in &point.attributes { + match (attribute.key.as_str(), any(attribute)) { + ("Namespace", Some(any_value::Value::StringValue(value))) => { + namespace = Some(value.clone()); + } + ("MetricName", Some(any_value::Value::StringValue(value))) => { + metric_name = Some(value.clone()); + } + ("Dimensions", Some(any_value::Value::KvlistValue(list))) => { + dimensions.extend(list.values.iter().filter_map(|dimension| { + match any(dimension)? { + any_value::Value::StringValue(value) => { + Some((dimension.key.clone(), value.clone())) + } + _ => None, + } + })); + } + _ => {} + } + } + let from_name = metric + .name + .strip_prefix("amazonaws.com/") + .and_then(|rest| rest.rsplit_once('/')); + let namespace = namespace.or_else(|| from_name.map(|(ns, _)| ns.to_owned()))?; + let metric_name = metric_name.or_else(|| from_name.map(|(_, name)| name.to_owned()))?; + if namespace.is_empty() || metric_name.is_empty() { + return None; + } + Some(Self { + namespace, + metric_name, + dimensions, + }) + } + + fn dimension(&self, name: &str) -> Option<&str> { + self.dimensions + .iter() + .find(|(key, value)| key == name && !value.is_empty()) + .map(|(_, value)| value.as_str()) + } + + fn service_name(&self) -> String { + SERVICE_DIMENSIONS + .iter() + .find_map(|name| self.dimension(name)) + .map_or_else( + || format!("aws/{}", normalize_namespace(&self.namespace)), + str::to_owned, + ) + } + + fn cloud_platform(&self) -> Option<&'static str> { + match self.namespace.as_str() { + "AWS/ECS" | "ECS/ContainerInsights" => Some("aws_ecs"), + "AWS/Lambda" => Some("aws_lambda"), + "AWS/EC2" => Some("aws_ec2"), + _ => None, + } + } + + fn point_attributes(&self) -> Vec { + let mut attributes: Vec = self + .dimensions + .iter() + .map(|(name, value)| attribute(&format!("aws.dimension.{name}"), value)) + .collect(); + attributes.push(attribute("aws.namespace", &self.namespace)); + attributes.push(attribute("aws.metric_name", &self.metric_name)); + attributes + } + + /// The stream's own resource attributes (`cloud.*`, `aws.exporter.arn`) + /// with Maple's stamped over them, sorted by key. + fn resource_attributes( + &self, + incoming: &[KeyValue], + connection: &Connection<'_>, + ) -> Vec { + let mut attributes: Vec = incoming + .iter() + .filter(|attribute| { + !attribute.key.starts_with("maple_") + && !matches!( + attribute.key.as_str(), + "org_id" | "service.name" | "cloud.platform" + ) + }) + .cloned() + .collect(); + attributes.push(attribute("service.name", &self.service_name())); + if let Some(platform) = self.cloud_platform() { + attributes.push(attribute("cloud.platform", platform)); + } + attributes.push(attribute("maple_org_id", connection.org_id)); + attributes.push(attribute("maple_ingest_source", INGEST_SOURCE)); + attributes.push(attribute("maple_ingest_key_type", "connector")); + attributes.push(attribute("maple_aws_connection_id", connection.id)); + if let Some(source_id) = connection.source_id { + attributes.push(attribute("maple_source_id", source_id)); + } + attributes.sort_by(|a, b| a.key.cmp(&b.key)); + attributes + } +} + +/// The metrics Maple stores for a delivery's summary points: per point a +/// `` average gauge (when it has samples), `.min` and `.max` +/// gauges, and `.sum` / `.count` delta sums. Other metric types +/// are not part of the format and are skipped. +pub fn build_metrics_request( + requests: &[ExportMetricsServiceRequest], + connection: &Connection<'_>, +) -> ExportMetricsServiceRequest { + let mut builder = RequestBuilder::default(); + for resource_metrics in requests + .iter() + .flat_map(|request| &request.resource_metrics) + { + let incoming = resource_metrics + .resource + .as_ref() + .map_or(&[][..], |resource| resource.attributes.as_slice()); + for metric in resource_metrics + .scope_metrics + .iter() + .flat_map(|scope| &scope.metrics) + { + let Some(metric::Data::Summary(summary)) = &metric.data else { + continue; + }; + for point in &summary.data_points { + builder.add_point(incoming, metric, point, connection); + } + } + } + builder.finish() +} + +#[derive(Default)] +struct RequestBuilder { + resources: Vec, + /// The encoded resource attributes of each group, to its index. + by_resource: HashMap, usize>, +} + +struct ResourceGroup { + attributes: Vec, + metrics: Vec, + by_name: HashMap, +} + +#[derive(Clone, Copy)] +enum Kind { + Gauge, + DeltaSum { monotonic: bool }, +} + +impl RequestBuilder { + fn add_point( + &mut self, + incoming: &[KeyValue], + metric: &Metric, + point: &SummaryDataPoint, + connection: &Connection<'_>, + ) { + let Some(cloudwatch) = CloudWatchMetric::of(metric, point) else { + return; + }; + let group = self.group(cloudwatch.resource_attributes(incoming, connection)); + let base = metric_base_name(&cloudwatch.namespace, &cloudwatch.metric_name); + let attributes = cloudwatch.point_attributes(); + let number = |value: number_data_point::Value| NumberDataPoint { + attributes: attributes.clone(), + start_time_unix_nano: point.start_time_unix_nano, + time_unix_nano: point.time_unix_nano, + value: Some(value), + exemplars: Vec::new(), + flags: point.flags, + }; + let double = |value: f64| number(number_data_point::Value::AsDouble(value)); + let unit = metric.unit.as_str(); + + if point.count > 0 { + #[expect( + clippy::cast_precision_loss, + reason = "a sample count past 2^53 averages the same within f64 precision" + )] + let average = point.sum / point.count as f64; + group.push(base.clone(), unit, Kind::Gauge, double(average)); + } + if let Some(min) = quantile(point, 0.0) { + group.push(format!("{base}.min"), unit, Kind::Gauge, double(min)); + } + if let Some(max) = quantile(point, 1.0) { + group.push(format!("{base}.max"), unit, Kind::Gauge, double(max)); + } + group.push( + format!("{base}.sum"), + unit, + Kind::DeltaSum { monotonic: false }, + double(point.sum), + ); + // A count of samples, whatever the metric measures. + let count = i64::try_from(point.count).unwrap_or(i64::MAX); + group.push( + format!("{base}.count"), + "1", + Kind::DeltaSum { monotonic: true }, + number(number_data_point::Value::AsInt(count)), + ); + } + + fn group(&mut self, attributes: Vec) -> &mut ResourceGroup { + let key = Resource { + attributes: attributes.clone(), + ..Default::default() + } + .encode_to_vec(); + let index = *self.by_resource.entry(key).or_insert_with(|| { + self.resources.push(ResourceGroup { + attributes, + metrics: Vec::new(), + by_name: HashMap::new(), + }); + self.resources.len() - 1 + }); + &mut self.resources[index] + } + + fn finish(self) -> ExportMetricsServiceRequest { + ExportMetricsServiceRequest { + resource_metrics: self + .resources + .into_iter() + .map(|group| ResourceMetrics { + resource: Some(Resource { + attributes: group.attributes, + ..Default::default() + }), + scope_metrics: vec![ScopeMetrics { + scope: Some(InstrumentationScope { + name: SCOPE_NAME.to_owned(), + ..Default::default() + }), + metrics: group.metrics, + schema_url: String::new(), + }], + schema_url: String::new(), + }) + .collect(), + } + } +} + +impl ResourceGroup { + fn push(&mut self, name: String, unit: &str, kind: Kind, point: NumberDataPoint) { + if let Some(&index) = self.by_name.get(&name) { + match &mut self.metrics[index].data { + Some(metric::Data::Gauge(gauge)) => gauge.data_points.push(point), + Some(metric::Data::Sum(sum)) => sum.data_points.push(point), + _ => {} + } + return; + } + let data = match kind { + Kind::Gauge => metric::Data::Gauge(Gauge { + data_points: vec![point], + }), + Kind::DeltaSum { monotonic } => metric::Data::Sum(Sum { + data_points: vec![point], + aggregation_temporality: AggregationTemporality::Delta as i32, + is_monotonic: monotonic, + }), + }; + self.by_name.insert(name.clone(), self.metrics.len()); + self.metrics.push(Metric { + name, + unit: unit.to_owned(), + data: Some(data), + ..Default::default() + }); + } +} + +fn quantile(point: &SummaryDataPoint, quantile: f64) -> Option { + point + .quantile_values + .iter() + .find(|value| (value.quantile - quantile).abs() < f64::EPSILON) + .map(|value| value.value) +} + +fn any(attribute: &KeyValue) -> Option<&any_value::Value> { + attribute.value.as_ref()?.value.as_ref() +} + +fn attribute(key: &str, value: &str) -> KeyValue { + KeyValue { + key: key.to_owned(), + value: Some(AnyValue { + value: Some(any_value::Value::StringValue(value.to_owned())), + }), + ..Default::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use opentelemetry_proto::tonic::common::v1::KeyValueList; + use opentelemetry_proto::tonic::metrics::v1::summary_data_point::ValueAtQuantile; + use opentelemetry_proto::tonic::metrics::v1::Summary; + + const CONNECTION: Connection<'static> = Connection { + id: "6f1c2b9e-1d3a-4c5b-9e8f-0a1b2c3d4e5f", + org_id: "org_1", + source_id: Some("src_aws"), + }; + const START: u64 = 1_800_000_000_000_000_000; + const END: u64 = 1_800_000_060_000_000_000; + + fn string(value: &str) -> AnyValue { + AnyValue { + value: Some(any_value::Value::StringValue(value.to_owned())), + } + } + + fn kv(key: &str, value: &str) -> KeyValue { + KeyValue { + key: key.to_owned(), + value: Some(string(value)), + ..Default::default() + } + } + + /// A point as CloudWatch writes it: count, sum, min at 0.0 and max at 1.0. + fn point( + dimensions: &[(&str, &str)], + count: u64, + sum: f64, + min: f64, + max: f64, + ) -> SummaryDataPoint { + point_in( + "AWS/ECS", + "CPUUtilization", + dimensions, + count, + sum, + min, + max, + ) + } + + fn point_in( + namespace: &str, + metric_name: &str, + dimensions: &[(&str, &str)], + count: u64, + sum: f64, + min: f64, + max: f64, + ) -> SummaryDataPoint { + SummaryDataPoint { + attributes: vec![ + kv("Namespace", namespace), + kv("MetricName", metric_name), + KeyValue { + key: "Dimensions".to_owned(), + value: Some(AnyValue { + value: Some(any_value::Value::KvlistValue(KeyValueList { + values: dimensions.iter().map(|(k, v)| kv(k, v)).collect(), + })), + }), + ..Default::default() + }, + ], + start_time_unix_nano: START, + time_unix_nano: END, + count, + sum, + quantile_values: vec![ + ValueAtQuantile { + quantile: 0.0, + value: min, + }, + ValueAtQuantile { + quantile: 0.99, + value: max, + }, + ValueAtQuantile { + quantile: 1.0, + value: max, + }, + ], + flags: 0, + } + } + + fn stream_request( + metrics: Vec<(&str, &str, Vec)>, + ) -> ExportMetricsServiceRequest { + ExportMetricsServiceRequest { + resource_metrics: vec![ResourceMetrics { + resource: Some(Resource { + attributes: vec![ + kv("cloud.provider", "aws"), + kv("cloud.account.id", "123456789012"), + kv("cloud.region", "us-east-1"), + kv( + "aws.exporter.arn", + "arn:aws:cloudwatch:us-east-1:123456789012:metric-stream/maple", + ), + kv("maple_org_id", "org_forged"), + kv("org_id", "org_forged"), + ], + ..Default::default() + }), + scope_metrics: vec![ScopeMetrics { + scope: None, + metrics: metrics + .into_iter() + .map(|(name, unit, points)| Metric { + name: name.to_owned(), + unit: unit.to_owned(), + data: Some(metric::Data::Summary(Summary { + data_points: points, + })), + ..Default::default() + }) + .collect(), + schema_url: String::new(), + }], + schema_url: String::new(), + }], + } + } + + fn string_attr<'a>(attributes: &'a [KeyValue], key: &str) -> Option<&'a str> { + attributes + .iter() + .find(|attribute| attribute.key == key) + .and_then(|attribute| match any(attribute)? { + any_value::Value::StringValue(value) => Some(value.as_str()), + _ => None, + }) + } + + fn resource_attrs(resource: &ResourceMetrics) -> &[KeyValue] { + &resource.resource.as_ref().unwrap().attributes + } + + fn metrics(resource: &ResourceMetrics) -> &[Metric] { + &resource.scope_metrics[0].metrics + } + + fn find<'a>(resource: &'a ResourceMetrics, name: &str) -> &'a Metric { + metrics(resource) + .iter() + .find(|metric| metric.name == name) + .unwrap_or_else(|| panic!("no metric {name}")) + } + + fn value(point: &NumberDataPoint) -> f64 { + match point.value { + Some(number_data_point::Value::AsDouble(value)) => value, + #[expect(clippy::cast_precision_loss, reason = "small test counts")] + Some(number_data_point::Value::AsInt(value)) => value as f64, + None => f64::NAN, + } + } + + fn gauge_value(metric: &Metric) -> f64 { + match &metric.data { + Some(metric::Data::Gauge(gauge)) => value(&gauge.data_points[0]), + other => panic!("{} is not a gauge: {other:?}", metric.name), + } + } + + #[test] + fn snake_cases_cloudwatch_metric_names() { + for (name, expected) in [ + ("CPUUtilization", "cpu_utilization"), + ("HTTPCode_Target_5XX_Count", "http_code_target_5xx_count"), + ("RequestCount", "request_count"), + ( + "ApproximateAgeOfOldestMessage", + "approximate_age_of_oldest_message", + ), + ("MemoryUtilized", "memory_utilized"), + ("4XXError", "4xx_error"), + ("Already_snake__case_", "already_snake_case"), + ] { + assert_eq!(snake_case(name), expected, "{name}"); + } + } + + #[test] + fn normalizes_namespaces() { + for (namespace, expected) in [ + ("AWS/ECS", "ecs"), + ("ECS/ContainerInsights", "ecs.container_insights"), + ("AWS/ApplicationELB", "application_elb"), + ("AWS/Lambda", "lambda"), + ("Custom/MyApp", "custom.my_app"), + ] { + assert_eq!(normalize_namespace(namespace), expected, "{namespace}"); + } + assert_eq!( + metric_base_name("AWS/ApplicationELB", "HTTPCode_Target_5XX_Count"), + "aws.application_elb.http_code_target_5xx_count" + ); + } + + #[test] + fn expands_a_summary_point_into_gauges_and_delta_sums() { + let request = stream_request(vec![( + "amazonaws.com/AWS/ECS/CPUUtilization", + "Percent", + vec![point( + &[("ClusterName", "prod"), ("ServiceName", "checkout")], + 4, + 100.0, + 10.0, + 40.0, + )], + )]); + let built = build_metrics_request(&[request], &CONNECTION); + assert_eq!(built.resource_metrics.len(), 1); + let resource = &built.resource_metrics[0]; + let names: Vec<&str> = metrics(resource).iter().map(|m| m.name.as_str()).collect(); + assert_eq!( + names, + [ + "aws.ecs.cpu_utilization", + "aws.ecs.cpu_utilization.min", + "aws.ecs.cpu_utilization.max", + "aws.ecs.cpu_utilization.sum", + "aws.ecs.cpu_utilization.count", + ] + ); + assert!((gauge_value(find(resource, "aws.ecs.cpu_utilization")) - 25.0).abs() < 1e-9); + assert!((gauge_value(find(resource, "aws.ecs.cpu_utilization.min")) - 10.0).abs() < 1e-9); + assert!((gauge_value(find(resource, "aws.ecs.cpu_utilization.max")) - 40.0).abs() < 1e-9); + + for (name, expected, monotonic) in [ + ("aws.ecs.cpu_utilization.sum", 100.0, false), + ("aws.ecs.cpu_utilization.count", 4.0, true), + ] { + let Some(metric::Data::Sum(sum)) = &find(resource, name).data else { + panic!("{name} is not a sum"); + }; + assert_eq!( + sum.aggregation_temporality, + AggregationTemporality::Delta as i32, + "{name}" + ); + assert_eq!(sum.is_monotonic, monotonic, "{name}"); + assert!( + (value(&sum.data_points[0]) - expected).abs() < 1e-9, + "{name}" + ); + assert_eq!(sum.data_points[0].start_time_unix_nano, START); + assert_eq!(sum.data_points[0].time_unix_nano, END); + } + assert_eq!(find(resource, "aws.ecs.cpu_utilization").unit, "Percent"); + assert_eq!( + find(resource, "aws.ecs.cpu_utilization.sum").unit, + "Percent" + ); + } + + #[test] + fn a_point_without_samples_has_no_average() { + let request = stream_request(vec![( + "amazonaws.com/AWS/SQS/NumberOfMessagesSent", + "Count", + vec![point_in( + "AWS/SQS", + "NumberOfMessagesSent", + &[("QueueName", "jobs")], + 0, + 0.0, + 0.0, + 0.0, + )], + )]); + let built = build_metrics_request(&[request], &CONNECTION); + let resource = &built.resource_metrics[0]; + assert!(metrics(resource) + .iter() + .all(|metric| metric.name != "aws.sqs.number_of_messages_sent")); + assert_eq!(metrics(resource).len(), 4); + } + + #[test] + fn stamps_dimensions_on_points_and_maple_attributes_on_the_resource() { + let request = stream_request(vec![( + "amazonaws.com/AWS/ECS/CPUUtilization", + "Percent", + vec![point( + &[("ClusterName", "prod"), ("ServiceName", "checkout")], + 1, + 5.0, + 5.0, + 5.0, + )], + )]); + let built = build_metrics_request(&[request], &CONNECTION); + let resource = &built.resource_metrics[0]; + let Some(metric::Data::Gauge(gauge)) = &find(resource, "aws.ecs.cpu_utilization").data + else { + panic!("not a gauge"); + }; + let point_attrs = &gauge.data_points[0].attributes; + assert_eq!( + string_attr(point_attrs, "aws.dimension.ClusterName"), + Some("prod") + ); + assert_eq!( + string_attr(point_attrs, "aws.dimension.ServiceName"), + Some("checkout") + ); + assert_eq!(string_attr(point_attrs, "aws.namespace"), Some("AWS/ECS")); + assert_eq!( + string_attr(point_attrs, "aws.metric_name"), + Some("CPUUtilization") + ); + + let attrs = resource_attrs(resource); + assert_eq!(string_attr(attrs, "service.name"), Some("checkout")); + assert_eq!(string_attr(attrs, "cloud.platform"), Some("aws_ecs")); + assert_eq!(string_attr(attrs, "cloud.provider"), Some("aws")); + assert_eq!(string_attr(attrs, "cloud.account.id"), Some("123456789012")); + assert_eq!(string_attr(attrs, "cloud.region"), Some("us-east-1")); + assert!(string_attr(attrs, "aws.exporter.arn").is_some()); + assert_eq!(string_attr(attrs, "maple_org_id"), Some("org_1")); + assert_eq!( + string_attr(attrs, "maple_ingest_source"), + Some(INGEST_SOURCE) + ); + assert_eq!( + string_attr(attrs, "maple_ingest_key_type"), + Some("connector") + ); + assert_eq!( + string_attr(attrs, "maple_aws_connection_id"), + Some(CONNECTION.id) + ); + assert_eq!(string_attr(attrs, "maple_source_id"), Some("src_aws")); + assert!(string_attr(attrs, "org_id").is_none()); + assert_eq!( + attrs.iter().filter(|kv| kv.key == "maple_org_id").count(), + 1 + ); + } + + #[test] + fn derives_service_name_and_platform_from_dimensions() { + for (namespace, dimensions, service, platform) in [ + ( + "AWS/Lambda", + vec![("FunctionName", "resize")], + "resize", + Some("aws_lambda"), + ), + ( + "AWS/RDS", + vec![ + ("DBInstanceIdentifier", "db-1"), + ("DBClusterIdentifier", "c"), + ], + "db-1", + None, + ), + ( + "AWS/RDS", + vec![("DBClusterIdentifier", "cluster-1")], + "cluster-1", + None, + ), + ( + "AWS/ApplicationELB", + vec![("LoadBalancer", "app/web/50dc")], + "app/web/50dc", + None, + ), + ("AWS/SQS", vec![("QueueName", "jobs")], "jobs", None), + ( + "AWS/EC2", + vec![("InstanceId", "i-0abc")], + "i-0abc", + Some("aws_ec2"), + ), + ( + "ECS/ContainerInsights", + vec![("ClusterName", "prod")], + "aws/ecs.container_insights", + Some("aws_ecs"), + ), + ("AWS/EBS", vec![("VolumeId", "vol-1")], "aws/ebs", None), + ] { + let request = stream_request(vec![( + "ignored", + "None", + vec![point_in(namespace, "Metric", &dimensions, 1, 1.0, 1.0, 1.0)], + )]); + let built = build_metrics_request(&[request], &CONNECTION); + let attrs = resource_attrs(&built.resource_metrics[0]); + assert_eq!( + string_attr(attrs, "service.name"), + Some(service), + "{namespace}" + ); + assert_eq!( + string_attr(attrs, "cloud.platform"), + platform, + "{namespace}" + ); + } + } + + #[test] + fn groups_points_into_one_resource_per_service() { + let first = stream_request(vec![( + "amazonaws.com/AWS/ECS/CPUUtilization", + "Percent", + vec![ + point(&[("ServiceName", "checkout")], 1, 1.0, 1.0, 1.0), + point(&[("ServiceName", "search")], 1, 2.0, 2.0, 2.0), + ], + )]); + let second = stream_request(vec![( + "amazonaws.com/AWS/ECS/MemoryUtilization", + "Percent", + vec![point_in( + "AWS/ECS", + "MemoryUtilization", + &[("ServiceName", "checkout")], + 1, + 3.0, + 3.0, + 3.0, + )], + )]); + let built = build_metrics_request(&[first, second], &CONNECTION); + let services: Vec> = built + .resource_metrics + .iter() + .map(|resource| string_attr(resource_attrs(resource), "service.name")) + .collect(); + assert_eq!(services, [Some("checkout"), Some("search")]); + let checkout = &built.resource_metrics[0]; + assert!(metrics(checkout) + .iter() + .any(|m| m.name == "aws.ecs.cpu_utilization")); + assert!(metrics(checkout) + .iter() + .any(|m| m.name == "aws.ecs.memory_utilization")); + assert_eq!(metrics(&built.resource_metrics[1]).len(), 5); + } + + #[test] + fn falls_back_to_the_metric_name_for_namespace_and_name() { + let mut bare = point(&[], 1, 1.0, 1.0, 1.0); + bare.attributes + .retain(|attribute| attribute.key == "Dimensions"); + let request = stream_request(vec![( + "amazonaws.com/ECS/ContainerInsights/CpuUtilized", + "None", + vec![bare], + )]); + let built = build_metrics_request(&[request], &CONNECTION); + let resource = &built.resource_metrics[0]; + assert!(metrics(resource) + .iter() + .any(|m| m.name == "aws.ecs.container_insights.cpu_utilized")); + } + + fn delivery_json(records: &[Vec]) -> Vec { + let records: Vec = records + .iter() + .map(|data| serde_json::json!({ "data": STANDARD.encode(data) })) + .collect(); + serde_json::json!({ "requestId": "req-1", "timestamp": 1_800_000_000_000_i64, "records": records }) + .to_string() + .into_bytes() + } + + fn length_delimited(requests: &[ExportMetricsServiceRequest]) -> Vec { + requests + .iter() + .flat_map(Message::encode_length_delimited_to_vec) + .collect() + } + + #[test] + fn reads_every_length_delimited_request_in_every_record() { + let one = stream_request(vec![( + "amazonaws.com/AWS/ECS/A", + "None", + vec![point(&[], 1, 1.0, 1.0, 1.0)], + )]); + let two = stream_request(vec![( + "amazonaws.com/AWS/ECS/B", + "None", + vec![point(&[], 2, 2.0, 1.0, 1.0)], + )]); + let body = delivery_json(&[ + length_delimited(&[one.clone(), two.clone()]), + length_delimited(std::slice::from_ref(&one)), + ]); + + let delivery = parse_delivery(&body).expect("a Firehose request"); + assert_eq!(delivery.request_id.as_deref(), Some("req-1")); + let requests = delivery + .metric_requests(1024 * 1024) + .expect("readable records"); + assert_eq!(requests, [one.clone(), two, one]); + } + + #[test] + fn refuses_unreadable_or_oversized_records() { + assert!(parse_delivery(b"not json").is_none()); + assert!(parse_delivery(br#"{"requestId":"r"}"#).is_none()); + + let bad_base64 = + parse_delivery(br#"{"requestId":"r","records":[{"data":"!!!"}]}"#).unwrap(); + assert_eq!( + bad_base64.metric_requests(1024).unwrap_err(), + RecordError::Unreadable + ); + + let truncated = { + let mut bytes = length_delimited(&[stream_request(vec![])]); + bytes.push(0x7f); + bytes + }; + let delivery = parse_delivery(&delivery_json(&[truncated])).unwrap(); + assert_eq!( + delivery.metric_requests(1024).unwrap_err(), + RecordError::Unreadable + ); + + let big = length_delimited(&[stream_request(vec![( + "amazonaws.com/AWS/ECS/A", + "None", + vec![point(&[], 1, 1.0, 1.0, 1.0)], + )])]); + let delivery = parse_delivery(&delivery_json(&[big.clone(), big.clone()])).unwrap(); + assert_eq!( + delivery.metric_requests(big.len() + 1).unwrap_err(), + RecordError::TooLarge + ); + assert_eq!(delivery.metric_requests(big.len() * 2).unwrap().len(), 2); + } +} diff --git a/apps/ingest/src/lib.rs b/apps/ingest/src/lib.rs index 22c1e5e57c..ec7d9a4649 100644 --- a/apps/ingest/src/lib.rs +++ b/apps/ingest/src/lib.rs @@ -1,5 +1,6 @@ pub mod ai_session; pub mod aws; +pub mod aws_metric_stream; pub mod clickhouse_insert_mappings; pub mod gcp_logging; pub mod gcp_push_auth; diff --git a/apps/ingest/src/main.rs b/apps/ingest/src/main.rs index 328fd917c8..1c669fc9fd 100644 --- a/apps/ingest/src/main.rs +++ b/apps/ingest/src/main.rs @@ -46,6 +46,7 @@ use flate2::Compression; use hmac::{Hmac, Mac}; use maple_ingest::ai_session; use maple_ingest::aws::CredentialsProvider as AwsCredentialsProvider; +use maple_ingest::aws_metric_stream; use maple_ingest::clickhouse_insert_mappings::SCHEMA_VERSION as CLICKHOUSE_SCHEMA_VERSION; use maple_ingest::gcp_logging; use maple_ingest::gcp_push_auth::{GooglePushAuth, PushAuthError}; @@ -910,6 +911,26 @@ struct GcpConnectorResolver { push_auth: GooglePushAuth, } +const AWS_CONNECTIONS_TABLE: &str = "aws_connections"; + +/// How often one AWS connection's stream health columns are rewritten per +/// outcome. A busy stream delivers every few seconds. +const AWS_STREAM_HEALTH_WRITE_INTERVAL: Duration = Duration::from_mins(1); + +struct AwsConnectionResolver { + store: Arc, + lookup_hmac_key: String, + /// Keyed on (connection id, stream secret hash). + cache: Cache<(String, String), AwsConnectionIdentity>, + // A deleted connection's Firehose keeps delivering and retrying each 401; + // this keeps that off Postgres. + negative_cache: Cache<(String, String), ()>, + routing: Arc, + /// (connection id, succeeded) pairs already written within + /// `AWS_STREAM_HEALTH_WRITE_INTERVAL`. + recent_health_writes: Cache<(String, bool), ()>, +} + struct OrgRoutingResolver { store: Arc, cache: Cache, @@ -1061,6 +1082,22 @@ trait KeyStore: Send + Sync { report: gcp_logging::SetupReport, now_ms: i64, ) -> Result<(), String>; + + /// An AWS connection by id and metric stream secret hash. + async fn fetch_aws_connection( + &self, + connection_id: &str, + secret_hash: &str, + ) -> Result, String>; + + /// Stamps `stream_last_received_at` and clears `stream_last_error` + /// (`error == None`), or writes `stream_last_error`. + async fn record_aws_stream_health( + &self, + connection_id: &str, + error: Option<&str>, + now_ms: i64, + ) -> Result<(), String>; } #[derive(Clone, Debug)] @@ -1128,6 +1165,15 @@ struct GcpConnectorRow { source: Option, } +#[derive(Clone, Debug)] +struct AwsConnectionRow { + org_id: String, + self_managed: bool, + clickhouse_ready: bool, + /// The connection's `sources` row, joined through `source_id`. + source: Option, +} + #[derive(Clone, Debug)] struct SamplingPolicyRow { trace_sample_ratio: f64, @@ -1228,6 +1274,33 @@ impl GcpConnectorIdentity { } } +#[derive(Clone)] +struct AwsConnectionIdentity { + org_id: String, + secret_key_id: String, + /// The connection's integration source, stamped on its metrics. + source: Option, + /// That source's pause window, judged per delivery so a timed pause ends on time. + source_pause: PauseWindow, +} + +impl AwsConnectionIdentity { + fn into_resolved(self, routing: &OrgRouting) -> ResolvedAwsConnection { + ResolvedAwsConnection { + source_pause: self.source_pause, + key: ResolvedIngestKey { + org_id: self.org_id, + key_type: IngestKeyType::Connector, + key_id: self.secret_key_id, + self_managed: routing.self_managed, + clickhouse_ready: routing.clickhouse_ready, + source: None, + default_source: self.source, + }, + } + } +} + #[derive(Clone, Debug, Default)] struct OrgRouting { self_managed: bool, @@ -1396,6 +1469,7 @@ struct AppState { attribute_mapping_resolver: AttributeMappingResolver, cloudflare_resolver: CloudflareConnectorResolver, gcp_resolver: GcpConnectorResolver, + aws_resolver: AwsConnectionResolver, autumn_tracker: Option, autumn_entitlements: Option, /// Per-org ingest volume, on its own delta-temporality provider. Recorded @@ -1496,6 +1570,23 @@ struct ResolvedGcpConnector { key: ResolvedIngestKey, } +struct ResolvedAwsConnection { + /// The integration source's pause window: a paused source's deliveries are acked and dropped. + source_pause: PauseWindow, + key: ResolvedIngestKey, +} + +/// What an acked Firehose delivery did. +enum AwsStreamOutcome { + Accepted { + item_count: usize, + }, + /// The body or a record can never decode, so a retry is pointless. + Undecodable, + /// The connection's integration source is paused. + SourcePaused, +} + /// What an accepted Cloudflare Logpush request did. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum LogpushOutcome { @@ -2884,10 +2975,16 @@ async fn main() { gcp_resolver: GcpConnectorResolver::new( Arc::clone(&store), config.lookup_hmac_key.clone(), - org_routing_resolver, + Arc::clone(&org_routing_resolver), Duration::from_secs(config.ingest_key_cache_ttl_secs), GooglePushAuth::new(http_client.clone()), ), + aws_resolver: AwsConnectionResolver::new( + Arc::clone(&store), + config.lookup_hmac_key.clone(), + org_routing_resolver, + Duration::from_secs(config.ingest_key_cache_ttl_secs), + ), telemetry_pipeline, http_client, config: config.clone(), @@ -2945,6 +3042,10 @@ async fn main() { post(handle_cloudflare_logpush_http_requests), ) .route("/v1/logpush/gcp/{connector_id}", post(handle_gcp_logpush)) + .route( + "/v1/firehose/aws/{connection_id}", + post(handle_aws_firehose), + ) .layer(cors) .layer(DefaultBodyLimit::max(config.max_request_body_bytes)) .with_state(state); @@ -5807,6 +5908,371 @@ fn is_connector_id(value: &str) -> bool { .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'-') } +/// The plaintext stream secret Firehose sends with every delivery. +const FIREHOSE_ACCESS_KEY_HEADER: &str = "x-amz-firehose-access-key"; +const FIREHOSE_REQUEST_ID_HEADER: &str = "x-amz-firehose-request-id"; + +/// What Firehose's HTTP endpoint destination expects back. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct FirehoseResponseBody<'a> { + request_id: &'a str, + timestamp: i64, + #[serde(skip_serializing_if = "Option::is_none")] + error_message: Option<&'a str>, +} + +fn firehose_response(status: StatusCode, request_id: &str, error: Option<&ApiError>) -> Response { + let body = FirehoseResponseBody { + request_id, + timestamp: current_time_millis(), + error_message: error.map(|error| error.message.as_str()), + }; + let mut response = (status, axum::Json(body)).into_response(); + let retry_after = error.and_then(|error| error.kind.retry_after_seconds); + if let Some(value) = + retry_after.and_then(|seconds| HeaderValue::from_str(&seconds.to_string()).ok()) + { + response.headers_mut().insert(RETRY_AFTER, value); + } + response +} + +/// Receives a CloudWatch metric stream through Amazon Data Firehose's HTTP +/// endpoint destination. Firehose retries anything but a 200 until its retry +/// window ends, so a delivery that can never decode is acked. +async fn handle_aws_firehose( + State(state): State>, + Path(connection_id): Path, + headers: HeaderMap, + body: Bytes, +) -> Response { + let start = Instant::now(); + + metrics::request_started(); + let _guard = InFlightGuard; + + let span = tracing::info_span!( + "aws_firehose", + otel.name = "POST /v1/firehose/aws/{connection_id}", + otel.kind = "server", + otel.status_code = tracing::field::Empty, + otel.status_description = tracing::field::Empty, + "maple.ingest.reject_reason" = tracing::field::Empty, + "http.request.method" = "POST", + "http.route" = "/v1/firehose/aws/{connection_id}", + "http.request.body.size" = body.len(), + "http.response.status_code" = tracing::field::Empty, + "error.type" = tracing::field::Empty, + "maple.signal" = "metrics", + "maple.org_id" = tracing::field::Empty, + "maple.aws.connection_id" = tracing::field::Empty, + "maple.ingest.self_managed" = tracing::field::Empty, + "maple.ingest.clickhouse_ready" = tracing::field::Empty, + "maple.ingest.destination" = tracing::field::Empty, + "maple.ingest.decoded_bytes" = tracing::field::Empty, + "maple.ingest.item_count" = tracing::field::Empty, + ); + let span_handle = span.clone(); + + let header = |name: &str| headers.get(name).and_then(|value| value.to_str().ok()); + // The body's `requestId` replaces this once the body is read. + let mut request_id = header(FIREHOSE_REQUEST_ID_HEADER) + .unwrap_or_default() + .to_owned(); + let content_encoding = header(CONTENT_ENCODING.as_str()) + .map(|value| value.trim().to_ascii_lowercase()) + .filter(|value| !value.is_empty() && value != "identity"); + let result = handle_aws_firehose_inner( + &state, + &connection_id, + header(FIREHOSE_ACCESS_KEY_HEADER), + content_encoding.as_deref(), + &body, + &mut request_id, + ) + .instrument(span) + .await; + let duration = start.elapsed().as_secs_f64(); + let signal = Signal::Metrics.path(); + + match result { + Ok(outcome) => { + span_handle.record("http.response.status_code", StatusCode::OK.as_u16()); + match outcome { + AwsStreamOutcome::Accepted { item_count } => { + span_handle.record("otel.status_code", "Ok"); + span_handle.record("maple.ingest.item_count", item_count); + metrics::request_completed(signal, "ok", "none", duration); + } + AwsStreamOutcome::Undecodable => { + // Acked, but the metrics are gone: an `Error` like every + // other rejection that loses the sender's data. + span_handle.record("error.type", "decode"); + record_rejection_reason( + &span_handle, + 200, + "decode", + "Firehose delivery is not OpenTelemetry 1.0 metric stream records", + ); + metrics::request_completed(signal, "error", "decode", duration); + } + AwsStreamOutcome::SourcePaused => { + span_handle.record("otel.status_code", "Ok"); + span_handle.record("maple.ingest.reject_reason", "source_paused"); + metrics::request_completed(signal, "ok", "none", duration); + } + } + firehose_response(StatusCode::OK, &request_id, None) + } + Err((error, error_kind)) => { + let status = error.status.as_u16(); + span_handle.record("http.response.status_code", status); + span_handle.record("error.type", error_kind); + record_rejection_reason(&span_handle, status, error_kind, error.reason().as_str()); + metrics::request_completed(signal, "error", error_kind, duration); + firehose_response(error.status, &request_id, Some(&error)) + } + } +} + +/// Returns what the ack means, or Err((ApiError, error_kind_label)). +/// `request_id` takes the body's `requestId` once the body is read. +async fn handle_aws_firehose_inner( + state: &AppState, + connection_id: &str, + access_key: Option<&str>, + content_encoding: Option<&str>, + body: &Bytes, + request_id: &mut String, +) -> Result { + let unauthorized = || { + ( + ApiError::unauthorized("Invalid connection credentials"), + "auth", + ) + }; + let access_key = access_key.map(str::trim).filter(|key| !key.is_empty()); + let (Some(access_key), true) = (access_key, is_aws_connection_id(connection_id)) else { + warn!("Missing Firehose access key or malformed AWS connection id"); + return Err(unauthorized()); + }; + Span::current().record("maple.aws.connection_id", connection_id); + + let resolved = state + .aws_resolver + .resolve_connection(connection_id, access_key) + .await + .map_err(|error| { + error!(error = %error, connection_id, "AWS connection resolution failed"); + ( + ApiError::service_unavailable("Connection authentication unavailable"), + "auth", + ) + })? + .ok_or_else(|| { + warn!(connection_id, "Invalid AWS metric stream credentials"); + unauthorized() + })?; + let org_id = resolved.key.org_id.as_str(); + let signal = Signal::Metrics.path(); + + Span::current().record("maple.org_id", org_id); + Span::current().record("maple.ingest.self_managed", resolved.key.self_managed); + Span::current().record( + "maple.ingest.clickhouse_ready", + resolved.key.clickhouse_ready, + ); + + // A paused integration source: acked like a paused token source, nothing stored or billed. + if resolved.source_pause.is_paused(current_time_millis()) { + metrics::source_paused_drop(org_id, signal); + debug!( + org_id, + connection_id, "AWS integration source paused; delivery acked and dropped" + ); + return Ok(AwsStreamOutcome::SourcePaused); + } + + let _org_inflight_permit = state + .org_inflight_limiter + .try_acquire(org_id) + .ok_or_else(|| { + warn!( + org_id, + connection_id, "Per-org in-flight ingest limit exceeded" + ); + ( + ApiError::too_many_requests("Per-org ingest limit exceeded"), + "throttle", + ) + })?; + + // Before the body is decoded, so a denied org's delivery is not gunzipped. + if let Some(error) = entitlement_rejection(state, org_id, signal).await { + state + .aws_resolver + .record_health(connection_id, Some(aws_metric_stream::OVER_PLAN_LIMIT)) + .await; + return Err((error, "billing_limit")); + } + + let Some(requests) = read_delivery( + state, + connection_id, + org_id, + content_encoding, + body, + request_id, + ) + .await? + else { + return Ok(AwsStreamOutcome::Undecodable); + }; + let item_count = store_delivery(state, connection_id, &resolved.key, &requests).await?; + Ok(AwsStreamOutcome::Accepted { item_count }) +} + +/// The delivery's metric requests, or `None` for one that can never decode +/// (already acked and recorded as lost). Sets `request_id` from the body. +async fn read_delivery( + state: &AppState, + connection_id: &str, + org_id: &str, + content_encoding: Option<&str>, + body: &Bytes, + request_id: &mut String, +) -> Result>, (ApiError, &'static str)> { + let max_decoded = state.config.max_decoded_body_bytes; + let payload = match decode_payload(body, content_encoding, max_decoded) { + Ok(payload) => payload, + Err(error) if error.status == StatusCode::PAYLOAD_TOO_LARGE => { + return Err(refuse_oversized_delivery(state, connection_id, org_id).await); + } + Err(_) => { + ack_undecodable_delivery(state, connection_id, org_id).await; + return Ok(None); + } + }; + let Some(delivery) = aws_metric_stream::parse_delivery(&payload) else { + ack_undecodable_delivery(state, connection_id, org_id).await; + return Ok(None); + }; + if let Some(body_request_id) = &delivery.request_id { + request_id.clone_from(body_request_id); + } + match delivery.metric_requests(max_decoded) { + Ok(requests) => Ok(Some(requests)), + Err(aws_metric_stream::RecordError::TooLarge) => { + Err(refuse_oversized_delivery(state, connection_id, org_id).await) + } + Err(aws_metric_stream::RecordError::Unreadable) => { + ack_undecodable_delivery(state, connection_id, org_id).await; + Ok(None) + } + } +} + +/// Converts, stores and meters a delivery's metrics; returns the points stored. +async fn store_delivery( + state: &AppState, + connection_id: &str, + key: &ResolvedIngestKey, + requests: &[ExportMetricsServiceRequest], +) -> Result { + let org_id = key.org_id.as_str(); + let signal = Signal::Metrics.path(); + // Metered as CloudWatch sent it, not as the larger expansion Maple stores. + let received_bytes: usize = requests.iter().map(Message::encoded_len).sum(); + Span::current().record("maple.ingest.decoded_bytes", received_bytes); + + let decoded = DecodedPayload::Metrics(aws_metric_stream::build_metrics_request( + requests, + &aws_metric_stream::Connection { + id: connection_id, + org_id, + source_id: key.source_public_id(), + }, + )); + let item_count = decoded.item_count(); + if item_count > 0 { + if let Err(error) = process_decoded_payload( + state, + Signal::Metrics, + PayloadFormat::Protobuf, + None, + &decoded, + key, + ) + .await + { + state + .aws_resolver + .record_health(connection_id, Some(aws_metric_stream::DELIVERY_NOT_STORED)) + .await; + return Err((error, "forward")); + } + } + state.aws_resolver.record_health(connection_id, None).await; + state.resolver.note_source_use(key, false); + + if item_count > 0 { + if let Some(usage) = &state.usage_metrics { + usage.record(org_id, signal, received_bytes as u64, item_count as u64); + } + if let Some(tracker) = &state.autumn_tracker { + tracker.track(org_id, signal, billable_gb(received_bytes as u64)); + } + } + Ok(item_count) +} + +/// A delivery that can never decode: acked so Firehose does not retry it, and +/// counted as lost. +async fn ack_undecodable_delivery(state: &AppState, connection_id: &str, org_id: &str) { + // Not `warn!`: a stream in the wrong output format fails this way on every delivery. + debug!( + org_id, + connection_id, "AWS metric stream delivery cannot be decoded; acked and dropped" + ); + metrics::org_data_loss(org_id, Signal::Metrics.path(), "decode"); + state + .aws_resolver + .record_health(connection_id, Some(aws_metric_stream::UNREADABLE_DELIVERY)) + .await; +} + +/// Firehose does not retry a 413, so the delivery is lost. +async fn refuse_oversized_delivery( + state: &AppState, + connection_id: &str, + org_id: &str, +) -> (ApiError, &'static str) { + warn!( + org_id, + connection_id, "AWS metric stream delivery decodes past the size limit" + ); + metrics::org_data_loss(org_id, Signal::Metrics.path(), "payload_too_large"); + state + .aws_resolver + .record_health(connection_id, Some(aws_metric_stream::DELIVERY_TOO_LARGE)) + .await; + ( + ApiError::payload_too_large("Decoded delivery too large"), + "payload_too_large", + ) +} + +/// `aws_connections.id` is a lowercase UUID; anything else is refused before +/// it reaches Postgres. +fn is_aws_connection_id(value: &str) -> bool { + value.len() == 36 + && value.bytes().enumerate().all(|(index, byte)| match index { + 8 | 13 | 18 | 23 => byte == b'-', + _ => byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte), + }) +} + enum ParsedCloudflarePayload { Validation, Records(Vec>), @@ -7510,10 +7976,109 @@ impl GcpConnectorResolver { } } -impl SamplingPolicyResolver { - #[hotpath::measure] - async fn resolve_policy(&self, org_id: &str) -> SamplingPolicy { - if let Some(policy) = self.cache.get(org_id).await { +impl AwsConnectionResolver { + fn new( + store: Arc, + lookup_hmac_key: String, + routing: Arc, + cache_ttl: Duration, + ) -> Self { + Self { + store, + lookup_hmac_key, + cache: Cache::builder() + .time_to_live(cache_ttl) + .max_capacity(10_000) + .build(), + negative_cache: Cache::builder() + .time_to_live(Duration::from_secs(30)) + .max_capacity(10_000) + .build(), + routing, + recent_health_writes: Cache::builder() + .time_to_live(AWS_STREAM_HEALTH_WRITE_INTERVAL) + .max_capacity(20_000) + .build(), + } + } + + async fn resolve_connection( + &self, + connection_id: &str, + raw_secret: &str, + ) -> Result, String> { + let secret_hash = hash_ingest_key(raw_secret, &self.lookup_hmac_key)?; + let cache_key = (connection_id.to_owned(), secret_hash); + if let Some(identity) = self.cache.get(&cache_key).await { + let routing = self.routing.resolve_org_routing(&identity.org_id).await?; + return Ok(Some(identity.into_resolved(&routing))); + } + if self.negative_cache.contains_key(&cache_key) { + return Ok(None); + } + + let Some(row) = self + .store + .fetch_aws_connection(connection_id, &cache_key.1) + .await? + else { + self.negative_cache.insert(cache_key, ()).await; + return Ok(None); + }; + + let routing = OrgRouting { + self_managed: row.self_managed, + clickhouse_ready: row.clickhouse_ready, + }; + let (source, source_pause) = PullSourceRow::stamped(row.source); + let identity = AwsConnectionIdentity { + org_id: row.org_id, + secret_key_id: cache_key.1.chars().take(16).collect(), + source, + source_pause, + }; + + self.cache.insert(cache_key, identity.clone()).await; + self.routing + .remember_org_routing(&identity.org_id, routing.clone()) + .await; + + Ok(Some(identity.into_resolved(&routing))) + } + + /// Writes `stream_last_received_at` (`error == None`) or `stream_last_error`, + /// at most once per connection and outcome per interval. Best-effort, like + /// `GcpConnectorResolver::record_health`: a written failure lets the next + /// success through so the error does not outlive the retried delivery. + async fn record_health(&self, connection_id: &str, error: Option<&str>) { + let throttle_key = (connection_id.to_owned(), error.is_none()); + let throttle = self.recent_health_writes.entry(throttle_key).or_insert(()); + if !throttle.await.is_fresh() { + return; + } + if error.is_some() { + self.recent_health_writes + .invalidate(&(connection_id.to_owned(), true)) + .await; + } + if let Err(write_error) = self + .store + .record_aws_stream_health(connection_id, error, current_time_millis()) + .await + { + debug!( + connection_id, + error = write_error, + "Failed to record AWS stream health" + ); + } + } +} + +impl SamplingPolicyResolver { + #[hotpath::measure] + async fn resolve_policy(&self, org_id: &str) -> SamplingPolicy { + if let Some(policy) = self.cache.get(org_id).await { return policy; } @@ -8406,6 +8971,87 @@ impl KeyStore for PostgresKeyStore { .map(|_| ()) .map_err(|error| format!("postgres record_gcp_setup_report failed: {error}")) } + + async fn fetch_aws_connection( + &self, + connection_id: &str, + secret_hash: &str, + ) -> Result, String> { + let revision = required_schema_revision(); + let client = self.client().await?; + let sql = format!( + "SELECT c.org_id, \ + COALESCE(s.sync_status = 'connected', false) AS self_managed, \ + COALESCE(s.sync_status = 'connected' AND {SCHEMA_REVISION_COMPATIBLE_SQL}, false) AS clickhouse_ready, \ + {CONNECTOR_SOURCE_COLUMNS} \ + FROM aws_connections c \ + LEFT JOIN org_clickhouse_settings s ON s.org_id = c.org_id \ + {CONNECTOR_SOURCE_JOIN} \ + WHERE c.id = $2 AND c.stream_secret_hash = $3 LIMIT 1" + ); + let rows = client + .query(&sql, &[&revision, &connection_id, &secret_hash]) + .instrument(postgres_client_span( + "fetch_aws_connection", + "SELECT", + AWS_CONNECTIONS_TABLE, + &self.target, + )) + .await + .map_err(|error| format!("postgres fetch_aws_connection failed: {error}"))?; + Ok(rows.first().map(|row| AwsConnectionRow { + org_id: row.get("org_id"), + self_managed: row.get("self_managed"), + clickhouse_ready: row.get("clickhouse_ready"), + source: connector_source(row), + })) + } + + async fn record_aws_stream_health( + &self, + connection_id: &str, + error: Option<&str>, + now_ms: i64, + ) -> Result<(), String> { + let client = self.client().await?; + // `updated_at` is left alone: it tracks the connection's settings, not its traffic. + let written = match error { + None => { + client + .execute( + "UPDATE aws_connections \ + SET stream_last_received_at = to_timestamp($1::bigint / 1000.0), \ + stream_last_error = NULL \ + WHERE id = $2", + &[&now_ms, &connection_id], + ) + .instrument(postgres_client_span( + "record_aws_stream_health", + "UPDATE", + AWS_CONNECTIONS_TABLE, + &self.target, + )) + .await + } + Some(error) => { + client + .execute( + "UPDATE aws_connections SET stream_last_error = $1 WHERE id = $2", + &[&error, &connection_id], + ) + .instrument(postgres_client_span( + "record_aws_stream_health", + "UPDATE", + AWS_CONNECTIONS_TABLE, + &self.target, + )) + .await + } + }; + written + .map(|_| ()) + .map_err(|err| format!("postgres record_aws_stream_health failed: {err}")) + } } // Local-dev / single-tenant KeyStore: every well-formed ingest key resolves to @@ -8542,6 +9188,23 @@ impl KeyStore for StaticKeyStore { ) -> Result<(), String> { Ok(()) } + + async fn fetch_aws_connection( + &self, + _connection_id: &str, + _secret_hash: &str, + ) -> Result, String> { + Ok(None) + } + + async fn record_aws_stream_health( + &self, + _connection_id: &str, + _error: Option<&str>, + _now_ms: i64, + ) -> Result<(), String> { + Ok(()) + } } fn infer_ingest_key_type(raw_key: &str) -> Option { @@ -9677,6 +10340,10 @@ mod tests { connector_failures: std::sync::Mutex>, /// (connector id, report) of every `record_gcp_setup_report` call. gcp_setup_reports: std::sync::Mutex>, + aws_connections: + std::sync::Mutex>, + /// (connection id, error) of every `record_aws_stream_health` call. + aws_stream_health: std::sync::Mutex)>>, routings: std::sync::Mutex>, targets: std::sync::Mutex>, ingest_key_fetches: AtomicU64, @@ -9738,6 +10405,26 @@ mod tests { .insert((connector_id.to_owned(), hash), row); } + fn insert_aws_connection( + &self, + connection_id: &str, + raw_secret: &str, + row: AwsConnectionRow, + ) { + let hash = hash_ingest_key(raw_secret, "test-hmac-key").unwrap(); + self.set_org_routing( + &row.org_id, + OrgRouting { + self_managed: row.self_managed, + clickhouse_ready: row.clickhouse_ready, + }, + ); + self.aws_connections + .lock() + .unwrap() + .insert((connection_id.to_owned(), hash), row); + } + fn set_org_routing(&self, org_id: &str, routing: OrgRouting) { self.routings .lock() @@ -9925,6 +10612,35 @@ mod tests { .push((connector_id.to_owned(), report)); Ok(()) } + async fn fetch_aws_connection( + &self, + connection_id: &str, + secret_hash: &str, + ) -> Result, String> { + self.connector_fetches.fetch_add(1, Ordering::Relaxed); + let row = self + .aws_connections + .lock() + .unwrap() + .get(&(connection_id.to_owned(), secret_hash.to_owned())) + .cloned(); + Ok(row.map(|row| AwsConnectionRow { + source: self.joined_source(connection_id, &row.org_id), + ..row + })) + } + async fn record_aws_stream_health( + &self, + connection_id: &str, + error: Option<&str>, + _now_ms: i64, + ) -> Result<(), String> { + self.aws_stream_health + .lock() + .unwrap() + .push((connection_id.to_owned(), error.map(str::to_owned))); + Ok(()) + } } fn make_routing_resolver(store: Arc, ttl: Duration) -> Arc { @@ -10234,11 +10950,17 @@ mod tests { routing: Arc::clone(&routing), }, gcp_resolver: GcpConnectorResolver::new( + Arc::clone(&key_store), + "test-hmac-key".to_owned(), + Arc::clone(&routing), + Duration::from_mins(1), + GooglePushAuth::with_keys(gcp_push_test_keys()), + ), + aws_resolver: AwsConnectionResolver::new( key_store, "test-hmac-key".to_owned(), routing, Duration::from_mins(1), - GooglePushAuth::with_keys(gcp_push_test_keys()), ), autumn_tracker: None, autumn_entitlements: None, @@ -11545,6 +12267,330 @@ mod tests { drop(std::fs::remove_dir_all(queue_dir)); } + const AWS_CONNECTION: &str = "6f1c2b9e-1d3a-4c5b-9e8f-0a1b2c3d4e5f"; + const AWS_STREAM_SECRET: &str = "aws-stream-secret"; + + async fn aws_firehose_state( + name: &str, + clickhouse_url: &str, + max_decoded_body_bytes: usize, + ) -> (Arc, Arc, PathBuf) { + let queue_dir = unique_main_test_dir(name); + let store = Arc::new(FakeKeyStore::default()); + store.insert_aws_connection( + AWS_CONNECTION, + AWS_STREAM_SECRET, + AwsConnectionRow { + org_id: "org_aws".to_owned(), + self_managed: true, + clickhouse_ready: true, + source: None, + }, + ); + store.insert_clickhouse_target( + "org_aws", + ClickHouseTargetRow { + ch_url: clickhouse_url.to_owned(), + ch_user: "ingest".to_owned(), + ch_password_ciphertext: None, + ch_password_iv: None, + ch_password_tag: None, + ch_database: "maple".to_owned(), + schema_version: CLICKHOUSE_SCHEMA_VERSION.to_owned(), + }, + ); + let mut state = test_app_state( + Arc::clone(&store), + queue_dir.clone(), + "http://127.0.0.1:1".to_owned(), + Duration::from_mins(1), + ) + .await; + state.config.max_decoded_body_bytes = max_decoded_body_bytes; + (Arc::new(state), store, queue_dir) + } + + /// One `AWS/ECS` `CPUUtilization` point as a CloudWatch metric stream writes it. + fn cloudwatch_cpu_request() -> ExportMetricsServiceRequest { + use opentelemetry_proto::tonic::common::v1::KeyValueList; + use opentelemetry_proto::tonic::metrics::v1::{ + metric, summary_data_point::ValueAtQuantile, Metric, ResourceMetrics, ScopeMetrics, + Summary, SummaryDataPoint, + }; + let string = |key: &str, value: &str| KeyValue { + key: key.to_owned(), + value: Some(AnyValue { + value: Some(any_value::Value::StringValue(value.to_owned())), + }), + ..Default::default() + }; + let point = SummaryDataPoint { + attributes: vec![ + string("Namespace", "AWS/ECS"), + string("MetricName", "CPUUtilization"), + KeyValue { + key: "Dimensions".to_owned(), + value: Some(AnyValue { + value: Some(any_value::Value::KvlistValue(KeyValueList { + values: vec![ + string("ClusterName", "prod"), + string("ServiceName", "checkout"), + ], + })), + }), + ..Default::default() + }, + ], + start_time_unix_nano: 1_800_000_000_000_000_000, + time_unix_nano: 1_800_000_060_000_000_000, + count: 4, + sum: 100.0, + quantile_values: vec![ + ValueAtQuantile { + quantile: 0.0, + value: 10.0, + }, + ValueAtQuantile { + quantile: 1.0, + value: 40.0, + }, + ], + flags: 0, + }; + ExportMetricsServiceRequest { + resource_metrics: vec![ResourceMetrics { + resource: Some(Resource { + attributes: vec![ + string("cloud.provider", "aws"), + string("cloud.account.id", "123456789012"), + string("cloud.region", "us-east-1"), + ], + ..Default::default() + }), + scope_metrics: vec![ScopeMetrics { + scope: None, + metrics: vec![Metric { + name: "amazonaws.com/AWS/ECS/CPUUtilization".to_owned(), + unit: "Percent".to_owned(), + data: Some(metric::Data::Summary(Summary { + data_points: vec![point], + })), + ..Default::default() + }], + schema_url: String::new(), + }], + schema_url: String::new(), + }], + } + } + + /// A Firehose delivery whose one record holds two length-delimited requests. + fn firehose_body(records: &[&str]) -> String { + let records: Vec = records + .iter() + .map(|data| serde_json::json!({ "data": data })) + .collect(); + serde_json::json!({ + "requestId": "body-request-id", + "timestamp": 1_800_000_000_000_i64, + "records": records, + }) + .to_string() + } + + fn cloudwatch_record() -> String { + let mut data = cloudwatch_cpu_request().encode_length_delimited_to_vec(); + data.extend(cloudwatch_cpu_request().encode_length_delimited_to_vec()); + STANDARD.encode(data) + } + + async fn aws_deliver( + state: &Arc, + connection_id: &str, + secret: Option<&str>, + body: Bytes, + gzip: bool, + ) -> (StatusCode, JsonValue) { + let mut headers = HeaderMap::new(); + headers.insert( + FIREHOSE_REQUEST_ID_HEADER, + HeaderValue::from_static("header-request-id"), + ); + if let Some(secret) = secret { + headers.insert( + FIREHOSE_ACCESS_KEY_HEADER, + HeaderValue::from_str(secret).unwrap(), + ); + } + if gzip { + headers.insert(CONTENT_ENCODING, HeaderValue::from_static("gzip")); + } + let response = handle_aws_firehose( + State(Arc::clone(state)), + Path(connection_id.to_owned()), + headers, + body, + ) + .await; + let status = response.status(); + let bytes = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + (status, serde_json::from_slice(&bytes).unwrap()) + } + + #[tokio::test] + async fn aws_firehose_rejects_bad_credentials_without_a_lookup_for_malformed_ids() { + let (state, store, queue_dir) = + aws_firehose_state("aws-firehose-auth", "http://127.0.0.1:1", 8 * 1024 * 1024).await; + let body = || Bytes::from(firehose_body(&[])); + + for (case, connection_id) in [ + ("uppercase uuid", "6F1C2B9E-1D3A-4C5B-9E8F-0A1B2C3D4E5F"), + ("not a uuid", "aws_conn_1"), + ("path-like", "../6f1c2b9e-1d3a-4c5b-9e8f-0a1b2c3d4e"), + ] { + let (status, response) = aws_deliver( + &state, + connection_id, + Some(AWS_STREAM_SECRET), + body(), + false, + ) + .await; + assert_eq!(status, StatusCode::UNAUTHORIZED, "{case}"); + assert_eq!(response["requestId"], "header-request-id", "{case}"); + assert!(response["errorMessage"].is_string(), "{case}"); + } + assert_eq!(store.connector_fetches.load(Ordering::Relaxed), 0); + + for (case, secret) in [("missing key", None), ("wrong key", Some("wrong-secret"))] { + let (status, _) = aws_deliver(&state, AWS_CONNECTION, secret, body(), false).await; + assert_eq!(status, StatusCode::UNAUTHORIZED, "{case}"); + } + let (status, _) = aws_deliver( + &state, + "00000000-0000-4000-8000-000000000000", + Some(AWS_STREAM_SECRET), + body(), + false, + ) + .await; + assert_eq!(status, StatusCode::UNAUTHORIZED); + assert!(store.aws_stream_health.lock().unwrap().is_empty()); + + drop(std::fs::remove_dir_all(queue_dir)); + } + + #[tokio::test] + async fn aws_firehose_stores_a_gzipped_metric_stream_and_acks_an_unreadable_record() { + let (ch_url, mut ch_rx) = spawn_fake_clickhouse().await; + let (state, store, queue_dir) = + aws_firehose_state("aws-firehose-accept", &ch_url, 8 * 1024 * 1024).await; + + let body = gzip_bytes(&firehose_body(&[ + &cloudwatch_record(), + &cloudwatch_record(), + ])); + let (status, response) = + aws_deliver(&state, AWS_CONNECTION, Some(AWS_STREAM_SECRET), body, true).await; + assert_eq!(status, StatusCode::OK, "{response}"); + assert_eq!(response["requestId"], "body-request-id"); + assert!(response["timestamp"].is_i64()); + assert!(response.get("errorMessage").is_none()); + + let mut stored = Vec::new(); + while let Ok(Some(import)) = + tokio::time::timeout(Duration::from_secs(2), ch_rx.recv()).await + { + stored.push(import); + if stored.len() == 2 { + break; + } + } + let gauges = stored + .iter() + .find(|import| import.query.starts_with("INSERT INTO metrics_gauge")) + .expect("the average, min and max gauges should be stored"); + assert!( + gauges.body.contains("aws.ecs.cpu_utilization.max"), + "{}", + gauges.body + ); + assert!(gauges.body.contains("checkout"), "{}", gauges.body); + let sums = stored + .iter() + .find(|import| import.query.starts_with("INSERT INTO metrics_sum")) + .expect("the sum and count should be stored"); + assert!( + sums.body.contains("aws.ecs.cpu_utilization.count"), + "{}", + sums.body + ); + assert_eq!( + *store.aws_stream_health.lock().unwrap(), + [(AWS_CONNECTION.to_owned(), None)] + ); + + // Bad base64 never decodes, so it is acked rather than retried by Firehose. + // Sent twice: the `stream_last_error` write is throttled to one. + for _ in 0..2 { + let (status, _) = aws_deliver( + &state, + AWS_CONNECTION, + Some(AWS_STREAM_SECRET), + Bytes::from(firehose_body(&["!!not base64!!"])), + false, + ) + .await; + assert_eq!(status, StatusCode::OK); + } + assert_eq!( + *store.aws_stream_health.lock().unwrap(), + [ + (AWS_CONNECTION.to_owned(), None), + ( + AWS_CONNECTION.to_owned(), + Some(aws_metric_stream::UNREADABLE_DELIVERY.to_owned()) + ), + ] + ); + assert!( + tokio::time::timeout(Duration::from_millis(50), ch_rx.recv()) + .await + .is_err(), + "an unreadable record must not be stored" + ); + + drop(std::fs::remove_dir_all(queue_dir)); + } + + #[tokio::test] + async fn aws_firehose_refuses_a_delivery_that_decodes_past_the_limit() { + let (state, store, queue_dir) = + aws_firehose_state("aws-firehose-too-large", "http://127.0.0.1:1", 64).await; + + let (status, response) = aws_deliver( + &state, + AWS_CONNECTION, + Some(AWS_STREAM_SECRET), + Bytes::from(firehose_body(&[&cloudwatch_record()])), + false, + ) + .await; + assert_eq!(status, StatusCode::PAYLOAD_TOO_LARGE); + assert!(response["errorMessage"].is_string()); + assert_eq!( + *store.aws_stream_health.lock().unwrap(), + [( + AWS_CONNECTION.to_owned(), + Some(aws_metric_stream::DELIVERY_TOO_LARGE.to_owned()) + )] + ); + + drop(std::fs::remove_dir_all(queue_dir)); + } + #[tokio::test] async fn gcp_logpush_rejects_bad_credentials() { let (state, _store, queue_dir) = diff --git a/apps/landing/src/content/docs/integrations/aws.md b/apps/landing/src/content/docs/integrations/aws.md index f2a96336fb..cde6d28fd6 100644 --- a/apps/landing/src/content/docs/integrations/aws.md +++ b/apps/landing/src/content/docs/integrations/aws.md @@ -7,7 +7,7 @@ order: 11 The AWS integration connects one AWS account to Maple through an IAM role in that account. Maple assumes the role from its own AWS identity, and the role trusts that identity only with an **external ID** Maple minted for your connection, so another Maple organization can't assume it. Maple stores no access key, secret or session for your account. -Maple checks each connection every hour, and whenever you select **Check connection**. The check assumes the role and reads the role's own tags and attached policies. Nothing else is read yet. Metrics, a resource inventory, a security scan and cost each come as a **module**: a policy of its own on the same role, added by deploying the stack again. +Maple checks each connection every hour, and whenever you select **Check connection**. The check assumes the role and reads the role's own tags and attached policies. CloudWatch metrics can be streamed to Maple as well (see below). A resource inventory, a security scan and cost each come as a **module**: a policy of its own on the same role, added by deploying the stack again. ## Before you start @@ -43,6 +43,23 @@ Choose **Terraform** in the setup and copy the file into your repository. It nee The role is tagged with the setup version. When a later version of the setup changes the role, the connection shows **Update the stack**. Deploy it again from the setup to apply the change. +## CloudWatch metrics + +A connection can stream CloudWatch metrics to Maple. Choose the regions and the services (CloudWatch namespaces) to send, then run the setup script again. In each region it deploys a stack named `maple-metrics-` with: + +| Resource | Purpose | +| ------------------------- | --------------------------------------------------------------------------------------------- | +| CloudWatch metric stream | Sends the chosen namespaces every minute, in the OpenTelemetry 1.0 format. | +| Firehose delivery stream | Posts the metrics to Maple. It sends a secret only this connection holds, so Maple knows whose they are. | +| Two IAM roles | Let the metric stream write to the Firehose, and the Firehose to its bucket. | +| S3 bucket | Keeps batches Maple could not accept, and deletes them after 7 days. | + +Maple reads nothing back from your account for this: AWS pushes the metrics. The first ones arrive a few minutes after the stack finishes. + +AWS bills the metric stream at $0.003 per 1,000 metric updates, and a metric updates once a minute, so 100 metrics cost about $13 a month. Firehose adds a few cents per gigabyte. Stream only the namespaces you need. + +To stop streaming, turn the metric stream off in Maple and run the setup script again: it removes the stack from every region. The cleanup script removes it too. + ## Troubleshooting | What you see | What to do | diff --git a/apps/web/src/page/integrations/aws.scene.test.ts b/apps/web/src/page/integrations/aws.scene.test.ts index 5e125a9a47..b2502cfcab 100644 --- a/apps/web/src/page/integrations/aws.scene.test.ts +++ b/apps/web/src/page/integrations/aws.scene.test.ts @@ -33,6 +33,9 @@ const connection = (overrides: Partial = {}): V2AwsConnection = verified_at: null, last_checked_at: null, last_check_error: null, + metric_stream: null, + metrics_last_received_at: null, + metrics_last_error: null, created_at: Schema.decodeSync(Timestamp)("2026-10-21T12:00:00.000Z"), ...overrides, }) @@ -131,6 +134,8 @@ describe("aws card", () => { cleanup_script: "first", cloudformation_template: "{}", terraform: "", + metrics_stack_name: null, + metrics_cloudformation_template: null, }), }), ).model diff --git a/apps/web/src/page/sources/detail/detail.scene.test.ts b/apps/web/src/page/sources/detail/detail.scene.test.ts index 8f383dfb24..44f4486f8b 100644 --- a/apps/web/src/page/sources/detail/detail.scene.test.ts +++ b/apps/web/src/page/sources/detail/detail.scene.test.ts @@ -431,6 +431,9 @@ const awsConnection = (id: AwsConnectionId): V2AwsConnection => verified_at: null, last_checked_at: "2026-10-01T11:59:00.000Z", last_check_error: "Maple cannot assume the role yet.", + metric_stream: null, + metrics_last_received_at: null, + metrics_last_error: null, created_at: "2026-10-01T11:00:00.000Z", }) @@ -483,11 +486,33 @@ describe("integration focus", () => { }), ) expect(names(polled.commands)).not.toContain("FetchSourceDetailList") - // A click while that poll was in flight made it the user's: its answer re-reads the header. - const clicked = pageUpdate( + // A click while a poll is in flight makes it the user's: its answer re-reads the header. + const withStatus = pageUpdate( model, - Message.GotConnectionMessage({ message: IntegrationMessage.ClickedAwsCheck({ id: connection }) }), + Message.GotConnectionMessage({ + message: IntegrationMessage.CompletedLoadAws({ + key: model.connection?.statuses.aws.key ?? "", + result: Result.succeed({ + object: "aws_integration", + available: true, + modules: [], + connections: [awsConnection(connection)], + }), + }), + }), ).model + const polling = pageUpdate( + withStatus, + Message.GotConnectionMessage({ message: IntegrationMessage.TickedAwsPending() }), + ) + expect(names(polling.commands)).toContain("CheckAwsConnection") + const clicking = pageUpdate( + polling.model, + Message.GotConnectionMessage({ message: IntegrationMessage.ClickedAwsCheck({ id: connection }) }), + ) + // The poll in flight answers for the click: no second call to AWS. + expect(names(clicking.commands)).not.toContain("CheckAwsConnection") + const clicked = clicking.model const answered = pageUpdate( clicked, Message.GotConnectionMessage({ diff --git a/packages/backend/src/services/integrations/AwsConnectionService.test.ts b/packages/backend/src/services/integrations/AwsConnectionService.test.ts index 06ea608d7b..7023065ddb 100644 --- a/packages/backend/src/services/integrations/AwsConnectionService.test.ts +++ b/packages/backend/src/services/integrations/AwsConnectionService.test.ts @@ -2,7 +2,8 @@ import { afterEach, assert, describe, it } from "@effect/vitest" import { ConfigProvider, Effect, Layer, Schema } from "effect" import { TestClock } from "effect/testing" import { HttpClient } from "effect/http" -import { AwsAccountId, OrgId, UserId } from "@maple/domain/primitives" +import { hashIngestKey } from "@maple/db" +import { AwsAccountId, AwsRegion, OrgId, UserId } from "@maple/domain/primitives" import { Env } from "@maple/backend/platform/Env" import { cleanupTestDbs, @@ -211,6 +212,82 @@ describe("AwsConnectionService", () => { }).pipe(Effect.provide(makeLayer(testDb, aws))) }) + it.effect("streams metrics with a secret only this connection's row can verify", () => { + const testDb = createTestDb(trackedDbs) + const aws = makeFakeAws() + const regions = ["us-east-1", "eu-central-1"].map((region) => Schema.decodeUnknownSync(AwsRegion)(region)) + const streamSecretHash = (id: string) => + Effect.promise(() => + queryFirstRow<{ stream_secret_hash: string | null }>( + testDb, + "SELECT stream_secret_hash FROM aws_connections WHERE id = $1", + [id], + ), + ).pipe(Effect.map((row) => row?.stream_secret_hash)) + const accessKeyOf = (script: string) => /AccessKey=(maple_aws_[\w-]+)/u.exec(script)?.[1] + return Effect.gen(function* () { + const service = yield* AwsConnectionService + const created = yield* service.create(orgId, userId, { + accountId: account, + modules: [], + metricStream: { regions, namespaces: ["AWS/ECS", "AWS/ApplicationELB"] }, + }) + const setup = yield* service.setup(orgId, created.id) + const accessKey = accessKeyOf(setup.setupScript) + assert.isDefined(accessKey) + assert.strictEqual(yield* streamSecretHash(created.id), hashIngestKey(accessKey!, "maple-test-lookup-secret")) + assert.include(setup.setupScript, "for region in us-east-1 eu-central-1; do") + + const template = JSON.parse(setup.metricsCloudformationTemplate ?? "{}") + assert.deepStrictEqual(template.Resources.MetricStream.Properties.IncludeFilters, [ + { Namespace: "AWS/ECS" }, + { Namespace: "AWS/ApplicationELB" }, + ]) + assert.strictEqual(template.Resources.MetricStream.Properties.OutputFormat, "opentelemetry1.0") + const endpoint = template.Resources.DeliveryStream.Properties.HttpEndpointDestinationConfiguration + .EndpointConfiguration + assert.strictEqual(endpoint.Url, `https://ingest.test/v1/firehose/aws/${created.id}`) + // The secret is a parameter the script passes, never part of the template. + assert.deepStrictEqual(endpoint.AccessKey, { Ref: "AccessKey" }) + assert.isTrue(template.Parameters.AccessKey.NoEcho) + assert.notInclude(setup.metricsCloudformationTemplate ?? "", accessKey!) + + // Turning the stream off leaves a setup that only removes its stacks. + const off = yield* service.update(orgId, created.id, { metricStream: null }) + assert.isNull(off.metricStream) + const removed = yield* service.setup(orgId, created.id) + assert.isNull(removed.metricsStackName) + assert.isUndefined(accessKeyOf(removed.setupScript)) + assert.include(removed.setupScript, "describe-regions") + }).pipe(Effect.provide(makeLayer(testDb, aws))) + }) + + it.effect("mints a stream secret once for a connection made before streams", () => { + const testDb = createTestDb(trackedDbs) + const aws = makeFakeAws() + const region = Schema.decodeUnknownSync(AwsRegion)("us-east-1") + return Effect.gen(function* () { + const service = yield* AwsConnectionService + const created = yield* service.create(orgId, userId, { accountId: account, modules: [] }) + yield* Effect.promise(() => + executeSql( + testDb, + `UPDATE aws_connections SET stream_secret_ciphertext = NULL, stream_secret_iv = NULL, + stream_secret_tag = NULL, stream_secret_hash = NULL WHERE id = $1`, + [created.id], + ), + ) + yield* service.update(orgId, created.id, { + metricStream: { regions: [region], namespaces: ["AWS/Lambda"] }, + }) + const first = yield* service.setup(orgId, created.id) + const again = yield* service.setup(orgId, created.id) + const keyOf = (script: string) => /AccessKey=(maple_aws_[\w-]+)/u.exec(script)?.[1] + assert.isDefined(keyOf(first.setupScript)) + assert.strictEqual(keyOf(again.setupScript), keyOf(first.setupScript)) + }).pipe(Effect.provide(makeLayer(testDb, aws))) + }) + it.effect("is unavailable without Maple's AWS identity", () => { const testDb = createTestDb(trackedDbs) const aws = makeFakeAws() @@ -378,7 +455,8 @@ describe("AwsConnectionService", () => { assert.strictEqual(first.due, 80) assert.isAbove(first.checked, 0) assert.isBelow(first.checked, 80) - assert.strictEqual(aws.assumed.length, first.checked) + // At most once per check: a call that sees other calls' 9s jumps can time out unassumed. + assert.isAtMost(aws.assumed.length, first.checked) aws.beforeEachCall = Effect.void yield* TestClock.adjust("1 hour") assert.strictEqual((yield* service.checkDue()).checked, 80) diff --git a/packages/backend/src/services/integrations/AwsConnectionService.ts b/packages/backend/src/services/integrations/AwsConnectionService.ts index aad50f733d..76e1c6e8e7 100644 --- a/packages/backend/src/services/integrations/AwsConnectionService.ts +++ b/packages/backend/src/services/integrations/AwsConnectionService.ts @@ -1,5 +1,6 @@ import { randomBytes, randomUUID } from "node:crypto" import * as PG from "@maple-dev/effect-orm/postgres" +import { hashIngestKey, parseIngestKeyLookupHmacKey } from "@maple/db" import { AwsConnections, type AwsConnectionRow, Sources } from "@maple/db/tables" import { AWS_MODULE_SPECS, @@ -8,6 +9,7 @@ import { awsConnectionResourceNames, awsModuleOfPolicyName, awsRoleArn, + type AwsMetricStream, awsSetupPlan, isAwsModuleAvailable, parseAwsMaplePrincipal, @@ -27,8 +29,9 @@ import { type OrgId, type UserId, } from "@maple/domain/primitives" -import { Cause, Clock, Context, Effect, Layer, Option } from "effect" +import { Cause, Clock, Context, Effect, Layer, Option, Redacted } from "effect" import { FetchHttpClient, HttpClient } from "effect/http" +import { decryptAes256Gcm, encryptAes256Gcm, parseBase64Aes256GcmKey } from "@maple/backend/platform/Crypto" import { Database } from "@maple/backend/platform/DatabaseLive" import { makeDbExecute, makePersistenceErrorMapper } from "@maple/backend/platform/db-execute" import { Env } from "@maple/backend/platform/Env" @@ -56,6 +59,11 @@ export interface AwsConnection { readonly verifiedAt: number | null readonly lastCheckedAt: number | null readonly lastCheckError: string | null + /** Null: no metric stream asked for. */ + readonly metricStream: AwsMetricStream | null + /** The last metric delivery the receiver accepted, and why the last one failed. */ + readonly metricsLastReceivedAt: number | null + readonly metricsLastError: string | null readonly createdAt: number } @@ -73,6 +81,9 @@ export interface AwsSetup { readonly cleanupScript: string readonly cloudformationTemplate: string readonly terraform: string + /** The metric stream's stack, deployed to each of its regions. Null without a stream. */ + readonly metricsStackName: string | null + readonly metricsCloudformationTemplate: string | null } export interface AwsCheckSweep { @@ -96,7 +107,12 @@ export interface AwsConnectionServiceApi { readonly create: ( orgId: OrgId, userId: UserId, - input: { readonly accountId: AwsAccountId; readonly modules: ReadonlyArray }, + input: { + readonly accountId: AwsAccountId + readonly modules: ReadonlyArray + /** Omitted: no metric stream. */ + readonly metricStream?: AwsMetricStream | null | undefined + }, ) => Effect.Effect< AwsConnection, | AwsAccountAlreadyConnectedError @@ -108,7 +124,11 @@ export interface AwsConnectionServiceApi { readonly update: ( orgId: OrgId, connectionId: AwsConnectionId, - patch: { readonly modules?: ReadonlyArray | undefined }, + patch: { + readonly modules?: ReadonlyArray | undefined + /** Null turns the stream off; undefined leaves it. */ + readonly metricStream?: AwsMetricStream | null | undefined + }, ) => Effect.Effect readonly setup: ( orgId: OrgId, @@ -186,10 +206,17 @@ const toConnection = (row: AwsConnectionRow): AwsConnection => { verifiedAt: row.verifiedAt, lastCheckedAt: row.lastCheckedAt, lastCheckError: row.lastCheckError, + metricStream: row.metricStream, + metricsLastReceivedAt: row.streamLastReceivedAt, + metricsLastError: row.streamLastError, createdAt: row.createdAt, } } +// Binds the ciphertext to its row, so a stored secret cannot be moved onto another connection. +const streamSecretAad = (connectionId: string) => + Buffer.from(`aws_connections:stream:v1:${connectionId}`, "utf8") + const MODULE_INFO: ReadonlyArray = AWS_MODULES.map((module) => ({ module, title: AWS_MODULE_SPECS[module].title, @@ -303,6 +330,35 @@ export class AwsConnectionService extends Context.Service new IntegrationsPersistenceError({ message }) + const encryptionKey = yield* parseBase64Aes256GcmKey( + Redacted.value(env.MAPLE_INGEST_KEY_ENCRYPTION_KEY), + persistence, + ) + // The ingest gateway hashes a delivery's access key with this and looks the row up by it. + const lookupHmacKey = yield* parseIngestKeyLookupHmacKey( + Redacted.value(env.MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY), + persistence, + ) + const ingestBaseUrl = env.MAPLE_INGEST_PUBLIC_URL.replace(/\/+$/, "") + + /** A new stream secret as the four columns that hold it. */ + const mintStreamSecret = (connectionId: AwsConnectionId) => + Effect.gen(function* () { + const secret = `maple_aws_${randomBytes(24).toString("base64url")}` + const encrypted = yield* encryptAes256Gcm( + secret, + encryptionKey, + persistence, + streamSecretAad(connectionId), + ) + return { + streamSecretCiphertext: encrypted.ciphertext, + streamSecretIv: encrypted.iv, + streamSecretTag: encrypted.tag, + streamSecretHash: hashIngestKey(secret, lookupHmacKey), + } + }) // All three or nothing: a principal without a key could hand out setups nothing checks. const maple = Option.all({ @@ -361,12 +417,17 @@ export class AwsConnectionService extends Context.Service }, + input: { + readonly accountId: AwsAccountId + readonly modules: ReadonlyArray + readonly metricStream?: AwsMetricStream | null | undefined + }, ) { yield* Effect.annotateCurrentSpan({ orgId, "maple.aws.account_id": input.accountId }) if (maple === undefined) return yield* unavailable() const modules = yield* validateModules(input.modules) const id = AwsConnectionId.make(randomUUID()) + const streamSecret = yield* mintStreamSecret(id) const now = yield* Clock.currentTimeMillis // The connection and the source it sends as land together. const [row] = yield* dbExecute((db) => @@ -387,6 +448,8 @@ export class AwsConnectionService extends Context.Service | undefined }, + patch: { + readonly modules?: ReadonlyArray | undefined + readonly metricStream?: AwsMetricStream | null | undefined + }, ) { yield* Effect.annotateCurrentSpan({ orgId, "maple.aws.connection_id": connectionId }) const modules = patch.modules === undefined ? undefined : yield* validateModules(patch.modules) - if (modules === undefined) return toConnection(yield* selectRow(orgId, connectionId)) + const changes = { + ...(modules !== undefined && { modules }), + ...(patch.metricStream !== undefined && { metricStream: patch.metricStream }), + } + if (Object.keys(changes).length === 0) return toConnection(yield* selectRow(orgId, connectionId)) const updatedAt = yield* Clock.currentTimeMillis const [row] = yield* dbExecute((db) => db.run( PG.update(AwsConnections) - .set({ modules, updatedAt }) + .set({ ...changes, updatedAt }) .where(($) => [$.orgId.eq(orgId), $.id.eq(connectionId)]) .returning(), ), @@ -431,6 +501,38 @@ export class AwsConnectionService extends Context.Service + Effect.gen(function* () { + const minted = yield* mintStreamSecret(row.id) + // A concurrent setup may mint first: whichever secret landed is the one to use. + const [updated] = yield* dbExecute((db) => + db.run( + PG.update(AwsConnections) + .set(minted) + .where(($) => [$.id.eq(row.id), $.streamSecretHash.isNull()]) + .returning(), + ), + ) + return updated ?? (yield* selectRow(row.orgId, row.id)) + }) + + /** The plaintext stream secret the account's Firehose sends. */ + const streamAccessKey = (row: AwsConnectionRow) => + Effect.gen(function* () { + const held = row.streamSecretHash === null ? yield* withStreamSecret(row) : row + const { streamSecretCiphertext, streamSecretIv, streamSecretTag } = held + if (streamSecretCiphertext === null || streamSecretIv === null || streamSecretTag === null) { + return yield* persistence("The AWS connection has no stream secret") + } + return yield* decryptAes256Gcm( + { ciphertext: streamSecretCiphertext, iv: streamSecretIv, tag: streamSecretTag }, + encryptionKey, + () => persistence("Failed to decrypt the AWS stream secret"), + streamSecretAad(row.id), + ) + }) + const setup = Effect.fn("AwsConnectionService.setup")(function* ( orgId: OrgId, connectionId: AwsConnectionId, @@ -445,7 +547,15 @@ export class AwsConnectionService extends Context.Service diff --git a/packages/backend/src/services/integrations/aws/setup.ts b/packages/backend/src/services/integrations/aws/setup.ts index 85e787f489..0d35e88e54 100644 --- a/packages/backend/src/services/integrations/aws/setup.ts +++ b/packages/backend/src/services/integrations/aws/setup.ts @@ -1,4 +1,4 @@ -import { AWS_TEMPLATE_VERSION, type AwsSetupPlan } from "@maple/domain/aws" +import { AWS_TEMPLATE_VERSION, type AwsMetricStream, type AwsSetupPlan } from "@maple/domain/aws" import type { AwsAccountId, AwsConnectionId } from "@maple/domain/primitives" import { Effect } from "effect" import { forPaste } from "../paste-script" @@ -56,9 +56,168 @@ export const renderAwsCloudFormation = (connectionId: AwsConnectionId, plan: Aws return `${JSON.stringify(template, null, 2)}\n` } +/** A connection's metric stream as its setup renders it. */ +export interface AwsStreamTarget extends AwsMetricStream { + /** The ingest URL the Firehose posts to, and the secret it sends as its access key. */ + readonly endpoint: string + readonly accessKey: string +} + +/** + * One region's metric stream: CloudWatch streams that region's metrics to a Firehose, which posts + * them to Maple. Failed batches land in a bucket that empties itself after 7 days. + */ +export const renderAwsMetricsCloudFormation = ( + connectionId: AwsConnectionId, + streamName: string, + stream: Pick, +) => { + const template = { + AWSTemplateFormatVersion: "2010-09-09", + Description: `Maple integration ${connectionId}: streams CloudWatch metrics of this region to Maple. ${DOCS_URL}`, + Parameters: { + AccessKey: { + Type: "String", + NoEcho: true, + MinLength: 1, + Description: "Sent with every delivery so Maple knows which connection it is for", + }, + }, + Resources: { + FailedDeliveries: { + Type: "AWS::S3::Bucket", + Properties: { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { ServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" } }, + ], + }, + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + OwnershipControls: { Rules: [{ ObjectOwnership: "BucketOwnerEnforced" }] }, + LifecycleConfiguration: { + Rules: [{ Id: "expire-failed-deliveries", Status: "Enabled", ExpirationInDays: 7 }], + }, + }, + }, + FirehoseRole: { + Type: "AWS::IAM::Role", + Properties: { + Description: "Lets the Maple Firehose keep batches Maple could not accept", + AssumeRolePolicyDocument: { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "firehose.amazonaws.com" }, + Action: "sts:AssumeRole", + Condition: { StringEquals: { "aws:SourceAccount": { Ref: "AWS::AccountId" } } }, + }, + ], + }, + Policies: [ + { + PolicyName: "failed-deliveries", + PolicyDocument: { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Action: [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + ], + Resource: [ + { "Fn::GetAtt": ["FailedDeliveries", "Arn"] }, + { "Fn::Sub": "${FailedDeliveries.Arn}/*" }, + ], + }, + ], + }, + }, + ], + }, + }, + DeliveryStream: { + Type: "AWS::KinesisFirehose::DeliveryStream", + Properties: { + DeliveryStreamName: streamName, + DeliveryStreamType: "DirectPut", + HttpEndpointDestinationConfiguration: { + EndpointConfiguration: { Name: "Maple", Url: stream.endpoint, AccessKey: { Ref: "AccessKey" } }, + RequestConfiguration: { ContentEncoding: "GZIP" }, + BufferingHints: { IntervalInSeconds: 60, SizeInMBs: 1 }, + RetryOptions: { DurationInSeconds: 3600 }, + RoleARN: { "Fn::GetAtt": ["FirehoseRole", "Arn"] }, + S3BackupMode: "FailedDataOnly", + S3Configuration: { + BucketARN: { "Fn::GetAtt": ["FailedDeliveries", "Arn"] }, + RoleARN: { "Fn::GetAtt": ["FirehoseRole", "Arn"] }, + CompressionFormat: "GZIP", + Prefix: "failed/", + }, + }, + }, + }, + MetricStreamRole: { + Type: "AWS::IAM::Role", + Properties: { + Description: "Lets the Maple metric stream write to its Firehose", + AssumeRolePolicyDocument: { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "streams.metrics.cloudwatch.amazonaws.com" }, + Action: "sts:AssumeRole", + Condition: { StringEquals: { "aws:SourceAccount": { Ref: "AWS::AccountId" } } }, + }, + ], + }, + Policies: [ + { + PolicyName: "put-to-firehose", + PolicyDocument: { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Action: ["firehose:PutRecord", "firehose:PutRecordBatch"], + Resource: { "Fn::GetAtt": ["DeliveryStream", "Arn"] }, + }, + ], + }, + }, + ], + }, + }, + MetricStream: { + Type: "AWS::CloudWatch::MetricStream", + Properties: { + Name: streamName, + FirehoseArn: { "Fn::GetAtt": ["DeliveryStream", "Arn"] }, + RoleArn: { "Fn::GetAtt": ["MetricStreamRole", "Arn"] }, + OutputFormat: "opentelemetry1.0", + IncludeFilters: stream.namespaces.map((namespace) => ({ Namespace: namespace })), + }, + }, + }, + } + return `${JSON.stringify(template, null, 2)}\n` +} + export interface AwsScriptTarget { readonly accountId: AwsAccountId readonly stackName: string + readonly metricsStackName: string } // Every value interpolated below is pattern-checked (account id) or derived from a UUID (stack @@ -73,11 +232,57 @@ if [ "$account" != "${accountId}" ]; then fi ` +/** + * Deletes the metric stream stack from every enabled region not in `keep`. A stream turned off, or + * a region dropped, leaves a stack only a later setup or the cleanup knows to remove. + */ +const removeMetricsStacks = (stackName: string, keep: ReadonlyArray) => `keep=" ${keep.join(" ")} " +for region in $(aws ec2 describe-regions --region ${AWS_STACK_REGION} --query 'Regions[].RegionName' --output text); do + case "$keep" in *" $region "*) continue ;; esac + if aws cloudformation describe-stacks --region "$region" --stack-name ${stackName} >/dev/null 2>&1; then + echo "Removing the metric stream from $region..." + aws cloudformation delete-stack --region "$region" --stack-name ${stackName} + aws cloudformation wait stack-delete-complete --region "$region" --stack-name ${stackName} + fi +done +` + +/** Deploys the metric stream stack to each of the stream's regions. */ +const deployMetricsStacks = ( + stackName: string, + stream: AwsStreamTarget, + template: string, +) => `metrics_template="$(mktemp)" +cat > "$metrics_template" <<'MAPLE_METRICS_TEMPLATE' +${template}MAPLE_METRICS_TEMPLATE +for region in ${stream.regions.join(" ")}; do + status="$(aws cloudformation describe-stacks --region "$region" --stack-name ${stackName} --query 'Stacks[0].StackStatus' --output text 2>/dev/null || true)" + if [ "$status" = "ROLLBACK_COMPLETE" ]; then + aws cloudformation delete-stack --region "$region" --stack-name ${stackName} + aws cloudformation wait stack-delete-complete --region "$region" --stack-name ${stackName} + fi + echo "Deploying the metric stream to $region..." + aws cloudformation deploy \\ + --region "$region" \\ + --stack-name ${stackName} \\ + --template-file "$metrics_template" \\ + --capabilities CAPABILITY_IAM \\ + --parameter-overrides "AccessKey=${stream.accessKey}" \\ + --no-fail-on-empty-changeset +done +rm -f "$metrics_template" +` + /** * The script an administrator pastes into AWS CloudShell. It deploys the stack, which converges: a - * re-run after a module change updates the role and its policies in place. + * re-run after a module change updates the role and its policies in place. With a metric stream it + * then deploys the stream's stack to each of its regions. */ -export const renderAwsSetupScript = (target: AwsScriptTarget, template: string) => +export const renderAwsSetupScript = ( + target: AwsScriptTarget, + template: string, + metrics: { readonly stream: AwsStreamTarget; readonly template: string } | null, +) => forPaste( "AWS", "MAPLE_SETUP_SCRIPT", @@ -100,7 +305,7 @@ aws cloudformation deploy \\ --capabilities CAPABILITY_NAMED_IAM \\ --no-fail-on-empty-changeset rm -f "$template" -echo "Done. Go back to Maple and select Check connection. Details: ${DOCS_URL}" +${metrics === null ? "" : deployMetricsStacks(target.metricsStackName, metrics.stream, metrics.template)}${removeMetricsStacks(target.metricsStackName, metrics?.stream.regions ?? [])}echo "Done. Go back to Maple and select Check connection. Details: ${DOCS_URL}" `, ) @@ -109,7 +314,7 @@ export const renderAwsCleanupScript = (target: AwsScriptTarget) => "AWS", "MAPLE_CLEANUP_SCRIPT", `set -euo pipefail -${accountGuard(target.accountId)}echo "Deleting ${target.stackName} from account ${target.accountId}..." +${accountGuard(target.accountId)}${removeMetricsStacks(target.metricsStackName, [])}echo "Deleting ${target.stackName} from account ${target.accountId}..." aws cloudformation delete-stack --region ${AWS_STACK_REGION} --stack-name ${target.stackName} aws cloudformation wait stack-delete-complete --region ${AWS_STACK_REGION} --stack-name ${target.stackName} echo "Done. The role Maple used is gone." @@ -174,15 +379,22 @@ export const renderAwsSetup = Effect.fn("renderAwsSetup")(function* ( connectionId: AwsConnectionId, accountId: AwsAccountId, plan: AwsSetupPlan, + stream: AwsStreamTarget | null, ) { - const target = { accountId, stackName: plan.stackName } + const target = { accountId, stackName: plan.stackName, metricsStackName: plan.metricsStackName } const template = renderAwsCloudFormation(connectionId, plan) + const metrics = + stream === null + ? null + : { stream, template: renderAwsMetricsCloudFormation(connectionId, plan.metricStreamName, stream) } return { stackName: plan.stackName, stackRegion: AWS_STACK_REGION, - setupScript: yield* renderAwsSetupScript(target, template), + setupScript: yield* renderAwsSetupScript(target, template, metrics), cleanupScript: yield* renderAwsCleanupScript(target), cloudformationTemplate: template, terraform: renderAwsTerraform(connectionId, plan), + metricsStackName: metrics === null ? null : plan.metricsStackName, + metricsCloudformationTemplate: metrics?.template ?? null, } }) diff --git a/packages/db/drizzle/20261022120643_aws_metric_stream/migration.sql b/packages/db/drizzle/20261022120643_aws_metric_stream/migration.sql new file mode 100644 index 0000000000..b3ac15b20d --- /dev/null +++ b/packages/db/drizzle/20261022120643_aws_metric_stream/migration.sql @@ -0,0 +1,23 @@ +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "metric_stream" jsonb; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_secret_ciphertext" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_secret_iv" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_secret_tag" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_secret_hash" text; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_last_received_at" timestamp with time zone; +--> statement-breakpoint +-- metadata +ALTER TABLE "aws_connections" ADD COLUMN IF NOT EXISTS "stream_last_error" text; +--> statement-breakpoint +-- metadata +CREATE UNIQUE INDEX IF NOT EXISTS "aws_connections_stream_secret_hash_idx" ON "aws_connections" USING btree ("stream_secret_hash"); diff --git a/packages/db/drizzle/20261022120643_aws_metric_stream/snapshot.json b/packages/db/drizzle/20261022120643_aws_metric_stream/snapshot.json new file mode 100644 index 0000000000..db88e69d76 --- /dev/null +++ b/packages/db/drizzle/20261022120643_aws_metric_stream/snapshot.json @@ -0,0 +1,14610 @@ +{ + "version": "1", + "dialect": "postgres", + "id": "1114e1d68c7bb4181d44d075902b85031ca801449b37d35830e8f2c27ca6273d", + "prevIds": [ + "173c3bd875c5315ea12da7cf74a67aa25cf691fe7d494722eea5ed50b309bb49" + ], + "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_incidents", + "primaryKey": { + "name": "alert_incidents_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": "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": "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_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_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_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": "sources", + "primaryKey": { + "name": "sources_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": "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_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": "error_issue_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "created_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "updated_at", + "position": 26, + "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_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": "updated_at", + "position": 11, + "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": "tags_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "signal_type", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "comparator", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold", + "position": 13, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold_upper", + "position": 14, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "thresholds_json", + "position": 15, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "window_minutes", + "position": 16, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "minimum_sample_count", + "position": 17, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_breaches_required", + "position": 18, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "breaches_out_of", + "position": 19, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_healthy_required", + "position": 20, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "renotify_interval_minutes", + "position": 21, + "type": "integer", + "notNull": true, + "default": "30", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "apdex_threshold_ms", + "position": 22, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_builder_draft_json", + "position": 23, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "raw_query_sql", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "group_by", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "destination_ids_json", + "position": 26, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_spec_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "reducer", + "position": 28, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "sample_count_strategy", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "no_data_behavior", + "position": 30, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "last_scheduled_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_by", + "position": 34, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_by", + "position": 35, + "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": "external_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "modules", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "granted_modules", + "position": 6, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "template_version", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "verified_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_checked_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_check_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "check_lease_until", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream", + "position": 12, + "type": "jsonb", + "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": "posted_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "updated_at", + "position": 9, + "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": "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": "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_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": "created_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "updated_at", + "position": 33, + "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": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "expires_at", + "position": 8, + "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_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_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": "deleted_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "updated_at", + "position": 11, + "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": "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": "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": "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": "installed_by_user_id", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "updated_at", + "position": 15, + "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_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_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": "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_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_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_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_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": "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": "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": "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": "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": "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": "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/aws-connections.ts b/packages/db/src/tables/aws-connections.ts index 5c3013772c..fd85026028 100644 --- a/packages/db/src/tables/aws-connections.ts +++ b/packages/db/src/tables/aws-connections.ts @@ -1,11 +1,14 @@ import * as PG from "@maple-dev/effect-orm/postgres" +import { AwsMetricStream } from "@maple/domain/aws" import { AwsAccountId, AwsConnectionId, AwsModule, OrgId, SourceId } from "@maple/domain/primitives" import { Schema } from "effect" import { Sources } from "./sources" -// One row per connected AWS account. Maple holds no secret for it: the customer's stack creates a -// role (named from `id`) that trusts Maple's own identity with `external_id`. `modules` is what +// One row per connected AWS account. Maple holds no AWS secret for it: the customer's stack creates +// a role (named from `id`) that trusts Maple's own identity with `external_id`. `modules` is what // the connection asks for; the check writes what the role was found to grant. +// The stream secret is Maple's own: the account's Firehose sends it to `/v1/firehose/aws/{id}`, +// which looks the connection up by `id` + `stream_secret_hash`. export const AwsConnections = PG.table("aws_connections", { columns: { id: PG.brand(PG.text, AwsConnectionId), @@ -27,6 +30,18 @@ export const AwsConnections = PG.table("aws_connections", { lastCheckError: PG.column(PG.nullable(PG.text), { name: "last_check_error" }), // A check claims the row by moving this past now; it also orders the hourly sweep. checkLeaseUntil: PG.column(PG.nullable(PG.timestamptzMillis), { name: "check_lease_until" }), + // Null: no metric stream. The setup deploys one stack per region in it. + metricStream: PG.column(PG.nullable(PG.jsonb(AwsMetricStream)), { name: "metric_stream" }), + // Null on connections made before metric streams; minted when their setup is next read. + streamSecretCiphertext: PG.column(PG.nullable(PG.text), { name: "stream_secret_ciphertext" }), + streamSecretIv: PG.column(PG.nullable(PG.text), { name: "stream_secret_iv" }), + streamSecretTag: PG.column(PG.nullable(PG.text), { name: "stream_secret_tag" }), + streamSecretHash: PG.column(PG.nullable(PG.text), { name: "stream_secret_hash" }), + // Written by the receiver: the last delivery it accepted, and why the last one failed. + streamLastReceivedAt: PG.column(PG.nullable(PG.timestamptzMillis), { + name: "stream_last_received_at", + }), + streamLastError: PG.column(PG.nullable(PG.text), { name: "stream_last_error" }), createdBy: PG.column(PG.text, { name: "created_by" }), createdAt: PG.column(PG.timestamptzMillis, { name: "created_at" }), updatedAt: PG.column(PG.timestamptzMillis, { name: "updated_at" }), @@ -35,6 +50,7 @@ export const AwsConnections = PG.table("aws_connections", { indexes: [ PG.index("aws_connections_source_idx", ["sourceId"]), PG.uniqueIndex("aws_connections_org_account_idx", ["orgId", "accountId"]), + PG.uniqueIndex("aws_connections_stream_secret_hash_idx", ["streamSecretHash"]), ], foreignKeys: [ PG.foreignKey({ diff --git a/packages/domain/src/aws.ts b/packages/domain/src/aws.ts index 945698aa24..19c3c9336f 100644 --- a/packages/domain/src/aws.ts +++ b/packages/domain/src/aws.ts @@ -1,5 +1,11 @@ import { Schema } from "effect" -import { type AwsAccountId, type AwsConnectionId, AwsModule } from "./primitives" +import { + type AwsAccountId, + type AwsConnectionId, + AwsMetricNamespace, + AwsModule, + AwsRegion, +} from "./primitives" const isAwsModule = Schema.is(AwsModule) @@ -101,9 +107,22 @@ export const awsConnectionResourceNames = (connectionId: AwsConnectionId) => { modulePolicyName: (module: AwsModule) => `${name}-${module}`, /** What Maple names its sessions on the role, as CloudTrail shows them. */ sessionName: `maple-${hex}`, + /** The stack of one region's metric stream; the same name in every region it runs in. */ + metricsStackName: `maple-metrics-${hex}`, + metricStreamName: `maple-${hex}`, } } +/** + * A connection's CloudWatch metric stream. Streams are regional, so the setup deploys one stack + * per region, each sending these namespaces to Maple through Firehose. + */ +export const AwsMetricStream = Schema.Struct({ + regions: Schema.Array(AwsRegion).check(Schema.isMinLength(1), Schema.isMaxLength(20)), + namespaces: Schema.Array(AwsMetricNamespace).check(Schema.isMinLength(1)), +}) +export type AwsMetricStream = Schema.Schema.Type + export const awsRoleArn = (accountId: AwsAccountId, roleName: string) => `arn:aws:iam::${accountId}:role/${roleName}` @@ -151,6 +170,8 @@ interface IamDocument { export interface AwsSetupPlan { readonly roleName: string readonly stackName: string + readonly metricsStackName: string + readonly metricStreamName: string readonly roleArn: string readonly trustPolicy: IamDocument /** Lets the role read its own tags and attached policies: how Maple checks the setup. */ @@ -171,6 +192,8 @@ export const awsSetupPlan = (input: AwsSetupPlanInput): AwsSetupPlan => { return { roleName: names.roleName, stackName: names.stackName, + metricsStackName: names.metricsStackName, + metricStreamName: names.metricStreamName, roleArn, // Maple's account is the principal and the condition names the exact identity: an IAM user // recreated under the same name still matches, which a principal ARN alone would not. diff --git a/packages/domain/src/http/v2/integrations-aws.ts b/packages/domain/src/http/v2/integrations-aws.ts index b7419c5b9a..000998e64d 100644 --- a/packages/domain/src/http/v2/integrations-aws.ts +++ b/packages/domain/src/http/v2/integrations-aws.ts @@ -1,5 +1,6 @@ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/http-api" import { Schema } from "effect" +import { AwsMetricStream } from "../../aws" import { AwsAccountId, AwsConnectionId, AwsModule } from "../../primitives" import { AwsAccountAlreadyConnectedError, @@ -14,10 +15,10 @@ import { V2InsufficientPermissions } from "./errors" import { publicErrors } from "./public-error" import { PublicId, PublicIdPrefixes } from "./public-id" -// AWS integration. No OAuth and no stored secret: an org registers a connection for an account, an -// administrator deploys the generated CloudFormation stack, and Maple assumes the role it creates -// with an external id Maple minted. Each module is its own policy on the role. Public v2 from the -// start so scripted setups can use a scoped API key. +// AWS integration. No OAuth and no stored AWS secret: an org registers a connection for an account, +// an administrator deploys the generated CloudFormation stack, and Maple assumes the role it creates +// with an external id Maple minted. Each module is its own policy on the role; a metric stream is +// its own stack per region. Public v2 from the start so scripted setups can use a scoped API key. /** `awsc_…` public ID ⇄ internal `AwsConnectionId` (raw UUID). */ export const AwsConnectionPublicId = PublicId(PublicIdPrefixes.awsConnection, AwsConnectionId) @@ -35,9 +36,17 @@ const connectionExample = { verified_at: "2026-10-21T12:05:00.000Z", last_checked_at: "2026-10-21T13:00:00.000Z", last_check_error: null, + metric_stream: { regions: ["us-east-1"], namespaces: ["AWS/ECS", "AWS/ApplicationELB"] }, + metrics_last_received_at: "2026-10-21T13:04:00.000Z", + metrics_last_error: null, created_at: "2026-10-21T12:00:00.000Z", } as const +const metricStreamField = AwsMetricStream.annotate({ + description: + "Which regions stream which CloudWatch namespaces to Maple. Each region gets a metric stream and a Firehose, deployed by `setup`.", +}) + export const V2AwsConnection = Schema.Struct({ id: AwsConnectionPublicId, object: Schema.Literal("aws_connection").annotate({ @@ -84,6 +93,14 @@ export const V2AwsConnection = Schema.Struct({ description: "Why the latest check failed and what to do about it, or `null`.", examples: [null], }), + metric_stream: Schema.NullOr(metricStreamField), + metrics_last_received_at: Schema.NullOr(Timestamp).annotate({ + description: "When Maple last accepted metrics from the stream, or `null` before the first delivery.", + }), + metrics_last_error: Schema.NullOr(Schema.String).annotate({ + description: "Why the latest metric delivery was refused, or `null`.", + examples: [null], + }), created_at: Timestamp.annotate({ description: "When the connection was created." }), }).annotate({ identifier: "AwsConnection", @@ -140,6 +157,7 @@ const modulesField = (description: string) => export const V2AwsCreateConnectionRequest = Schema.Struct({ account_id: AwsAccountId.annotate({ description: "The 12-digit AWS account ID." }), modules: modulesField("What Maple may read. Defaults to none: the role then only lets Maple confirm the setup."), + metric_stream: Schema.optionalKey(Schema.NullOr(metricStreamField)), }).annotate({ identifier: "AwsCreateConnectionRequest", title: "AWS connection create request", @@ -149,6 +167,11 @@ export type V2AwsCreateConnectionRequest = Schema.Schema.Type +/** A commercial or GovCloud AWS region code, like `us-east-1` or `us-gov-west-1`. */ +export const AwsRegion = Schema.String.check(Schema.isPattern(/^[a-z]{2}(-gov)?-[a-z]+-[0-9]$/u)).pipe( + Schema.brand("@maple/AwsRegion"), + Schema.annotate({ identifier: "@maple/AwsRegion", title: "AWS Region" }), +) +export type AwsRegion = Schema.Schema.Type + +/** CloudWatch namespaces a connection's metric stream may send. */ +export const AWS_METRIC_NAMESPACES = [ + "AWS/ECS", + "ECS/ContainerInsights", + "AWS/ApplicationELB", + "AWS/NetworkELB", + "AWS/RDS", + "AWS/Lambda", + "AWS/SQS", + "AWS/EC2", + "AWS/DynamoDB", + "AWS/ApiGateway", +] as const +export const AwsMetricNamespace = Schema.Literals(AWS_METRIC_NAMESPACES) +export type AwsMetricNamespace = Schema.Schema.Type + /** What a Google Cloud connector covers: one project, or everything under a folder or organization. */ export const GcpScopeType = Schema.Literals(["project", "folder", "organization"]) export type GcpScopeType = Schema.Schema.Type From 2b7d4151f7ff7c8d494dfa7e014f866ad2c30118 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 14:27:04 +0200 Subject: [PATCH 02/10] fix(aws): empty the failed-deliveries bucket before deleting a metric stream stack CloudFormation cannot delete a bucket that holds objects, and failed Firehose batches land in it, so removing a region or cleaning up would stop with the stack half deleted. The scripts empty it first, and once more on a retry. --- .../src/services/integrations/aws/setup.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/services/integrations/aws/setup.ts b/packages/backend/src/services/integrations/aws/setup.ts index 0d35e88e54..195849253d 100644 --- a/packages/backend/src/services/integrations/aws/setup.ts +++ b/packages/backend/src/services/integrations/aws/setup.ts @@ -236,13 +236,33 @@ fi * Deletes the metric stream stack from every enabled region not in `keep`. A stream turned off, or * a region dropped, leaves a stack only a later setup or the cleanup knows to remove. */ +/** + * Deletes one region's metric stream stack. CloudFormation cannot delete a bucket with objects in + * it, and failed batches land in it, so the bucket is emptied first; once more if Firehose wrote + * into it while the stack was being deleted. + */ +const deleteMetricsStackFunction = (stackName: string) => `delete_metrics_stack() { + for attempt in 1 2; do + bucket="$(aws cloudformation describe-stack-resource --region "$1" --stack-name ${stackName} --logical-resource-id FailedDeliveries --query 'StackResourceDetail.PhysicalResourceId' --output text 2>/dev/null || true)" + if [ -n "$bucket" ] && [ "$bucket" != "None" ]; then + aws s3 rm "s3://$bucket" --recursive --region "$1" >/dev/null 2>&1 || true + fi + aws cloudformation delete-stack --region "$1" --stack-name ${stackName} + if aws cloudformation wait stack-delete-complete --region "$1" --stack-name ${stackName}; then + return 0 + fi + echo "Deleting the metric stream stack in $1 failed (attempt $attempt)." >&2 + done + return 1 +} +` + const removeMetricsStacks = (stackName: string, keep: ReadonlyArray) => `keep=" ${keep.join(" ")} " for region in $(aws ec2 describe-regions --region ${AWS_STACK_REGION} --query 'Regions[].RegionName' --output text); do case "$keep" in *" $region "*) continue ;; esac if aws cloudformation describe-stacks --region "$region" --stack-name ${stackName} >/dev/null 2>&1; then echo "Removing the metric stream from $region..." - aws cloudformation delete-stack --region "$region" --stack-name ${stackName} - aws cloudformation wait stack-delete-complete --region "$region" --stack-name ${stackName} + delete_metrics_stack "$region" fi done ` @@ -258,8 +278,7 @@ ${template}MAPLE_METRICS_TEMPLATE for region in ${stream.regions.join(" ")}; do status="$(aws cloudformation describe-stacks --region "$region" --stack-name ${stackName} --query 'Stacks[0].StackStatus' --output text 2>/dev/null || true)" if [ "$status" = "ROLLBACK_COMPLETE" ]; then - aws cloudformation delete-stack --region "$region" --stack-name ${stackName} - aws cloudformation wait stack-delete-complete --region "$region" --stack-name ${stackName} + delete_metrics_stack "$region" fi echo "Deploying the metric stream to $region..." aws cloudformation deploy \\ @@ -305,7 +324,7 @@ aws cloudformation deploy \\ --capabilities CAPABILITY_NAMED_IAM \\ --no-fail-on-empty-changeset rm -f "$template" -${metrics === null ? "" : deployMetricsStacks(target.metricsStackName, metrics.stream, metrics.template)}${removeMetricsStacks(target.metricsStackName, metrics?.stream.regions ?? [])}echo "Done. Go back to Maple and select Check connection. Details: ${DOCS_URL}" +${deleteMetricsStackFunction(target.metricsStackName)}${metrics === null ? "" : deployMetricsStacks(target.metricsStackName, metrics.stream, metrics.template)}${removeMetricsStacks(target.metricsStackName, metrics?.stream.regions ?? [])}echo "Done. Go back to Maple and select Check connection. Details: ${DOCS_URL}" `, ) @@ -314,7 +333,7 @@ export const renderAwsCleanupScript = (target: AwsScriptTarget) => "AWS", "MAPLE_CLEANUP_SCRIPT", `set -euo pipefail -${accountGuard(target.accountId)}${removeMetricsStacks(target.metricsStackName, [])}echo "Deleting ${target.stackName} from account ${target.accountId}..." +${accountGuard(target.accountId)}${deleteMetricsStackFunction(target.metricsStackName)}${removeMetricsStacks(target.metricsStackName, [])}echo "Deleting ${target.stackName} from account ${target.accountId}..." aws cloudformation delete-stack --region ${AWS_STACK_REGION} --stack-name ${target.stackName} aws cloudformation wait stack-delete-complete --region ${AWS_STACK_REGION} --stack-name ${target.stackName} echo "Done. The role Maple used is gone." From 62955b62490fa4db25d877923be92d5280a8a9cd Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 14:31:01 +0200 Subject: [PATCH 03/10] test(aws): run the setup and cleanup scripts against a fake aws CLI Pins that a metric stream's bucket is emptied before every stack delete, that a failed delete is retried, and that a cleanup stops before the role's stack when a stream's stack cannot be deleted. --- .../services/integrations/aws/setup.test.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 packages/backend/src/services/integrations/aws/setup.test.ts diff --git a/packages/backend/src/services/integrations/aws/setup.test.ts b/packages/backend/src/services/integrations/aws/setup.test.ts new file mode 100644 index 0000000000..dd523de002 --- /dev/null +++ b/packages/backend/src/services/integrations/aws/setup.test.ts @@ -0,0 +1,124 @@ +import { spawnSync } from "node:child_process" +import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { afterEach, describe, expect, it } from "vitest" +import { Effect, Schema } from "effect" +import { AwsAccountId } from "@maple/domain/primitives" +import { renderAwsCleanupScript, renderAwsSetupScript } from "./setup" + +const accountId = Schema.decodeUnknownSync(AwsAccountId)("123456789012") +const target = { + accountId, + stackName: "maple-integration-abc", + metricsStackName: "maple-metrics-abc", +} + +// A fake `aws` that logs every call. Metric stream stacks exist in $AWS_STACKS_IN; the first +// wait for a deleted one fails when $AWS_WAIT_FAILS is set, as when Firehose wrote meanwhile. +const FAKE_AWS = `#!/usr/bin/env bash +echo "$*" >> "$AWS_LOG" +region="" +args=("$@") +for i in "\${!args[@]}"; do [ "\${args[$i]}" = "--region" ] && region="\${args[$((i + 1))]}"; done +case "$1 $2" in + "sts get-caller-identity") echo "123456789012" ;; + "ec2 describe-regions") echo "us-east-1 eu-central-1 us-west-2" ;; + "cloudformation describe-stacks") + case "$*" in + *maple-metrics-abc*) case " $AWS_STACKS_IN " in *" $region "*) echo CREATE_COMPLETE ;; *) exit 254 ;; esac ;; + *) echo CREATE_COMPLETE ;; + esac ;; + "cloudformation describe-stack-resource") echo "failed-deliveries-$region" ;; + "cloudformation wait") + if [ -n "$AWS_WAIT_FAILS" ] && [ ! -e "$AWS_LOG.waited" ]; then touch "$AWS_LOG.waited"; exit 255; fi ;; +esac +exit 0 +` + +const tempDirs: string[] = [] +afterEach(() => { + for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) +}) + +const run = (script: string, env: { readonly stacksIn: ReadonlyArray; readonly waitFails?: boolean }) => { + const dir = mkdtempSync(join(tmpdir(), "maple-aws-script-")) + tempDirs.push(dir) + writeFileSync(join(dir, "aws"), FAKE_AWS) + chmodSync(join(dir, "aws"), 0o755) + const log = join(dir, "aws.log") + const result = spawnSync("bash", [], { + input: script, + cwd: dir, + encoding: "utf8", + env: { + ...process.env, + PATH: `${dir}:${process.env.PATH}`, + AWS_LOG: log, + AWS_STACKS_IN: env.stacksIn.join(" "), + AWS_WAIT_FAILS: env.waitFails === true ? "1" : "", + }, + }) + return { + status: result.status, + calls: existsSync(log) ? readFileSync(log, "utf8").trim().split("\n") : [], + } +} + +/** The calls that touch one region's metric stream stack or its bucket, in order. */ +const metricsCalls = (calls: ReadonlyArray, region: string) => + calls + .filter((call) => call.includes(`--region ${region}`) && /s3 rm|delete-stack|wait stack-delete/u.test(call)) + .filter((call) => !call.includes(target.stackName)) + .map((call) => + call.startsWith("s3 rm") ? "empty bucket" : call.includes("delete-stack ") ? "delete" : "wait", + ) + +describe("AWS setup scripts", () => { + it("empties a stream's bucket before every delete, and retries a delete that failed", () => { + const cleanup = Effect.runSync(renderAwsCleanupScript(target)) + const { status, calls } = run(cleanup, { stacksIn: ["eu-central-1"], waitFails: true }) + expect(status).toBe(0) + expect(metricsCalls(calls, "eu-central-1")).toEqual([ + "empty bucket", + "delete", + "wait", + "empty bucket", + "delete", + "wait", + ]) + // Regions without the stack are left alone, and the role's stack goes last. + expect(metricsCalls(calls, "us-west-2")).toEqual([]) + expect(calls.at(-1)).toContain(`stack-delete-complete --region us-east-1 --stack-name ${target.stackName}`) + }) + + it("stops a cleanup that cannot delete a stream's stack, so the role that trusts Maple is not removed first", () => { + const cleanup = Effect.runSync(renderAwsCleanupScript(target)) + const failing = FAKE_AWS.replace( + `if [ -n "$AWS_WAIT_FAILS" ] && [ ! -e "$AWS_LOG.waited" ]; then touch "$AWS_LOG.waited"; exit 255; fi ;;`, + "exit 255 ;;", + ) + const dir = mkdtempSync(join(tmpdir(), "maple-aws-script-")) + tempDirs.push(dir) + writeFileSync(join(dir, "aws"), failing) + chmodSync(join(dir, "aws"), 0o755) + const log = join(dir, "aws.log") + const result = spawnSync("bash", [], { + input: cleanup, + cwd: dir, + encoding: "utf8", + env: { ...process.env, PATH: `${dir}:${process.env.PATH}`, AWS_LOG: log, AWS_STACKS_IN: "eu-central-1" }, + }) + expect(result.status).not.toBe(0) + expect(readFileSync(log, "utf8")).not.toContain(`delete-stack --region us-east-1 --stack-name ${target.stackName}`) + }) + + it("a setup without a stream removes the stream from every region that still has it", () => { + const setup = Effect.runSync(renderAwsSetupScript(target, "{}\n", null)) + const { status, calls } = run(setup, { stacksIn: ["us-east-1", "eu-central-1"] }) + expect(status).toBe(0) + expect(metricsCalls(calls, "us-east-1")).toEqual(["empty bucket", "delete", "wait"]) + expect(metricsCalls(calls, "eu-central-1")).toEqual(["empty bucket", "delete", "wait"]) + expect(metricsCalls(calls, "us-west-2")).toEqual([]) + }) +}) From be10f32351c76cdd30479deef840484e96cf8a77 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 14:40:57 +0200 Subject: [PATCH 04/10] fix(aws): retry a metric stream delete request AWS refused Under set -e a failed delete-stack call ended the script before the retry. It is now part of the retried condition, with a test for a refused request. --- .../services/integrations/aws/setup.test.ts | 23 ++++++++++++++++--- .../src/services/integrations/aws/setup.ts | 4 ++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/services/integrations/aws/setup.test.ts b/packages/backend/src/services/integrations/aws/setup.test.ts index dd523de002..2ce88c2e8f 100644 --- a/packages/backend/src/services/integrations/aws/setup.test.ts +++ b/packages/backend/src/services/integrations/aws/setup.test.ts @@ -14,8 +14,8 @@ const target = { metricsStackName: "maple-metrics-abc", } -// A fake `aws` that logs every call. Metric stream stacks exist in $AWS_STACKS_IN; the first -// wait for a deleted one fails when $AWS_WAIT_FAILS is set, as when Firehose wrote meanwhile. +// A fake `aws` that logs every call. Metric stream stacks exist in $AWS_STACKS_IN. The first wait +// fails with $AWS_WAIT_FAILS (Firehose wrote meanwhile); the first delete call with $AWS_DELETE_FAILS. const FAKE_AWS = `#!/usr/bin/env bash echo "$*" >> "$AWS_LOG" region="" @@ -30,6 +30,8 @@ case "$1 $2" in *) echo CREATE_COMPLETE ;; esac ;; "cloudformation describe-stack-resource") echo "failed-deliveries-$region" ;; + "cloudformation delete-stack") + if [ -n "$AWS_DELETE_FAILS" ] && [ ! -e "$AWS_LOG.deleted" ]; then touch "$AWS_LOG.deleted"; exit 254; fi ;; "cloudformation wait") if [ -n "$AWS_WAIT_FAILS" ] && [ ! -e "$AWS_LOG.waited" ]; then touch "$AWS_LOG.waited"; exit 255; fi ;; esac @@ -41,7 +43,14 @@ afterEach(() => { for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) }) -const run = (script: string, env: { readonly stacksIn: ReadonlyArray; readonly waitFails?: boolean }) => { +const run = ( + script: string, + env: { + readonly stacksIn: ReadonlyArray + readonly waitFails?: boolean + readonly deleteFails?: boolean + }, +) => { const dir = mkdtempSync(join(tmpdir(), "maple-aws-script-")) tempDirs.push(dir) writeFileSync(join(dir, "aws"), FAKE_AWS) @@ -57,6 +66,7 @@ const run = (script: string, env: { readonly stacksIn: ReadonlyArray; re AWS_LOG: log, AWS_STACKS_IN: env.stacksIn.join(" "), AWS_WAIT_FAILS: env.waitFails === true ? "1" : "", + AWS_DELETE_FAILS: env.deleteFails === true ? "1" : "", }, }) return { @@ -92,6 +102,13 @@ describe("AWS setup scripts", () => { expect(calls.at(-1)).toContain(`stack-delete-complete --region us-east-1 --stack-name ${target.stackName}`) }) + it("retries a delete request AWS refused, instead of ending the script", () => { + const cleanup = Effect.runSync(renderAwsCleanupScript(target)) + const { status, calls } = run(cleanup, { stacksIn: ["eu-central-1"], deleteFails: true }) + expect(status).toBe(0) + expect(metricsCalls(calls, "eu-central-1")).toEqual(["empty bucket", "delete", "empty bucket", "delete", "wait"]) + }) + it("stops a cleanup that cannot delete a stream's stack, so the role that trusts Maple is not removed first", () => { const cleanup = Effect.runSync(renderAwsCleanupScript(target)) const failing = FAKE_AWS.replace( diff --git a/packages/backend/src/services/integrations/aws/setup.ts b/packages/backend/src/services/integrations/aws/setup.ts index 195849253d..df8ca51b96 100644 --- a/packages/backend/src/services/integrations/aws/setup.ts +++ b/packages/backend/src/services/integrations/aws/setup.ts @@ -247,8 +247,8 @@ const deleteMetricsStackFunction = (stackName: string) => `delete_metrics_stack( if [ -n "$bucket" ] && [ "$bucket" != "None" ]; then aws s3 rm "s3://$bucket" --recursive --region "$1" >/dev/null 2>&1 || true fi - aws cloudformation delete-stack --region "$1" --stack-name ${stackName} - if aws cloudformation wait stack-delete-complete --region "$1" --stack-name ${stackName}; then + if aws cloudformation delete-stack --region "$1" --stack-name ${stackName} && + aws cloudformation wait stack-delete-complete --region "$1" --stack-name ${stackName}; then return 0 fi echo "Deleting the metric stream stack in $1 failed (attempt $attempt)." >&2 From 3c97f7f946c7c8c36116c793abeb6a806917e26e Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 17:57:45 +0200 Subject: [PATCH 05/10] test(aws): give the removal stub the metric stream fields; delete copy names metrics --- apps/web/src/page/sources/kit/integration.ts | 2 +- .../src/services/integrations/SourceRemovalService.test.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/page/sources/kit/integration.ts b/apps/web/src/page/sources/kit/integration.ts index 6f7fd4c564..8b7d99f32d 100644 --- a/apps/web/src/page/sources/kit/integration.ts +++ b/apps/web/src/page/sources/kit/integration.ts @@ -8,7 +8,7 @@ const DISCONNECTS = { prometheus: "Maple stops scraping its targets and removes them", cloudflare: "Maple revokes its read-only Cloudflare token", gcp: "Maple stops accepting its logs and metrics", - aws: "Maple stops assuming its role in the account", + aws: "Maple stops assuming its role and accepting its CloudWatch metrics", railway: "Maple deletes its Railway API token", planetscale: "Maple drops its PlanetScale access and the metrics scrape", github: "Maple deletes this installation's repositories, branches and commits", diff --git a/packages/backend/src/services/integrations/SourceRemovalService.test.ts b/packages/backend/src/services/integrations/SourceRemovalService.test.ts index 7c7418987f..203d62e44d 100644 --- a/packages/backend/src/services/integrations/SourceRemovalService.test.ts +++ b/packages/backend/src/services/integrations/SourceRemovalService.test.ts @@ -118,6 +118,9 @@ const makeLayer = (testDb: TestDb, calls: string[]) => { lastCheckedAt: null, lastCheckError: null, createdAt: 0, + metricStream: null, + metricsLastReceivedAt: null, + metricsLastError: null, }, cleanupScript: "aws cloudformation delete-stack", }), From 0f7b24eb83f4a3403b93776260fcdb26f884ea27 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 18:05:20 +0200 Subject: [PATCH 06/10] fix(ingest): snake_case CloudWatch dimension attribute keys aws.dimension.ServiceName becomes aws.dimension.service_name, matching the converter's other keys. --- apps/ingest/src/aws_metric_stream.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ingest/src/aws_metric_stream.rs b/apps/ingest/src/aws_metric_stream.rs index 38ccea3c6f..cdf7ba3177 100644 --- a/apps/ingest/src/aws_metric_stream.rs +++ b/apps/ingest/src/aws_metric_stream.rs @@ -255,7 +255,7 @@ impl CloudWatchMetric { let mut attributes: Vec = self .dimensions .iter() - .map(|(name, value)| attribute(&format!("aws.dimension.{name}"), value)) + .map(|(name, value)| attribute(&format!("aws.dimension.{}", snake_case(name)), value)) .collect(); attributes.push(attribute("aws.namespace", &self.namespace)); attributes.push(attribute("aws.metric_name", &self.metric_name)); @@ -807,11 +807,11 @@ mod tests { }; let point_attrs = &gauge.data_points[0].attributes; assert_eq!( - string_attr(point_attrs, "aws.dimension.ClusterName"), + string_attr(point_attrs, "aws.dimension.cluster_name"), Some("prod") ); assert_eq!( - string_attr(point_attrs, "aws.dimension.ServiceName"), + string_attr(point_attrs, "aws.dimension.service_name"), Some("checkout") ); assert_eq!(string_attr(point_attrs, "aws.namespace"), Some("AWS/ECS")); From 6b56dd32f7fc0f5f0e2db65a1d0f916c04f16731 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 18:28:32 +0200 Subject: [PATCH 07/10] fix(aws): turning a metric stream off revokes its secret A Firehose still deployed in the account stops authenticating; turning the stream back on mints a new secret for the next setup. --- .../integrations/AwsConnectionService.test.ts | 26 ++++++++++++++++--- .../integrations/AwsConnectionService.ts | 17 ++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/services/integrations/AwsConnectionService.test.ts b/packages/backend/src/services/integrations/AwsConnectionService.test.ts index 7023065ddb..a9aab264c5 100644 --- a/packages/backend/src/services/integrations/AwsConnectionService.test.ts +++ b/packages/backend/src/services/integrations/AwsConnectionService.test.ts @@ -215,7 +215,9 @@ describe("AwsConnectionService", () => { it.effect("streams metrics with a secret only this connection's row can verify", () => { const testDb = createTestDb(trackedDbs) const aws = makeFakeAws() - const regions = ["us-east-1", "eu-central-1"].map((region) => Schema.decodeUnknownSync(AwsRegion)(region)) + const regions = ["us-east-1", "eu-central-1"].map((region) => + Schema.decodeUnknownSync(AwsRegion)(region), + ) const streamSecretHash = (id: string) => Effect.promise(() => queryFirstRow<{ stream_secret_hash: string | null }>( @@ -235,7 +237,10 @@ describe("AwsConnectionService", () => { const setup = yield* service.setup(orgId, created.id) const accessKey = accessKeyOf(setup.setupScript) assert.isDefined(accessKey) - assert.strictEqual(yield* streamSecretHash(created.id), hashIngestKey(accessKey!, "maple-test-lookup-secret")) + assert.strictEqual( + yield* streamSecretHash(created.id), + hashIngestKey(accessKey!, "maple-test-lookup-secret"), + ) assert.include(setup.setupScript, "for region in us-east-1 eu-central-1; do") const template = JSON.parse(setup.metricsCloudformationTemplate ?? "{}") @@ -244,8 +249,9 @@ describe("AwsConnectionService", () => { { Namespace: "AWS/ApplicationELB" }, ]) assert.strictEqual(template.Resources.MetricStream.Properties.OutputFormat, "opentelemetry1.0") - const endpoint = template.Resources.DeliveryStream.Properties.HttpEndpointDestinationConfiguration - .EndpointConfiguration + const endpoint = + template.Resources.DeliveryStream.Properties.HttpEndpointDestinationConfiguration + .EndpointConfiguration assert.strictEqual(endpoint.Url, `https://ingest.test/v1/firehose/aws/${created.id}`) // The secret is a parameter the script passes, never part of the template. assert.deepStrictEqual(endpoint.AccessKey, { Ref: "AccessKey" }) @@ -259,6 +265,18 @@ describe("AwsConnectionService", () => { assert.isNull(removed.metricsStackName) assert.isUndefined(accessKeyOf(removed.setupScript)) assert.include(removed.setupScript, "describe-regions") + // The old secret stops matching; turning the stream back on deploys a new one. + assert.isNull(yield* streamSecretHash(created.id)) + yield* service.update(orgId, created.id, { + metricStream: { regions, namespaces: ["AWS/ECS"] }, + }) + const again = accessKeyOf((yield* service.setup(orgId, created.id)).setupScript) + assert.isDefined(again) + assert.notStrictEqual(again, accessKey) + assert.strictEqual( + yield* streamSecretHash(created.id), + hashIngestKey(again!, "maple-test-lookup-secret"), + ) }).pipe(Effect.provide(makeLayer(testDb, aws))) }) diff --git a/packages/backend/src/services/integrations/AwsConnectionService.ts b/packages/backend/src/services/integrations/AwsConnectionService.ts index 76e1c6e8e7..af9886e01d 100644 --- a/packages/backend/src/services/integrations/AwsConnectionService.ts +++ b/packages/backend/src/services/integrations/AwsConnectionService.ts @@ -486,8 +486,17 @@ export class AwsConnectionService extends Context.Service db.run( @@ -522,7 +531,11 @@ export class AwsConnectionService extends Context.Service Date: Thu, 22 Oct 2026 18:38:43 +0200 Subject: [PATCH 08/10] fix(ingest): drop empty CloudWatch requests and resources while decoding A run of zero bytes decoded to one empty request per byte, each far larger in memory than its input. --- apps/ingest/src/aws_metric_stream.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/ingest/src/aws_metric_stream.rs b/apps/ingest/src/aws_metric_stream.rs index cdf7ba3177..ecb0d76b5e 100644 --- a/apps/ingest/src/aws_metric_stream.rs +++ b/apps/ingest/src/aws_metric_stream.rs @@ -97,9 +97,16 @@ impl Delivery { } let mut buffer = bytes.as_slice(); while !buffer.is_empty() { - let request = ExportMetricsServiceRequest::decode_length_delimited(&mut buffer) + let mut request = ExportMetricsServiceRequest::decode_length_delimited(&mut buffer) .map_err(|_| RecordError::Unreadable)?; - requests.push(request); + // Empty messages decode from a byte or two each but cost far more in memory: + // keep only what carries metrics, so a run of zero bytes allocates nothing. + request + .resource_metrics + .retain(|resource| !resource.scope_metrics.is_empty()); + if !request.resource_metrics.is_empty() { + requests.push(request); + } } } Ok(requests) @@ -1047,4 +1054,20 @@ mod tests { ); assert_eq!(delivery.metric_requests(big.len() * 2).unwrap().len(), 2); } + + #[test] + fn keeps_nothing_for_empty_requests_or_resources() { + let one = stream_request(vec![( + "amazonaws.com/AWS/ECS/A", + "None", + vec![point(&[], 1, 1.0, 1.0, 1.0)], + )]); + let mut padded = one.clone(); + padded.resource_metrics.push(Default::default()); + // Zero bytes are empty requests, one per byte. + let mut record = vec![0_u8; 4096]; + record.extend(length_delimited(&[padded])); + let delivery = parse_delivery(&delivery_json(&[record])).unwrap(); + assert_eq!(delivery.metric_requests(1024 * 1024).unwrap(), [one]); + } } From f86f9171de6cbc7bcd0347ea8cb5db681804bb7b Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 18:41:00 +0200 Subject: [PATCH 09/10] fix(ingest): cap metric points per CloudWatch delivery A direct sender with a stream secret is not held to Firehose's 1 MiB buffer; past 20,000 points the delivery is refused as too large (413). --- apps/ingest/src/aws_metric_stream.rs | 46 ++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/ingest/src/aws_metric_stream.rs b/apps/ingest/src/aws_metric_stream.rs index ecb0d76b5e..90c7932402 100644 --- a/apps/ingest/src/aws_metric_stream.rs +++ b/apps/ingest/src/aws_metric_stream.rs @@ -69,10 +69,34 @@ struct DeliveryRecord { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RecordError { Unreadable, - /// The records decode to more than the allowed bytes. + /// The records decode to more than the allowed bytes or metric points. TooLarge, } +/// Points one delivery may carry. Maple's stack buffers 1 MiB, a few thousand points; each +/// point expands to five metrics, so a direct sender cannot use the byte cap to amplify memory. +pub const MAX_POINTS_PER_DELIVERY: usize = 20_000; + +/// Metrics plus their data points: what a request costs once expanded. +fn point_count(request: &ExportMetricsServiceRequest) -> usize { + request + .resource_metrics + .iter() + .flat_map(|resource| &resource.scope_metrics) + .flat_map(|scope| &scope.metrics) + .map(|metric| { + 1 + match &metric.data { + Some(metric::Data::Summary(summary)) => summary.data_points.len(), + Some(metric::Data::Gauge(gauge)) => gauge.data_points.len(), + Some(metric::Data::Sum(sum)) => sum.data_points.len(), + Some(metric::Data::Histogram(histogram)) => histogram.data_points.len(), + Some(metric::Data::ExponentialHistogram(histogram)) => histogram.data_points.len(), + None => 0, + } + }) + .sum() +} + /// `None` when the body is not a Firehose request. pub fn parse_delivery(payload: &[u8]) -> Option { serde_json::from_slice(payload).ok() @@ -86,6 +110,7 @@ impl Delivery { max_decoded: usize, ) -> Result, RecordError> { let mut decoded_total = 0_usize; + let mut points = 0_usize; let mut requests = Vec::new(); for record in &self.records { let bytes = STANDARD @@ -105,6 +130,10 @@ impl Delivery { .resource_metrics .retain(|resource| !resource.scope_metrics.is_empty()); if !request.resource_metrics.is_empty() { + points = points.saturating_add(point_count(&request)); + if points > MAX_POINTS_PER_DELIVERY { + return Err(RecordError::TooLarge); + } requests.push(request); } } @@ -1055,6 +1084,19 @@ mod tests { assert_eq!(delivery.metric_requests(big.len() * 2).unwrap().len(), 2); } + #[test] + fn refuses_a_delivery_with_more_points_than_a_stream_sends() { + let points = (0..=MAX_POINTS_PER_DELIVERY) + .map(|_| point(&[], 1, 1.0, 1.0, 1.0)) + .collect(); + let request = stream_request(vec![("amazonaws.com/AWS/ECS/A", "None", points)]); + let delivery = parse_delivery(&delivery_json(&[length_delimited(&[request])])).unwrap(); + assert_eq!( + delivery.metric_requests(64 * 1024 * 1024).unwrap_err(), + RecordError::TooLarge + ); + } + #[test] fn keeps_nothing_for_empty_requests_or_resources() { let one = stream_request(vec![( @@ -1063,7 +1105,7 @@ mod tests { vec![point(&[], 1, 1.0, 1.0, 1.0)], )]); let mut padded = one.clone(); - padded.resource_metrics.push(Default::default()); + padded.resource_metrics.push(ResourceMetrics::default()); // Zero bytes are empty requests, one per byte. let mut record = vec![0_u8; 4096]; record.extend(length_delimited(&[padded])); From 865724341fbaeb59fe5018af291d514387f625a9 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 22 Oct 2026 18:53:01 +0200 Subject: [PATCH 10/10] fix(ingest): also drop empty scope metrics while decoding Resources padded with empty scopes survived the filter uncounted; now only what carries a metric is kept, and the point cap counts every metric. --- apps/ingest/src/aws_metric_stream.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/ingest/src/aws_metric_stream.rs b/apps/ingest/src/aws_metric_stream.rs index 90c7932402..c2e4867107 100644 --- a/apps/ingest/src/aws_metric_stream.rs +++ b/apps/ingest/src/aws_metric_stream.rs @@ -125,7 +125,12 @@ impl Delivery { let mut request = ExportMetricsServiceRequest::decode_length_delimited(&mut buffer) .map_err(|_| RecordError::Unreadable)?; // Empty messages decode from a byte or two each but cost far more in memory: - // keep only what carries metrics, so a run of zero bytes allocates nothing. + // keep only what carries a metric, which the point cap then counts. + for resource in &mut request.resource_metrics { + resource + .scope_metrics + .retain(|scope| !scope.metrics.is_empty()); + } request .resource_metrics .retain(|resource| !resource.scope_metrics.is_empty()); @@ -1106,6 +1111,13 @@ mod tests { )]); let mut padded = one.clone(); padded.resource_metrics.push(ResourceMetrics::default()); + padded.resource_metrics.push(ResourceMetrics { + scope_metrics: vec![ScopeMetrics::default(); 3], + ..ResourceMetrics::default() + }); + padded.resource_metrics[0] + .scope_metrics + .push(ScopeMetrics::default()); // Zero bytes are empty requests, one per byte. let mut record = vec![0_u8; 4096]; record.extend(length_delimited(&[padded]));