From c158a71f8074cfb5ec41a2ca75eee251ec1c30f2 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 26 Sep 2026 03:28:40 +0000 Subject: [PATCH 1/4] Build logs: one timeline per Image Build, a section per Builder Each Builder's go is a section of the Image Build's log, keyed by a new attempt column. A move opens with one plain sentence from the skip trail. Cloud times the wait for a runner; ployz build streams the push; the SDK reports sending each image to its Machines. The mechanical Built-on header is gone; routine selection reasons stay in the data only. --- core/crates/ployz-sdk/index.d.ts | 5 +- core/crates/ployz/src/deploy/pipeline.rs | 26 +- core/crates/ployz/src/handlers/build.rs | 101 +- core/crates/ployz/src/sdk/prepare.rs | 14 +- core/crates/ployz/src/sdk/running.rs | 1 + dashboard/CONTEXT.md | 2 +- .../migration.sql | 3 + .../snapshot.json | 10933 ++++++++++++++++ .../src/components/deployment-logs.test.tsx | 19 +- dashboard/src/components/deployment-logs.tsx | 40 +- .../deployments/deployment-events.server.ts | 30 +- .../deployments/deployment-view.test.ts | 66 +- .../modules/deployments/deployment-view.ts | 86 +- ...loyment.build-order.inngest-engine.test.ts | 5 +- .../environment-deployment.inngest.ts | 9 +- .../github-image-builds.postgres.test.ts | 42 +- .../deployments/github-image-builds.server.ts | 41 +- .../src/modules/deployments/image-build.ts | 35 +- .../deployments/image-builds.server.ts | 29 +- .../deployments/preparation-progress.test.ts | 22 +- .../deployments/preparation-progress.ts | 38 +- .../deployments/server-image-builds.server.ts | 7 +- dashboard/src/modules/deployments/tables.ts | 7 +- .../-components/DeploymentServicePanel.tsx | 4 +- .../-components/canvas/DeploymentNode.tsx | 6 +- 25 files changed, 11363 insertions(+), 208 deletions(-) create mode 100644 dashboard/drizzle/20260926031614_build_step_attempt/migration.sql create mode 100644 dashboard/drizzle/20260926031614_build_step_attempt/snapshot.json diff --git a/core/crates/ployz-sdk/index.d.ts b/core/crates/ployz-sdk/index.d.ts index 0353be9451..27ae1c4432 100644 --- a/core/crates/ployz-sdk/index.d.ts +++ b/core/crates/ployz-sdk/index.d.ts @@ -125,7 +125,10 @@ export type PreparationEvent = | { Selected: { machine: import("./generated/payloads").Machine; reason: BuilderReason; rejections: string[] } } | { Build: { Stage: string } | { Output: number[] } | { Step: BuildStep } | { StepOutput: { step: string; stderr: boolean; text: string } } | { Timing: unknown } | { Target: { name: string; outcome: unknown } } } | "Transfer" - | { Delivered: { image: string; machine_id: MachineId } }; + /** One Service's image starts going to these Machines, by name. */ + | { Sending: { service: string; machines: string[] } } + /** One Machine received a Service's image. */ + | { Delivered: { image: string; service: string; machine_id: MachineId } }; export type RunningPreparation = AsyncIterable & { abort(): void; diff --git a/core/crates/ployz/src/deploy/pipeline.rs b/core/crates/ployz/src/deploy/pipeline.rs index 2a7048eaf1..12178d8d1c 100644 --- a/core/crates/ployz/src/deploy/pipeline.rs +++ b/core/crates/ployz/src/deploy/pipeline.rs @@ -250,7 +250,6 @@ fn invalid_argument(message: String) -> RpcError { #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct PushOutcome { - pub pushed: Vec, pub failures: Vec, } @@ -274,8 +273,8 @@ pub(crate) async fn push_project_images( machines: &[MachineObservation], preview: &DeployPlan, cancellation: &CancellationToken, + progress: &impl Fn(crate::sdk::prepare::Progress), ) -> Result { - let mut pushed = Vec::new(); let mut failures = Vec::new(); // Check every actual destination before any image or application changes. let deliveries = builds.iter().map(|service| { @@ -297,15 +296,34 @@ pub(crate) async fn push_project_images( if targets.is_empty() { continue; } + // The Build Machine already holds its image; only the others receive it. + let receivers = machines + .iter() + .filter(|machine| machine.machine.id != service.machine_id) + .filter(|machine| targets.contains(&machine.machine.id.to_string())) + .map(|machine| machine.machine.name.to_string()) + .collect::>(); + if !receivers.is_empty() { + progress(crate::sdk::prepare::Progress::Sending { + service: service.name.to_string(), + machines: receivers, + }); + } match push_image(client, service, machines, &targets, cancellation).await { Ok((images, service_failures)) => { - pushed.extend(images); + for image in images { + progress(crate::sdk::prepare::Progress::Delivered { + image: image.image, + service: service.name.to_string(), + machine_id: image.machine_id, + }); + } failures.extend(service_failures); } Err(error) => failures.push(format!("{}: {error}", service.image)), } } - Ok(PushOutcome { pushed, failures }) + Ok(PushOutcome { failures }) } pub(crate) async fn plan_project( diff --git a/core/crates/ployz/src/handlers/build.rs b/core/crates/ployz/src/handlers/build.rs index 6e55816b7a..eca2c91365 100644 --- a/core/crates/ployz/src/handlers/build.rs +++ b/core/crates/ployz/src/handlers/build.rs @@ -82,26 +82,27 @@ pub(super) fn build(root: &ArgMatches) -> Result<(), Error> { } .into()); } - let events = matches - .get_one::("events") - .map(std::fs::File::create) - .transpose()? - .map(std::sync::Mutex::new); + let events = std::sync::Arc::new(Events( + matches + .get_one::("events") + .map(std::fs::File::create) + .transpose()? + .map(std::sync::Mutex::new), + )); runtime()?.block_on(async { let cancellation = crate::cancellation::on_ctrl_c(); let build = captured.build; let export = cancellation.clone(); + let building = events.clone(); let (build, built) = tokio::task::spawn_blocking(move || { let built = build.execute_local(&cancellation, &|event| { - if let Some(file) = &events { - write_event(file, event); - } + building.write(&json!({"Build": event})); }); (build, built) }) .await .map_err(std::io::Error::other)?; - push(&grant, &built.map_err(BuildCommandError::from)?).await?; + push(&grant, &built.map_err(BuildCommandError::from)?, &events).await?; // The pushed image and its printed result are final; uploading cache only speeds up // the next Build, so its failure is a warning and it never reaches the events file. let exported = tokio::task::spawn_blocking(move || build.export_local_cache(&export)) @@ -116,19 +117,32 @@ pub(super) fn build(root: &ArgMatches) -> Result<(), Error> { }) } -/// One `{"at": , "event": }` line, the shape Cloud's build -/// log reads. Progress is best effort: a failed write never fails the build. -fn write_event(file: &std::sync::Mutex, event: &ployz_build::Progress) { - use std::io::Write as _; - let at = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_or(0, |elapsed| elapsed.as_millis()); - let line = json!({"at": at, "event": {"Build": event}}); - if let Ok(mut file) = file.lock() { - let _ = writeln!(file, "{line}"); +/// The `--events` file: one `{"at": , "event": }` line per +/// event, the shape Cloud's build log reads. Progress is best effort: a failed write never +/// fails the build. +struct Events(Option>); + +impl Events { + fn write(&self, event: &Value) { + use std::io::Write as _; + let Some(file) = &self.0 else { return }; + let at = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |elapsed| elapsed.as_millis()); + if let Ok(mut file) = file.lock() { + let _ = writeln!(file, "{}", json!({"at": at, "event": event})); + } + } + + /// A line of the push, as output of its `Push` stage. + fn push_output(&self, text: &str) { + self.write(&json!({"Build": {"StepOutput": {"step": PUSH_STEP, "stderr": false, "text": format!("{text}\n")}}})); } } +/// The push is a Ployz stage of its own; `ployz_build` knows only the build's stages. +const PUSH_STEP: &str = "stage:Push"; + /// The deployment's single Git-sourced Service: the one this command builds. fn git_service(deployment: &Value) -> Result { let mut services = deployment @@ -179,17 +193,19 @@ fn git(source: &Path, args: &[&str]) -> Result { } /// Push through the grant with `docker push`, which sends only the layers the Machine -/// lacks, and print what the Machine verified it received. -async fn push(grant: &BuildGrant, built: &LocalImage) -> Result<(), Error> { +/// lacks, and print what the Machine verified it received. Each layer's progress line +/// goes to the events file as the push's output. +async fn push(grant: &BuildGrant, built: &LocalImage, events: &Events) -> Result<(), Error> { let digest = &built.image.reference; let hex = digest .strip_prefix("sha256:") .ok_or(BuildCommandError::NotDigestIdentified)?; let tag = format!("{}:{RETAINED_DIGEST_TAG_PREFIX}{hex}", built.repository); + events.write(&json!({"Build": {"Stage": "Push"}})); let registry = open_grant_registry(grant, &ManagementRelay::default()).await?; let local = format!("{}/{tag}", registry.address()); docker(&["tag", digest, &local]).await?; - let pushed = docker(&["push", "--quiet", &local]).await; + let pushed = docker_push(&local, events).await; // The loopback name is meaningless once this command exits. let _ = docker(&["image", "rm", &local]).await; if let Err(error) = pushed { @@ -197,6 +213,7 @@ async fn push(grant: &BuildGrant, built: &LocalImage) -> Result<(), Error> { BuildCommandError::GrantRefused(refusal).into() })); } + events.push_output(&format!("Pushed {digest}")); // The Machine stores a tagged manifest only when its bytes hash to the tag's // digest, so a completed push is the Machine's confirmation of `digest`. println!( @@ -206,6 +223,46 @@ async fn push(grant: &BuildGrant, built: &LocalImage) -> Result<(), Error> { Ok(()) } +/// `docker push`, its per-layer lines streamed to the events file as they come. +async fn docker_push(reference: &str, events: &Events) -> Result<(), Error> { + use tokio::io::AsyncBufReadExt as _; + let mut child = tokio::process::Command::new("docker") + .args(["push", reference]) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn()?; + let stderr = child.stderr.take().map(|stderr| { + tokio::spawn(async move { + let mut text = String::new(); + let _ = tokio::io::AsyncReadExt::read_to_string( + &mut tokio::io::BufReader::new(stderr), + &mut text, + ) + .await; + text + }) + }); + if let Some(stdout) = child.stdout.take() { + let mut lines = tokio::io::BufReader::new(stdout).lines(); + while let Ok(Some(line)) = lines.next_line().await { + events.push_output(&line); + } + } + let status = child.wait().await?; + let stderr = match stderr { + Some(task) => task.await.unwrap_or_default(), + None => String::new(), + }; + if status.success() { + return Ok(()); + } + Err(BuildCommandError::Docker { + command: "push".to_owned(), + stderr: stderr.trim().to_owned(), + } + .into()) +} + async fn docker(args: &[&str]) -> Result<(), Error> { let output = tokio::process::Command::new("docker") .args(args) diff --git a/core/crates/ployz/src/sdk/prepare.rs b/core/crates/ployz/src/sdk/prepare.rs index 6a9e37b91b..14b0d58fe1 100644 --- a/core/crates/ployz/src/sdk/prepare.rs +++ b/core/crates/ployz/src/sdk/prepare.rs @@ -220,8 +220,15 @@ pub enum Progress { Selected(Box), Build(ployz_build::Progress), Transfer, + /// One Service's image starts going to these Machines, by name. + Sending { + service: String, + machines: Vec, + }, + /// One Machine received a Service's image. Delivered { image: String, + service: String, machine_id: ployz_core::MachineId, }, } @@ -278,14 +285,9 @@ pub async fn prepare( &machines, &plan, cancellation, + &progress, ) .await?; - for image in outcome.pushed { - progress(Progress::Delivered { - image: image.image, - machine_id: image.machine_id, - }); - } if cancellation.is_cancelled() { return Err(PreparationError::Cancelled); } diff --git a/core/crates/ployz/src/sdk/running.rs b/core/crates/ployz/src/sdk/running.rs index 4095beeb7c..7ada05249d 100644 --- a/core/crates/ployz/src/sdk/running.rs +++ b/core/crates/ployz/src/sdk/running.rs @@ -52,6 +52,7 @@ impl OutputBudget { | Progress::Platforms(_) | Progress::Selected(_) | Progress::Transfer + | Progress::Sending { .. } | Progress::Delivered { .. } => 0, }; buffered.fetch_add(size, Ordering::Relaxed); diff --git a/dashboard/CONTEXT.md b/dashboard/CONTEXT.md index 2d036a25ad..d84c60081f 100644 --- a/dashboard/CONTEXT.md +++ b/dashboard/CONTEXT.md @@ -248,7 +248,7 @@ How a Service's image is described for building: a Dockerfile or Railpack. _Avoid_: Builder, builder type **Build Step**: -One unit of an Image Build as the Engine reports it: a BuildKit step (a Dockerfile instruction, image resolution, or context transfer) or a Ployz-owned phase such as source upload or image delivery. A Build Step is keyed stably within its attempt, changes state until it completes, and owns the output attributed to it. Build Steps are retained with the attempt, separately from lifecycle history. +One unit of an Image Build: a BuildKit step (a Dockerfile instruction, image resolution, or context transfer) or a Ployz-owned phase such as source upload, waiting for a runner, installing ployz, pushing, or sending the image to a Machine. Each is recorded where it happens, so its timing is honest: Cloud times the wait for a runner, the runner its install and push, the Engine the rest. A Build Step is keyed stably within one Builder's go at its Image Build, changes state until it completes, and owns the output attributed to it. Each go is a section of the Image Build's one log; a later one opens with why the build moved there. Build Steps are retained with the attempt, separately from lifecycle history. _Avoid_: Build log line, vertex, build stage (a Cloud Deployment Stage is not a Build Step) Git repository identity and access are separate. Cloud can read a public GitHub repository anonymously or use an Organization member's connected GitHub App installation. Public access never falls back to installation credentials. Both paths pin a commit per Cloud Deployment Attempt and materialize it through the same source acquisition module. Automatic Git deployment and CI gating require installation access; public sources deploy manually. diff --git a/dashboard/drizzle/20260926031614_build_step_attempt/migration.sql b/dashboard/drizzle/20260926031614_build_step_attempt/migration.sql new file mode 100644 index 0000000000..fd31d2d085 --- /dev/null +++ b/dashboard/drizzle/20260926031614_build_step_attempt/migration.sql @@ -0,0 +1,3 @@ +ALTER TABLE "environment_deployment_build_step" ADD COLUMN "attempt" integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE "environment_deployment_build_step" DROP CONSTRAINT "environment_deployment_build_step_key_unique";--> statement-breakpoint +ALTER TABLE "environment_deployment_build_step" ADD CONSTRAINT "environment_deployment_build_step_key_unique" UNIQUE NULLS NOT DISTINCT("deployment_id","image","attempt","build","key"); \ No newline at end of file diff --git a/dashboard/drizzle/20260926031614_build_step_attempt/snapshot.json b/dashboard/drizzle/20260926031614_build_step_attempt/snapshot.json new file mode 100644 index 0000000000..7d21a73044 --- /dev/null +++ b/dashboard/drizzle/20260926031614_build_step_attempt/snapshot.json @@ -0,0 +1,10933 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "a4bbbd3a-2d08-4ed7-ae96-314abb4987b1", + "prevIds": [ + "49d3687c-4bc4-49e7-a802-9a2be33ef704" + ], + "ddl": [ + { + "isRlsEnabled": false, + "name": "organization", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_change", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "account", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "invitation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "member", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "session", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "verification", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "project", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user_project_preference", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_canvas_node_position", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_resource", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "resource_lineage", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "service", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "service_lineage", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "service_registry_credential", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "variable", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "variable_secret", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment_build_output", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment_build_step", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment_event", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment_image_build", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_deployment_secret", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_saved_state_snapshot", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_build_order", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "core_operation_event", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "core_operation_watch", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_node_config_snapshot", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_node_config_snapshot_secret", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_node_introduction", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "environment_node_introduction_secret", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_pairing", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "teardown_attempt", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "volume_remove_attempt", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "enrollment_allocation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "machine_enrollment_token", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "machine_remove_attempt", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_machine", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_branch_projection", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_check_suite_projection", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_environment_trigger", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_installation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_repository_cache", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_webhook_delivery", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_billing_state", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization_cluster_domain", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": { + "type": "always", + "name": "organization_change_seq_seq", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": 1, + "cycle": false + }, + "name": "seq", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "xid8", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "pg_current_xact_id()", + "generated": null, + "identity": null, + "name": "xid", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_table", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "changed_ids", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_ids", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "all_rows", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization_change" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scope", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inviter_id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "active_organization_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "active_organization_slug", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "sidebar_open", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_address", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_agent", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "email_verified", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "open_started_deployments", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "identifier", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "namespace", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "intent", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "revision", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "project" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "project" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "project" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "project" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "project" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "user_project_preference" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_id", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "x", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "y", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lineage_id", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "implementation_type", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_resource" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canonical_name", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canonical_slug", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "resource_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lineage_id", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{\"autoDeploy\":true,\"waitForCi\":false,\"watchPaths\":[],\"imageUpdate\":{\"type\":\"off\"}}'", + "generated": null, + "identity": null, + "name": "policy", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_registry_credential", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first_deployed_at", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "service" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canonical_name", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canonical_slug", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "service_lineage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "service_registry_credential" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "revision", + "entityType": "columns", + "schema": "public", + "table": "service_registry_credential" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "service_id", + "entityType": "columns", + "schema": "public", + "table": "service_registry_credential" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_registry_username", + "entityType": "columns", + "schema": "public", + "table": "service_registry_credential" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_registry_secret", + "entityType": "columns", + "schema": "public", + "table": "service_registry_credential" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "variable" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "variable" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "variable" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "service_id", + "entityType": "columns", + "schema": "public", + "table": "variable" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "variable" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "variable_id", + "entityType": "columns", + "schema": "public", + "table": "variable_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "variable_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "variable_secret" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_value", + "entityType": "columns", + "schema": "public", + "table": "variable_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger_origin", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "saved_state_snapshot_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "service_action_policy", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'queued'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "core_deploy_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retry_of_deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "source_pins", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "variable_producers", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deploy_manifest", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deploy_preview", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "runtime_progress", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_code", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_message", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cancellation_requested_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "dispatch_requested_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "step_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "stderr", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "text", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "attempt", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "build", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "completed_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "cached", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "error", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "progress", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "service_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'building'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "machine_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "server_choice", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_receipt", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_message", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'server'", + "generated": null, + "identity": null, + "name": "builder", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "github_run_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "checked_in_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "github", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "skips", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_secret" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_runtime_outcome", + "entityType": "columns", + "schema": "public", + "table": "environment_deployment_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor_id", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "intent", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "volume_deletion_authorizations", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_build_order" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "build_order", + "entityType": "columns", + "schema": "public", + "table": "organization_build_order" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization_build_order" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_build_order" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "watch_id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sequence", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_type", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "schema_version", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "core_operation_event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "operation_id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expected_kind", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "start_sequence", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "next_sequence", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cursor_state", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'active'", + "generated": null, + "identity": null, + "name": "observation_state", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "observation_detail", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deadline_at", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "terminal_at", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "core_operation_watch" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_deployment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_type", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_lineage_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "config_version", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_revision", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "snapshot_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_registry_username", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_registry_secret", + "entityType": "columns", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_type", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_lineage_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "config_version", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_type", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "node_id", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authored_intent", + "entityType": "columns", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_pairing_secret", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "removal_started_at", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "removal_endpoints", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "founder_public_key", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "founder_claim_machine_id", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "founder_machine_id", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first_connect_deployment_evaluated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_pairing" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "requested_by_user_id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "project_id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scope", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "confirm_data_loss", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targets", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outcome", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_message", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "terminal_at", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "teardown_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "requested_by_user_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_deployment_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_resource_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retry_of_attempt_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "volumes", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outcome", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_message", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "terminal_at", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cluster_key", + "entityType": "columns", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "assignments", + "entityType": "columns", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_hash", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_user_id", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "requested_by_user_id", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "machine_id", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "confirm_data_loss", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inngest_run_id", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "missing_identities", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_code", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_message", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "terminal_at", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "machine_id", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cluster_key", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_capability", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_dial_entry", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_machine" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repository_id", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ref", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "evaluated_head_sha", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "evaluation_reason", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_delivery_id", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_receipt_sequence", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "evaluation_revision", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_branch_projection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repository_id", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "check_suite_id", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "head_sha", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "conclusion", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_delivery_id", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_receipt_sequence", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "transition_revision", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "published_revision", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repository_id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ref", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "head_sha", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'waiting'", + "generated": null, + "identity": null, + "name": "admission_state", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "changed_paths", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": null, + "generated": null, + "identity": null, + "name": "service_ids", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "selection_mode", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_delivery_id", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_receipt_sequence", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "branch_evaluation_revision", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "trigger_revision", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "published_revision", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "publish_state", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published_at", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_login", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_type", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_avatar_url", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repository_id", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "full_name", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "default_branch", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "private", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "html_url", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repo_updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "synced_at", + "entityType": "columns", + "schema": "public", + "table": "github_repository_cache" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "receipt_sequence", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_kind", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'received'", + "generated": null, + "identity": null, + "name": "processing_state", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outcome", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "failure_code", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "repository_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ref", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "branch_state", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "head_sha", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "check_suite_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "check_suite_action", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "check_suite_status", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "check_suite_conclusion", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "processing_run_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "processing_started_at", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "processed_at", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "active_subscription_id", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "current_period_end", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_active_subscription", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "synced_at", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_updated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_billing_state" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "endpoint", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_token", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reserved_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lease_renewed_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "records_synced_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "traffic", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "checked_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encrypted_certificate_private_key", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "certificate_chain", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "certificate_not_after", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "xid", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organization_change_organization_id_xid_idx", + "entityType": "indexes", + "schema": "public", + "table": "organization_change" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "xid", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organization_change_xid_idx", + "entityType": "indexes", + "schema": "public", + "table": "organization_change" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "provider_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "account_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_provider_account_idx", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "invitation_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "invitation_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "identifier", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "verification_identifier_idx", + "entityType": "indexes", + "schema": "public", + "table": "verification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_project_preference_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_project_preference_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_project_preference_project_idx", + "entityType": "indexes", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_canvas_node_position_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_canvas_node_position_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "resource_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "resource_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_canvas_node_position_resource_lookup_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_resource_project_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_resource_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_resource_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lineage_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_resource_lineage_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resource_lineage_project_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "resource_lineage_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "service_project_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "service_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "service_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lineage_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "service_lineage_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "service_lineage_project_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "service_lineage" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "saved_state_snapshot_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_saved_state_snapshot_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" = 'queued' and \"inngest_run_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_one_building_attempt_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" = 'queued' and \"inngest_run_id\" is null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_one_pending_attempt_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" in ('planning','deploying')", + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_one_started_attempt_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "inngest_run_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_inngest_run_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "retry_of_deployment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_retry_of_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "deployment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_build_output_cursor_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "deployment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_deployment_event_cursor_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_saved_state_snapshot_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_saved_state_snapshot_environment_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_config_snapshot_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_config_snapshot_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "node_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "node_lineage_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_config_snapshot_lineage_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_deployment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_config_snapshot_deployment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "node_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "node_lineage_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_introduction_lineage_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "environment_node_introduction_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_project_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" in ('pending', 'running')\n and \"scope\" = 'environment'\n and \"environment_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_one_active_environment_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "project_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" in ('pending', 'running')\n and \"scope\" = 'project'\n and \"project_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_one_active_project_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" in ('pending', 'running')\n and \"scope\" = 'organization'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_one_active_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "inngest_run_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"inngest_run_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "teardown_attempt_inngest_run_uidx", + "entityType": "indexes", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_environment_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_deployment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_deployment_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_resource_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_resource_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "retry_of_attempt_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"retry_of_attempt_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_retry_of_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_resource_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"status\" in ('awaiting_deployment', 'pending', 'running')\n and \"environment_resource_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_one_active_resource_idx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "inngest_run_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"inngest_run_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "volume_remove_attempt_inngest_run_uidx", + "entityType": "indexes", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "token_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "machine_enrollment_token_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "machine_enrollment_token_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "inngest_run_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"inngest_run_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "machine_remove_attempt_inngest_run_uidx", + "entityType": "indexes", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "machine_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"state\" in ('pending', 'running')", + "with": "", + "method": "btree", + "concurrently": false, + "name": "machine_remove_attempt_one_active_org_machine_idx", + "entityType": "indexes", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "machine_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organization_machine_machine_idx", + "entityType": "indexes", + "schema": "public", + "table": "organization_machine" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"is_dial_entry\"", + "with": "", + "method": "btree", + "concurrently": false, + "name": "organization_machine_one_dial_entry_idx", + "entityType": "indexes", + "schema": "public", + "table": "organization_machine" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "repository_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "evaluated_head_sha", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_branch_projection_head_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_branch_projection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "repository_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "head_sha", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_check_suite_projection_head_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"published_revision\" < \"transition_revision\"", + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_check_suite_projection_unpublished_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_environment_trigger_environment_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"publish_state\" = 'pending'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_environment_trigger_pending_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_installation_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_installation_installation_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_repository_cache_installation_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_repository_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_repository_cache_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_repository_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "full_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_repository_cache_full_name_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_repository_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "processing_state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "receipt_sequence", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_webhook_delivery_processing_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "repository_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_webhook_delivery_identity_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "processing_run_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"processing_run_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_webhook_delivery_processing_run_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "account_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "invitation_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": false, + "columns": [ + "inviter_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "invitation_inviter_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "member_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "member_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "session_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id", + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_IqLeTQthFKCX_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "project_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "project" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "user_project_preference_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "user_project_preference_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "user_project_preference_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "user_project_preference_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_canvas_node_position_4ir4xbHMhQ0b_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_canvas_node_position_DP4bUoilqg6D_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_resource_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_resource_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_resource_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "lineage_id" + ], + "schemaTo": "public", + "tableTo": "resource_lineage", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "environment_resource_lineage_id_resource_lineage_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "project_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_resource_krkfQwOLhWDI_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "lineage_id" + ], + "schemaTo": "public", + "tableTo": "resource_lineage", + "columnsTo": [ + "project_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "environment_resource_zQ2aoOGRLIOR_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "resource_lineage_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "resource_lineage_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "lineage_id" + ], + "schemaTo": "public", + "tableTo": "service_lineage", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "service_lineage_id_service_lineage_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "project_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_bvUAv6STak07_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "lineage_id" + ], + "schemaTo": "public", + "tableTo": "service_lineage", + "columnsTo": [ + "project_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "service_1xRYD1MXFhcT_fkey", + "entityType": "fks", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_lineage_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "project_id" + ], + "schemaTo": "public", + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_lineage_project_id_project_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_registry_credential_fC7nGiZZPNsV_fkey", + "entityType": "fks", + "schema": "public", + "table": "service_registry_credential" + }, + { + "nameExplicit": false, + "columns": [ + "service_id" + ], + "schemaTo": "public", + "tableTo": "service", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "service_registry_credential_service_id_service_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "service_registry_credential" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable" + }, + { + "nameExplicit": false, + "columns": [ + "service_id" + ], + "schemaTo": "public", + "tableTo": "service", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_service_id_service_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "service_id" + ], + "schemaTo": "public", + "tableTo": "service", + "columnsTo": [ + "environment_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_dFGv8UXjv6eg_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_secret_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable_secret" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_secret_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable_secret" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "variable_id" + ], + "schemaTo": "public", + "tableTo": "variable", + "columnsTo": [ + "environment_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "variable_secret_fgbg4UfEzo6H_fkey", + "entityType": "fks", + "schema": "public", + "table": "variable_secret" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": false, + "columns": [ + "retry_of_deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "environment_deployment_Lofdv4M9OTIQ_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "saved_state_snapshot_id" + ], + "schemaTo": "public", + "tableTo": "environment_saved_state_snapshot", + "columnsTo": [ + "environment_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "environment_deployment_92k6WdAbc3AZ_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_build_output_es7jBaLp3vH8_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "nameExplicit": false, + "columns": [ + "deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_build_output_Xp68IrJouNSa_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "nameExplicit": false, + "columns": [ + "step_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment_build_step", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_build_output_XF27nu5BhEx1_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_build_output" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_build_step_28D6Rkzs1URj_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "nameExplicit": false, + "columns": [ + "deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_build_step_mAOz3YRgkCxT_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_event_eW8RHtn0mFyI_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "nameExplicit": false, + "columns": [ + "deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_event_nkhfxU66kCFO_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_event" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_image_build_omhRa2gD2UgI_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "nameExplicit": false, + "columns": [ + "deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_image_build_Ss4XYtuD2fFu_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_secret_IuNsdKp3NB8E_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_secret" + }, + { + "nameExplicit": false, + "columns": [ + "environment_deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_deployment_secret_IIXCU5ibjYPq_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_deployment_secret" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_saved_state_snapshot_QseWcaivJkoo_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_saved_state_snapshot_pYS3zmT4RKuQ_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "actor_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "environment_saved_state_snapshot_actor_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "organization_build_order_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organization_build_order" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "core_operation_event_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "core_operation_event" + }, + { + "nameExplicit": false, + "columns": [ + "watch_id" + ], + "schemaTo": "public", + "tableTo": "core_operation_watch", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "core_operation_event_watch_id_core_operation_watch_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "core_operation_event" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "core_operation_watch_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "core_operation_watch" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_config_snapshot_3y0HYOFWmCwQ_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "environment_deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_config_snapshot_CUaPaujMBOIs_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_config_snapshot_D4DPm1gvv2Di_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_config_snapshot_secret_jZo1GvXOHIs5_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "nameExplicit": false, + "columns": [ + "snapshot_id" + ], + "schemaTo": "public", + "tableTo": "environment_node_config_snapshot", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_config_snapshot_secret_j5H7VPFuELrf_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_introduction_aSA9HOqVYGRT_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_introduction_YPW21fX4uIUZ_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_introduction_secret_HaNAhrDo0dtU_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "node_type", + "node_id" + ], + "schemaTo": "public", + "tableTo": "environment_node_introduction", + "columnsTo": [ + "environment_id", + "node_type", + "node_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "environment_node_introduction_secret_sC7ALWFOnE38_fkey", + "entityType": "fks", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "organization_pairing_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organization_pairing" + }, + { + "nameExplicit": false, + "columns": [ + "requested_by_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "teardown_attempt_requested_by_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "volume_remove_attempt_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "requested_by_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "volume_remove_attempt_requested_by_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "volume_remove_attempt_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "environment_deployment_id" + ], + "schemaTo": "public", + "tableTo": "environment_deployment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "volume_remove_attempt_2ncjupSSetid_fkey", + "entityType": "fks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "retry_of_attempt_id" + ], + "schemaTo": "public", + "tableTo": "volume_remove_attempt", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "volume_remove_attempt_FQapB8QhQW1M_fkey", + "entityType": "fks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "enrollment_allocation_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "machine_enrollment_token_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "machine_enrollment_token_created_by_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "machine_remove_attempt_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "requested_by_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "machine_remove_attempt_requested_by_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "organization_machine_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organization_machine" + }, + { + "nameExplicit": false, + "columns": [ + "last_delivery_id", + "last_receipt_sequence" + ], + "schemaTo": "public", + "tableTo": "github_webhook_delivery", + "columnsTo": [ + "delivery_id", + "receipt_sequence" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "github_branch_projection_Udo5xaDQmVxR_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "nameExplicit": false, + "columns": [ + "last_delivery_id", + "last_receipt_sequence" + ], + "schemaTo": "public", + "tableTo": "github_webhook_delivery", + "columnsTo": [ + "delivery_id", + "receipt_sequence" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "github_check_suite_projection_9FLSHaRf6WgI_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_environment_trigger_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id" + ], + "schemaTo": "public", + "tableTo": "environment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_environment_trigger_environment_id_environment_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": false, + "columns": [ + "source_delivery_id", + "source_receipt_sequence" + ], + "schemaTo": "public", + "tableTo": "github_webhook_delivery", + "columnsTo": [ + "delivery_id", + "receipt_sequence" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "github_environment_trigger_dspFptTlur3h_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_installation_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_repository_cache_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_repository_cache" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "organization_billing_state_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organization_billing_state" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "organization_cluster_domain_eJqKfNjJBhUj_fkey", + "entityType": "fks", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "columns": [ + "environment_id", + "node_type", + "node_id" + ], + "nameExplicit": false, + "name": "environment_node_introduction_pkey", + "entityType": "pks", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "columns": [ + "environment_id", + "node_type", + "node_id" + ], + "nameExplicit": false, + "name": "environment_node_introduction_secret_pkey", + "entityType": "pks", + "schema": "public", + "table": "environment_node_introduction_secret" + }, + { + "columns": [ + "organization_id", + "cluster_key" + ], + "nameExplicit": false, + "name": "enrollment_allocation_pkey", + "entityType": "pks", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "columns": [ + "organization_id", + "machine_id" + ], + "nameExplicit": false, + "name": "organization_machine_pkey", + "entityType": "pks", + "schema": "public", + "table": "organization_machine" + }, + { + "columns": [ + "installation_id", + "repository_id", + "ref" + ], + "nameExplicit": false, + "name": "github_branch_projection_pkey", + "entityType": "pks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "columns": [ + "installation_id", + "repository_id", + "check_suite_id" + ], + "nameExplicit": false, + "name": "github_check_suite_projection_pkey", + "entityType": "pks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "columns": [ + "user_id", + "installation_id", + "repository_id" + ], + "nameExplicit": false, + "name": "github_repository_cache_pkey", + "entityType": "pks", + "schema": "public", + "table": "github_repository_cache" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "organization_pkey", + "schema": "public", + "table": "organization", + "entityType": "pks" + }, + { + "columns": [ + "seq" + ], + "nameExplicit": false, + "name": "organization_change_pkey", + "schema": "public", + "table": "organization_change", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pkey", + "schema": "public", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "invitation_pkey", + "schema": "public", + "table": "invitation", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "member_pkey", + "schema": "public", + "table": "member", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pkey", + "schema": "public", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "user_pkey", + "schema": "public", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "verification_pkey", + "schema": "public", + "table": "verification", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_pkey", + "schema": "public", + "table": "environment", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pkey", + "schema": "public", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "user_project_preference_pkey", + "schema": "public", + "table": "user_project_preference", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_canvas_node_position_pkey", + "schema": "public", + "table": "environment_canvas_node_position", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_resource_pkey", + "schema": "public", + "table": "environment_resource", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "resource_lineage_pkey", + "schema": "public", + "table": "resource_lineage", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "service_pkey", + "schema": "public", + "table": "service", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "service_lineage_pkey", + "schema": "public", + "table": "service_lineage", + "entityType": "pks" + }, + { + "columns": [ + "service_id" + ], + "nameExplicit": false, + "name": "service_registry_credential_pkey", + "schema": "public", + "table": "service_registry_credential", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "variable_pkey", + "schema": "public", + "table": "variable", + "entityType": "pks" + }, + { + "columns": [ + "variable_id" + ], + "nameExplicit": false, + "name": "variable_secret_pkey", + "schema": "public", + "table": "variable_secret", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_deployment_pkey", + "schema": "public", + "table": "environment_deployment", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_deployment_build_output_pkey", + "schema": "public", + "table": "environment_deployment_build_output", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_deployment_build_step_pkey", + "schema": "public", + "table": "environment_deployment_build_step", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_deployment_event_pkey", + "schema": "public", + "table": "environment_deployment_event", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_deployment_image_build_pkey", + "schema": "public", + "table": "environment_deployment_image_build", + "entityType": "pks" + }, + { + "columns": [ + "environment_deployment_id" + ], + "nameExplicit": false, + "name": "environment_deployment_secret_pkey", + "schema": "public", + "table": "environment_deployment_secret", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_saved_state_snapshot_pkey", + "schema": "public", + "table": "environment_saved_state_snapshot", + "entityType": "pks" + }, + { + "columns": [ + "organization_id" + ], + "nameExplicit": false, + "name": "organization_build_order_pkey", + "schema": "public", + "table": "organization_build_order", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "core_operation_event_pkey", + "schema": "public", + "table": "core_operation_event", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "core_operation_watch_pkey", + "schema": "public", + "table": "core_operation_watch", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "environment_node_config_snapshot_pkey", + "schema": "public", + "table": "environment_node_config_snapshot", + "entityType": "pks" + }, + { + "columns": [ + "snapshot_id" + ], + "nameExplicit": false, + "name": "environment_node_config_snapshot_secret_pkey", + "schema": "public", + "table": "environment_node_config_snapshot_secret", + "entityType": "pks" + }, + { + "columns": [ + "organization_id" + ], + "nameExplicit": false, + "name": "organization_pairing_pkey", + "schema": "public", + "table": "organization_pairing", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "teardown_attempt_pkey", + "schema": "public", + "table": "teardown_attempt", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "volume_remove_attempt_pkey", + "schema": "public", + "table": "volume_remove_attempt", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "machine_enrollment_token_pkey", + "schema": "public", + "table": "machine_enrollment_token", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "machine_remove_attempt_pkey", + "schema": "public", + "table": "machine_remove_attempt", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "github_environment_trigger_pkey", + "schema": "public", + "table": "github_environment_trigger", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "github_installation_pkey", + "schema": "public", + "table": "github_installation", + "entityType": "pks" + }, + { + "columns": [ + "delivery_id" + ], + "nameExplicit": false, + "name": "github_webhook_delivery_pkey", + "schema": "public", + "table": "github_webhook_delivery", + "entityType": "pks" + }, + { + "columns": [ + "organization_id" + ], + "nameExplicit": false, + "name": "organization_billing_state_pkey", + "schema": "public", + "table": "organization_billing_state", + "entityType": "pks" + }, + { + "columns": [ + "organization_id" + ], + "nameExplicit": false, + "name": "organization_cluster_domain_pkey", + "schema": "public", + "table": "organization_cluster_domain", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organization_slug_unique", + "entityType": "uniques", + "schema": "public", + "table": "organization" + }, + { + "nameExplicit": false, + "columns": [ + "user_id", + "organization_id" + ], + "nullsNotDistinct": false, + "name": "member_user_id_organization_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": false, + "columns": [ + "token" + ], + "nullsNotDistinct": false, + "name": "session_token_unique", + "entityType": "uniques", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "user_email_unique", + "entityType": "uniques", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "name" + ], + "nullsNotDistinct": false, + "name": "environment_project_id_name_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "id" + ], + "nullsNotDistinct": false, + "name": "environment_project_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id", + "namespace" + ], + "nullsNotDistinct": false, + "name": "environment_organization_id_namespace_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id", + "slug" + ], + "nullsNotDistinct": false, + "name": "project_organization_id_slug_unique", + "entityType": "uniques", + "schema": "public", + "table": "project" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id", + "id" + ], + "nullsNotDistinct": false, + "name": "project_organization_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "project" + }, + { + "nameExplicit": false, + "columns": [ + "user_id", + "project_id" + ], + "nullsNotDistinct": false, + "name": "user_project_preference_user_id_project_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "user_project_preference" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "resource_type", + "resource_id" + ], + "nullsNotDistinct": false, + "name": "environment_canvas_node_position_environment_id_resource_type_resource_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_canvas_node_position" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "id" + ], + "nullsNotDistinct": false, + "name": "environment_resource_project_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "id" + ], + "nullsNotDistinct": false, + "name": "environment_resource_environment_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "lineage_id" + ], + "nullsNotDistinct": false, + "name": "environment_resource_environment_id_lineage_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_resource" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "canonical_slug" + ], + "nullsNotDistinct": false, + "name": "resource_lineage_project_id_canonical_slug_unique", + "entityType": "uniques", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "id" + ], + "nullsNotDistinct": false, + "name": "resource_lineage_project_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "resource_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "id" + ], + "nullsNotDistinct": false, + "name": "service_project_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "id" + ], + "nullsNotDistinct": false, + "name": "service_environment_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "lineage_id" + ], + "nullsNotDistinct": false, + "name": "service_environment_id_lineage_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "service" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "canonical_slug" + ], + "nullsNotDistinct": false, + "name": "service_lineage_project_id_canonical_slug_unique", + "entityType": "uniques", + "schema": "public", + "table": "service_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "project_id", + "id" + ], + "nullsNotDistinct": false, + "name": "service_lineage_project_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "service_lineage" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "id" + ], + "nullsNotDistinct": false, + "name": "variable_environment_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "variable" + }, + { + "nameExplicit": true, + "columns": [ + "deployment_id", + "image", + "attempt", + "build", + "key" + ], + "nullsNotDistinct": true, + "name": "environment_deployment_build_step_key_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_deployment_build_step" + }, + { + "nameExplicit": true, + "columns": [ + "deployment_id", + "service_id" + ], + "nullsNotDistinct": false, + "name": "environment_deployment_image_build_service_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "nameExplicit": false, + "columns": [ + "environment_id", + "id" + ], + "nullsNotDistinct": false, + "name": "environment_saved_state_snapshot_environment_id_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_saved_state_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "watch_id", + "sequence" + ], + "nullsNotDistinct": false, + "name": "core_operation_event_watch_id_sequence_unique", + "entityType": "uniques", + "schema": "public", + "table": "core_operation_event" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id", + "operation_id" + ], + "nullsNotDistinct": false, + "name": "core_operation_watch_organization_id_operation_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "core_operation_watch" + }, + { + "nameExplicit": false, + "columns": [ + "environment_deployment_id", + "node_type", + "node_id" + ], + "nullsNotDistinct": false, + "name": "environment_node_config_snapshot_environment_deployment_id_node_type_node_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "nameExplicit": false, + "columns": [ + "installation_id", + "repository_id", + "ref", + "branch_evaluation_revision", + "environment_id" + ], + "nullsNotDistinct": false, + "name": "github_environment_trigger_installation_id_repository_id_ref_branch_evaluation_revision_environment_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "nameExplicit": false, + "columns": [ + "user_id", + "installation_id" + ], + "nullsNotDistinct": false, + "name": "github_installation_user_id_installation_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": false, + "columns": [ + "delivery_id", + "receipt_sequence" + ], + "nullsNotDistinct": false, + "name": "github_webhook_delivery_delivery_id_receipt_sequence_unique", + "entityType": "uniques", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "receipt_sequence" + ], + "nullsNotDistinct": false, + "name": "github_webhook_delivery_receipt_sequence_key", + "schema": "public", + "table": "github_webhook_delivery", + "entityType": "uniques" + }, + { + "value": "\"implementation_type\" in ('volume')", + "name": "environment_resource_implementation_type_check", + "entityType": "checks", + "schema": "public", + "table": "environment_resource" + }, + { + "value": "num_nonnulls(\"encrypted_registry_username\", \"encrypted_registry_secret\") > 0", + "name": "service_registry_credential_nonempty_check", + "entityType": "checks", + "schema": "public", + "table": "service_registry_credential" + }, + { + "value": "(\n (\"status\" = 'cancelled' and \"cancellation_requested_at\" is not null and \"finished_at\" is not null)\n or (\"status\" <> 'cancelled')\n )", + "name": "environment_deployment_cancellation_shape_check", + "entityType": "checks", + "schema": "public", + "table": "environment_deployment" + }, + { + "value": "(\"status\" = 'built') = (\"encrypted_receipt\" is not null)", + "name": "environment_deployment_image_build_receipt_check", + "entityType": "checks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "value": "\"builder\" in ('server', 'github')", + "name": "environment_deployment_image_build_builder_check", + "entityType": "checks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "value": "(\"builder\" = 'github') = (\"github\" is not null and \"github_run_id\" is not null)", + "name": "environment_deployment_image_build_github_check", + "entityType": "checks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "value": "\"checked_in_at\" is null or \"builder\" = 'github'", + "name": "environment_deployment_image_build_check_in_check", + "entityType": "checks", + "schema": "public", + "table": "environment_deployment_image_build" + }, + { + "value": "\"build_order\" in ('servers-only', 'github-then-servers', 'servers-then-github', 'github-only')", + "name": "organization_build_order_check", + "entityType": "checks", + "schema": "public", + "table": "organization_build_order" + }, + { + "value": "\"expected_kind\" in ('deploy','cert','machine_add','machine_update','machine_lifecycle','core_replace','credential_grant','network_repair','service_restart','managed_dns_reconcile','ingress_configure','ingress_refresh','namespace_remove','volume_create','volume_remove')", + "name": "core_operation_watch_expected_kind_check", + "entityType": "checks", + "schema": "public", + "table": "core_operation_watch" + }, + { + "value": "\"cursor_state\" in ('more','caught_up','terminal')", + "name": "core_operation_watch_cursor_state_check", + "entityType": "checks", + "schema": "public", + "table": "core_operation_watch" + }, + { + "value": "\"observation_state\" in ('active','core_terminal','cloud_timeout','cloud_cancelled')", + "name": "core_operation_watch_observation_state_check", + "entityType": "checks", + "schema": "public", + "table": "core_operation_watch" + }, + { + "value": "\"node_type\" in ('service', 'volume')", + "name": "environment_node_config_snapshot_node_type_check", + "entityType": "checks", + "schema": "public", + "table": "environment_node_config_snapshot" + }, + { + "value": "num_nonnulls(\"encrypted_registry_username\", \"encrypted_registry_secret\") > 0", + "name": "environment_node_config_snapshot_secret_nonempty_check", + "entityType": "checks", + "schema": "public", + "table": "environment_node_config_snapshot_secret" + }, + { + "value": "\"node_type\" in ('service', 'volume')", + "name": "environment_node_introduction_node_type_check", + "entityType": "checks", + "schema": "public", + "table": "environment_node_introduction" + }, + { + "value": "\n (\"removal_started_at\" is null and \"removal_endpoints\" is null)\n or (\"removal_started_at\" is not null and \"removal_endpoints\" is not null and jsonb_typeof(\"removal_endpoints\") = 'array')\n ", + "name": "organization_pairing_removal_shape_check", + "entityType": "checks", + "schema": "public", + "table": "organization_pairing" + }, + { + "value": "\"founder_public_key\" is not null or \"founder_machine_id\" is not null", + "name": "organization_pairing_state_check", + "entityType": "checks", + "schema": "public", + "table": "organization_pairing" + }, + { + "value": "\"founder_claim_machine_id\" ~ '^[0-9a-f]{32}$'", + "name": "organization_pairing_founder_claim_machine_id_check", + "entityType": "checks", + "schema": "public", + "table": "organization_pairing" + }, + { + "value": "\"founder_machine_id\" is null or \"founder_machine_id\" ~ '^[0-9a-f]{32}$'", + "name": "organization_pairing_founder_machine_id_check", + "entityType": "checks", + "schema": "public", + "table": "organization_pairing" + }, + { + "value": "\"scope\" in ('environment','project','organization')", + "name": "teardown_attempt_scope_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "\"status\" in ('pending','running','completed','partial','failed','cancelled')", + "name": "teardown_attempt_status_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "(\n (\"scope\" = 'environment' and \"environment_id\" is not null\n and \"project_id\" is not null)\n or (\"scope\" = 'project' and \"project_id\" is not null\n and \"environment_id\" is null)\n or (\"scope\" = 'organization' and \"project_id\" is null\n and \"environment_id\" is null)\n )", + "name": "teardown_attempt_scope_ids_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "jsonb_typeof(\"confirm_data_loss\") = 'array'", + "name": "teardown_attempt_confirm_data_loss_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "jsonb_typeof(\"targets\") = 'object'", + "name": "teardown_attempt_targets_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "(\n (\"status\" = 'pending' and \"inngest_run_id\" is null\n and \"started_at\" is null and \"terminal_at\" is null\n and \"outcome\" is null and \"failure_message\" is null)\n or (\"status\" = 'running' and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is null\n and \"failure_message\" is null)\n or (\"status\" = 'completed' and \"inngest_run_id\" is not null\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"outcome\" is not null and \"failure_message\" is null)\n or (\"status\" = 'partial' and \"inngest_run_id\" is not null\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"outcome\" is not null)\n or (\"status\" in ('failed', 'cancelled')\n and \"inngest_run_id\" is not null\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"failure_message\" is not null\n and length(\"failure_message\") between 1 and 2000)\n )", + "name": "teardown_attempt_status_shape_check", + "entityType": "checks", + "schema": "public", + "table": "teardown_attempt" + }, + { + "value": "\"status\" in ('awaiting_deployment','pending','running','unknown','completed','partial','failed','cancelled')", + "name": "volume_remove_attempt_status_check", + "entityType": "checks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "value": "jsonb_typeof(\"volumes\") = 'array'\n and jsonb_array_length(\"volumes\") >= 1", + "name": "volume_remove_attempt_volumes_check", + "entityType": "checks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "value": "(\n (\"status\" = 'awaiting_deployment'\n and \"environment_deployment_id\" is not null\n and \"inngest_run_id\" is null\n and \"started_at\" is null and \"terminal_at\" is null\n and \"outcome\" is null and \"failure_message\" is null)\n or (\"status\" = 'pending' and \"inngest_run_id\" is null\n and \"started_at\" is null and \"terminal_at\" is null\n and \"outcome\" is null and \"failure_message\" is null)\n or (\"status\" = 'running' and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"terminal_at\" is null\n and \"outcome\" is null and \"failure_message\" is null)\n or (\"status\" = 'unknown' and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"outcome\" is null and \"failure_message\" is not null\n and length(\"failure_message\") between 1 and 2000)\n or (\"status\" = 'completed' and \"inngest_run_id\" is not null\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"outcome\" is not null and \"failure_message\" is null)\n or (\"status\" = 'partial' and \"inngest_run_id\" is not null\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"outcome\" is not null)\n or (\"status\" in ('failed', 'cancelled')\n and \"started_at\" is null and \"terminal_at\" is not null\n and \"outcome\" is null and \"failure_message\" is not null\n and length(\"failure_message\") between 1 and 2000\n and (\n (\"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255)\n or (\"inngest_run_id\" is null\n and \"environment_deployment_id\" is not null)\n ))\n )", + "name": "volume_remove_attempt_status_shape_check", + "entityType": "checks", + "schema": "public", + "table": "volume_remove_attempt" + }, + { + "value": "\"cluster_key\" ~ '^[0-9a-f]{64}$'", + "name": "enrollment_allocation_cluster_key_check", + "entityType": "checks", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "value": "jsonb_typeof(\"assignments\") = 'array'", + "name": "enrollment_allocation_assignments_check", + "entityType": "checks", + "schema": "public", + "table": "enrollment_allocation" + }, + { + "value": "\"token_hash\" ~ '^[0-9a-f]{64}$'", + "name": "machine_enrollment_token_hash_check", + "entityType": "checks", + "schema": "public", + "table": "machine_enrollment_token" + }, + { + "value": "length(\"machine_id\") between 1 and 64 and \"machine_id\" !~ '[[:cntrl:]]'", + "name": "machine_remove_attempt_machine_id_check", + "entityType": "checks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "value": "jsonb_typeof(\"confirm_data_loss\") = 'array'", + "name": "machine_remove_attempt_confirm_data_loss_check", + "entityType": "checks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "value": "\"state\" in ('pending','running','succeeded','failed','cancelled','missing_identities')", + "name": "machine_remove_attempt_state_check", + "entityType": "checks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "value": "(\n (\"state\" = 'pending' and \"inngest_run_id\" is null\n and \"started_at\" is null and \"terminal_at\" is null\n and \"failure_code\" is null and \"failure_message\" is null\n and \"missing_identities\" is null)\n or (\"state\" = 'running' and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is null\n and \"failure_code\" is null and \"failure_message\" is null\n and \"missing_identities\" is null)\n or (\"state\" = 'succeeded' and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"failure_code\" is null and \"failure_message\" is null\n and \"missing_identities\" is null)\n or (\"state\" in ('failed','cancelled')\n and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"failure_code\" is not null and \"failure_message\" is not null\n and \"failure_code\" ~ '^[a-z][a-z0-9_]{0,63}$'\n and length(\"failure_message\") between 1 and 1024\n and \"missing_identities\" is null)\n or (\"state\" = 'missing_identities'\n and \"inngest_run_id\" is not null\n and length(\"inngest_run_id\") between 1 and 255\n and \"started_at\" is not null and \"terminal_at\" is not null\n and \"failure_code\" is null and \"failure_message\" is null\n and jsonb_typeof(\"missing_identities\") = 'array'\n and jsonb_array_length(\"missing_identities\") > 0)\n )", + "name": "machine_remove_attempt_state_shape_check", + "entityType": "checks", + "schema": "public", + "table": "machine_remove_attempt" + }, + { + "value": "\"cluster_key\" ~ '^[0-9a-f]{64}$'", + "name": "organization_machine_cluster_key_check", + "entityType": "checks", + "schema": "public", + "table": "organization_machine" + }, + { + "value": "\"machine_id\" ~ '^[0-9a-f]{32}$'", + "name": "organization_machine_id_format_check", + "entityType": "checks", + "schema": "public", + "table": "organization_machine" + }, + { + "value": "\"installation_id\" > 0 and \"repository_id\" > 0 and \"last_receipt_sequence\" > 0 and \"evaluation_revision\" > 0", + "name": "github_branch_projection_identity_check", + "entityType": "checks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "value": "\"ref\" like 'refs/heads/%' and length(\"ref\") > length('refs/heads/')", + "name": "github_branch_projection_ref_check", + "entityType": "checks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "value": "\"state\" in ('active','deleted')", + "name": "github_branch_projection_state_check", + "entityType": "checks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "value": "\"evaluation_reason\" in ('first_observation','changed_paths','rebaseline_all_services','branch_deleted')", + "name": "github_branch_projection_evaluation_reason_check", + "entityType": "checks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "value": "(\n (\"state\" = 'active' and \"evaluated_head_sha\" ~ '^[0-9a-f]{40}$' and \"evaluation_reason\" != 'branch_deleted')\n or (\"state\" = 'deleted' and \"evaluated_head_sha\" is null and \"evaluation_reason\" = 'branch_deleted')\n )", + "name": "github_branch_projection_head_check", + "entityType": "checks", + "schema": "public", + "table": "github_branch_projection" + }, + { + "value": "\"installation_id\" > 0 and \"repository_id\" > 0 and \"check_suite_id\" > 0 and \"last_receipt_sequence\" > 0", + "name": "github_check_suite_projection_identity_check", + "entityType": "checks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "value": "\"head_sha\" ~ '^[0-9a-f]{40}$'", + "name": "github_check_suite_projection_head_sha_check", + "entityType": "checks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "value": "\"status\" in ('queued','in_progress','completed','pending','waiting','requested')", + "name": "github_check_suite_projection_status_check", + "entityType": "checks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "value": "\"conclusion\" is null or \"conclusion\" in ('action_required','cancelled','failure','neutral','success','skipped','stale','timed_out','startup_failure')", + "name": "github_check_suite_projection_conclusion_check", + "entityType": "checks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "value": "\"transition_revision\" >= 1 and \"published_revision\" >= 0 and \"published_revision\" <= \"transition_revision\"", + "name": "github_check_suite_projection_revision_check", + "entityType": "checks", + "schema": "public", + "table": "github_check_suite_projection" + }, + { + "value": "\"installation_id\" > 0 and \"repository_id\" > 0 and \"source_receipt_sequence\" > 0 and \"branch_evaluation_revision\" > 0", + "name": "github_environment_trigger_identity_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "\"ref\" like 'refs/heads/%' and length(\"ref\") > length('refs/heads/')", + "name": "github_environment_trigger_ref_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "\"head_sha\" ~ '^[0-9a-f]{40}$'", + "name": "github_environment_trigger_head_sha_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "cardinality(\"service_ids\") > 0 and array_position(\"service_ids\", null) is null", + "name": "github_environment_trigger_service_ids_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "\"selection_mode\" in ('paths','all_services')", + "name": "github_environment_trigger_selection_mode_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "\"reason\" in ('first_observation','changed_paths','force_rebaseline','non_ancestor_rebaseline','changed_paths_incomplete_rebaseline')", + "name": "github_environment_trigger_reason_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "(\n (\"selection_mode\" = 'paths' and \"reason\" = 'changed_paths')\n or (\"selection_mode\" = 'all_services' and \"reason\" in ('first_observation','force_rebaseline','non_ancestor_rebaseline','changed_paths_incomplete_rebaseline'))\n )", + "name": "github_environment_trigger_selection_reason_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "\"trigger_revision\" = \"branch_evaluation_revision\" and \"published_revision\" >= 0 and \"published_revision\" <= \"trigger_revision\"", + "name": "github_environment_trigger_publish_revision_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "(\n (\"publish_state\" = 'pending' and \"published_revision\" < \"trigger_revision\" and \"published_at\" is null)\n or (\"publish_state\" = 'published' and \"published_revision\" = \"trigger_revision\" and \"published_at\" is not null)\n )", + "name": "github_environment_trigger_publish_state_check", + "entityType": "checks", + "schema": "public", + "table": "github_environment_trigger" + }, + { + "value": "length(trim(\"delivery_id\")) > 0 and \"receipt_sequence\" > 0 and (\"installation_id\" is null or \"installation_id\" > 0) and (\"repository_id\" is null or \"repository_id\" > 0) and (\"check_suite_id\" is null or \"check_suite_id\" > 0)", + "name": "github_webhook_delivery_identity_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"event_kind\" in ('push','check_suite')", + "name": "github_webhook_delivery_event_kind_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"processing_state\" in ('received','processing','processed','rejected','failed','cancelled')", + "name": "github_webhook_delivery_processing_state_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"outcome\" is null or \"outcome\" in ('branch_projected','branch_deleted','branch_rebased_all_services','check_suite_projected','check_suite_unchanged','ignored_stale','ignored_unconfigured_repository','ignored_no_matching_service','malformed','identity_unresolved','unsupported_action','processing_failed','cancelled')", + "name": "github_webhook_delivery_outcome_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"branch_state\" is null or \"branch_state\" in ('active','deleted')", + "name": "github_webhook_delivery_branch_state_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"failure_code\" is null or \"failure_code\" in ('malformed_payload','identity_unresolved','unsupported_action','observation_failed','persistence_failed','publication_failed','retry_exhausted','unexpected_error','inngest_cancelled')", + "name": "github_webhook_delivery_failure_code_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"check_suite_action\" is null or \"check_suite_action\" in ('requested','rerequested','completed')", + "name": "github_webhook_delivery_check_suite_action_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"check_suite_status\" is null or \"check_suite_status\" in ('queued','in_progress','completed','pending','waiting','requested')", + "name": "github_webhook_delivery_check_suite_status_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"check_suite_conclusion\" is null or \"check_suite_conclusion\" in ('action_required','cancelled','failure','neutral','success','skipped','stale','timed_out','startup_failure')", + "name": "github_webhook_delivery_check_suite_conclusion_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "\"head_sha\" is null or \"head_sha\" ~ '^[0-9a-f]{40}$'", + "name": "github_webhook_delivery_head_sha_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "(\n (\"processing_state\" = 'received' and \"processing_run_id\" is null and \"processing_started_at\" is null and \"outcome\" is null and \"failure_code\" is null and \"processed_at\" is null)\n or\n (\"processing_state\" = 'processing' and \"processing_run_id\" is not null and \"processing_started_at\" is not null and \"outcome\" is null and \"failure_code\" is null and \"processed_at\" is null)\n or\n (\"processing_state\" = 'processed' and \"processing_run_id\" is not null and \"processing_started_at\" is not null and \"outcome\" in ('branch_projected','branch_deleted','branch_rebased_all_services','check_suite_projected','check_suite_unchanged','ignored_stale','ignored_unconfigured_repository','ignored_no_matching_service') and \"failure_code\" is null and \"processed_at\" is not null)\n or\n (\"processing_state\" = 'rejected' and (\n (\"outcome\" = 'malformed' and \"failure_code\" = 'malformed_payload')\n or (\"outcome\" = 'identity_unresolved' and \"failure_code\" = 'identity_unresolved')\n or (\"outcome\" = 'unsupported_action' and \"failure_code\" = 'unsupported_action')\n ) and \"processing_run_id\" is null and \"processing_started_at\" is null and \"processed_at\" is not null)\n or\n (\"processing_state\" = 'failed' and \"processing_run_id\" is not null and \"processing_started_at\" is not null and \"outcome\" = 'processing_failed' and \"failure_code\" in ('observation_failed','persistence_failed','publication_failed','retry_exhausted','unexpected_error') and \"processed_at\" is not null)\n or\n (\"processing_state\" = 'cancelled' and \"processing_run_id\" is not null and \"processing_started_at\" is not null and \"outcome\" = 'cancelled' and \"failure_code\" = 'inngest_cancelled' and \"processed_at\" is not null)\n )", + "name": "github_webhook_delivery_processing_evidence_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "(\n \"processing_state\" not in ('processing','processed','failed','cancelled')\n or (\n \"installation_id\" is not null\n and \"repository_id\" is not null\n and (\n (\n \"event_kind\" = 'push'\n and \"ref\" is not null\n and \"ref\" like 'refs/heads/%'\n and \"branch_state\" is not null\n and \"check_suite_id\" is null\n and \"check_suite_action\" is null\n and \"check_suite_status\" is null\n and \"check_suite_conclusion\" is null\n and (\n (\"branch_state\" = 'active' and \"head_sha\" is not null)\n or (\"branch_state\" = 'deleted' and \"head_sha\" is null)\n )\n )\n or (\n \"event_kind\" = 'check_suite'\n and \"branch_state\" is null\n and \"head_sha\" is not null\n and \"check_suite_id\" is not null\n and \"check_suite_action\" is not null\n and \"check_suite_status\" is not null\n )\n )\n )\n )", + "name": "github_webhook_delivery_processed_summary_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "(\n \"outcome\" not in ('branch_projected','branch_deleted','branch_rebased_all_services','check_suite_projected','check_suite_unchanged')\n or (\"outcome\" in ('branch_projected','branch_deleted','branch_rebased_all_services') and \"event_kind\" = 'push')\n or (\"outcome\" in ('check_suite_projected','check_suite_unchanged') and \"event_kind\" = 'check_suite')\n )", + "name": "github_webhook_delivery_outcome_kind_check", + "entityType": "checks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "value": "(\n \"has_active_subscription\" = false\n OR (\n \"active_subscription_id\" IS NOT NULL\n AND \"current_period_end\" IS NOT NULL\n )\n )", + "name": "organization_billing_state_active_fields_check", + "entityType": "checks", + "schema": "public", + "table": "organization_billing_state" + }, + { + "value": "\"traffic\" is null or \"traffic\"->>'kind' in ('no_servers', 'no_public_ip', 'probed')", + "name": "organization_cluster_domain_traffic_check", + "entityType": "checks", + "schema": "public", + "table": "organization_cluster_domain" + }, + { + "value": "num_nulls(\"encrypted_certificate_private_key\", \"certificate_chain\", \"certificate_not_after\") in (0, 3)", + "name": "organization_cluster_domain_certificate_check", + "entityType": "checks", + "schema": "public", + "table": "organization_cluster_domain" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/dashboard/src/components/deployment-logs.test.tsx b/dashboard/src/components/deployment-logs.test.tsx index 2d8c2085ba..903ae813f0 100644 --- a/dashboard/src/components/deployment-logs.test.tsx +++ b/dashboard/src/components/deployment-logs.test.tsx @@ -9,7 +9,7 @@ const deploymentId = "8f79e99b-cd08-4e9c-af96-f3fed313acc5"; const organizationId = "6c0b2f4e-7a1d-4f3e-9b8a-2d5c1e0f9a7b"; const at = (seconds: number) => new Date(Date.UTC(2026, 8, 22, 21, 9, seconds)); const step = (id: number, name: string, extra: Partial = {}): BuildStepRow => ({ - id, organizationId, deploymentId, image: null, build: 1, key: `sha256:${id}`, name, startedAt: at(id), completedAt: null, cached: false, error: null, createdAt: at(id), updatedAt: at(id), ...extra, + id, organizationId, deploymentId, image: null, attempt: 0, build: 1, key: `sha256:${id}`, name, startedAt: at(id), completedAt: null, cached: false, error: null, createdAt: at(id), updatedAt: at(id), ...extra, }); const line = (id: number, stepId: number, text: string, stderr = false): BuildOutputRow => ({ id, organizationId, deploymentId, stepId, stderr, text, createdAt: at(id) }); @@ -92,6 +92,23 @@ it("keeps one Image Build's runs, not other images or the attempt-wide cleanup a expect(imageBuildSteps([step(1, "Uploading source", { build: 0, key: "stage:Upload", error: "upload failed" }), ...steps.slice(1)], "worker").map((row) => row.id)).toEqual([1, 4, 5]); }); +it("tells each Builder's go as a plain line above its steps", () => { + const html = renderToStaticMarkup(createElement(BuildLogs, { + finished: false, output: [], now: at(9).getTime(), + steps: [ + step(1, "GitHub Actions", { image: "web", key: "stage:Builder", completedAt: at(1) }), step(2, "Waiting for a runner", { image: "web", key: "runner", completedAt: at(4) }), + step(5, "hel-1", { image: "web", attempt: 1, key: "stage:Builder", completedAt: at(5) }), step(6, "Uploading source", { image: "web", attempt: 1, key: "stage:Upload" }), + ], + evidence: { image: "web", serverChoice: { machineName: "hel-1", reason: { kind: "spread" } }, github: null, skips: [{ builder: "github", kind: "not_started", minutes: 3 }] }, + })); + expect(html).toContain("Building on GitHub Actions"); + expect(html).toContain("No GitHub runner started within 3 min. Building on hel-1 instead."); + // Routine choices ("spread") never show; the moved-on run's link is gone with it. + expect(html).not.toContain("spread"); + expect(html).not.toContain("View run"); + expect(html.indexOf("Waiting for a runner")).toBeLessThan(html.indexOf("Building on hel-1")); +}); + it("splits BuildKit step names, rounds durations like Railway, and strips terminal sequences", () => { expect(splitStepName("[sdk 4/6] COPY core/ core/")).toEqual({ stage: "sdk", title: "COPY core/ core/" }); expect(splitStepName("[internal] load build definition from Dockerfile")).toEqual({ stage: "internal", title: "load build definition from Dockerfile" }); diff --git a/dashboard/src/components/deployment-logs.tsx b/dashboard/src/components/deployment-logs.tsx index 2fb1c769a6..f2e22a5a6d 100644 --- a/dashboard/src/components/deployment-logs.tsx +++ b/dashboard/src/components/deployment-logs.tsx @@ -4,12 +4,11 @@ import { useCollectionScope } from "#/collections/use-collection-scope"; import { getDeploymentLogsCollection, useDeploymentLogsReadState } from "#/modules/deployments/deployment-log.collection"; import { progressRowLabel, type DeploymentProgress } from "#/modules/deployments/deployment-progress"; import { Button } from "#/components/ui/button"; -import { Item, ItemActions, ItemContent, ItemDescription } from "#/components/ui/item"; import { Spinner } from "#/components/ui/spinner"; import { CheckIcon, TriangleAlertIcon } from "lucide-react"; import { useBuildLog, type BuildOutputRow, type BuildStepRow } from "#/modules/deployments/deployment-build-log.queries"; import { BUILDING_KEY, CLEANUP_KEY } from "#/modules/deployments/preparation-progress"; -import { builtOn, builtOnLine, imageBuildSteps, stripAnsi } from "#/modules/deployments/deployment-view"; +import { buildLogSections, imageBuildSteps, stripAnsi, type ImageBuildEvidence } from "#/modules/deployments/deployment-view"; import { ContainerLogs } from "./container-logs"; import type { ContainerLogRow } from "#/modules/runtime/container-log.collection"; import { BuildLogViewer } from "./log-scroll"; @@ -44,13 +43,15 @@ const lastLine = (rows: readonly BuildOutputRow[]) => { return lines.at(-1) ?? null; }; -export function BuildLogs({ steps, output, finished, now = Date.now() }: { - steps: readonly BuildStepRow[]; output: readonly BuildOutputRow[]; finished: boolean; now?: number; +/** One Image Build's log: a section per Builder's go, each a plain line and then its steps. */ +export function BuildLogs({ steps, output, finished, evidence, now = Date.now() }: { + steps: readonly BuildStepRow[]; output: readonly BuildOutputRow[]; finished: boolean; evidence?: ImageBuildEvidence; now?: number; }) { // Rows the user toggled; failed rows open by default until toggled. const [toggled, setToggled] = useState>(new Map()); + const sections = buildLogSections(steps, evidence); const started = steps.filter((step) => step.startedAt !== null); - if (!started.length) { + if (!sections.some((section) => section.title !== null) && !started.length) { return

{finished ? "No retained build output for this image." : "Waiting for the build to start"}

; } const outputByStep = new Map(); @@ -61,12 +62,21 @@ export function BuildLogs({ steps, output, finished, now = Date.now() }: { // One attempt may run BuildKit several times; the run's heading matters only then, or when it failed. const runs = new Set(started.map((step) => step.build).filter((build) => build > 0)).size; const failedRuns = new Set(steps.filter((step) => step.error !== null).map((step) => step.build)); - const shown = started.filter((step) => step.error !== null || (step.key !== CLEANUP_KEY && (step.key !== BUILDING_KEY || runs > 1 || failedRuns.has(step.build)))); + const shown = (section: readonly BuildStepRow[]) => section.filter((step) => step.startedAt !== null + && (step.error !== null || (step.key !== CLEANUP_KEY && (step.key !== BUILDING_KEY || runs > 1 || failedRuns.has(step.build))))); + const heading = "mt-2 flex items-center gap-3 px-1 font-medium"; return
    - {shown.map((step) => step.key === BUILDING_KEY && step.error === null - ?
  1. Building {step.name}
  2. - : setToggled((previous) => previous.get(step.id) === open ? previous : new Map(previous).set(step.id, open))} />)} + {sections.flatMap((section, index) => [ + section.title === null ? [] : [
  3. + + {section.title} + {section.runUrl ? : null} +
  4. ], + shown(section.steps).map((step) => step.key === BUILDING_KEY && step.error === null + ?
  5. Building {step.name}
  6. + : setToggled((previous) => previous.get(step.id) === open ? previous : new Map(previous).set(step.id, open))} />), + ].flat())}
; } @@ -126,17 +136,11 @@ export function ServiceBuildLogs({ organizationSlug, deploymentId, image }: { or const now = useNow(build.data?.finished === false); const steps = imageBuildSteps(build.data?.steps ?? [], image); const ids = new Set(steps.map((step) => step.id)); - const server = builtOn(build.data, image); return <> {build.isError ?

Could not load build logs.

: null} - {server ? - {builtOnLine(server)} - {server.server !== null && server.runUrl ? - - : null} - : null} - {build.isPending ?

Loading logs…

: ids.has(row.stepId))} finished={build.data?.finished ?? true} now={now} />} + {build.isPending ?

Loading logs…

: ids.has(row.stepId))} + finished={build.data?.finished ?? true} evidence={build.data?.imageBuilds.find((row) => row.image === image)} now={now} />}
; } diff --git a/dashboard/src/modules/deployments/deployment-events.server.ts b/dashboard/src/modules/deployments/deployment-events.server.ts index 1b0b0c84ee..43b91f658a 100644 --- a/dashboard/src/modules/deployments/deployment-events.server.ts +++ b/dashboard/src/modules/deployments/deployment-events.server.ts @@ -7,7 +7,6 @@ import { NotFound } from "#/server/public-error"; import { environmentDeployment, environmentDeploymentBuildOutput, environmentDeploymentBuildStep, environmentDeploymentEvent, environmentDeploymentImageBuild } from "./tables"; import type { BuildOutputWrite, BuildStepWrite } from "./preparation-progress"; import type { ImageBuildEvidence } from "./deployment-view"; -import type { CandidateReason } from "./image-build"; /** Logs are fetched only when opened. Live progress is the latest event; terminal progress lives on the deployment. */ export const loadDeploymentEvents = Effect.fn("Deployments.events")(function* (input: { @@ -48,42 +47,47 @@ export const loadDeploymentBuildLog = Effect.fn("Deployments.buildLog")(function drizzle.select({ id: table.id, rank: sql`row_number() over (partition by ${table.stepId} order by ${table.id} desc)`.as("rank") }) .from(table).where(eq(table.deploymentId, input.deploymentId)).as("ranked"), ).where(sql`ranked.rank <= ${input.tail}`))).orderBy(asc(table.id)); - // Which Server (or GitHub run) each image builds on, why, and what it skipped; never the receipt, grant or run state. + // Which Server (or GitHub run) each image builds on and what it skipped; never the receipt, grant or run state. const builds = environmentDeploymentImageBuild; const imageBuilds: ImageBuildEvidence[] = yield* drizzle.select({ image: builds.image, serverChoice: builds.serverChoice, skips: builds.skips, - runUrl: sql`${builds.github} ->> 'runUrl'`, reason: sql`${builds.github} ->> 'reason'`, - }).from(builds).where(eq(builds.deploymentId, input.deploymentId)).pipe(Effect.map((rows) => rows.map(({ runUrl, reason, ...row }) => - ({ ...row, github: runUrl && reason ? { runUrl, reason } : null })))); + runUrl: sql`${builds.github} ->> 'runUrl'`, + }).from(builds).where(eq(builds.deploymentId, input.deploymentId)).pipe(Effect.map((rows) => rows.map(({ runUrl, ...row }) => + ({ ...row, github: runUrl ? { runUrl } : null })))); const last = output.at(-1); return { steps, output, imageBuilds, finished: deployment.finishedAt !== null, nextSequence: input.tail === undefined && output.length === input.limit && last ? String(last.id) : null }; }); /** * Upsert steps by key and append output. Output may precede its step; a placeholder holds its place. - * Each Image Build files its steps under its image; the deploy step's own preparation under none. + * Each Image Build files its steps under its image, in the section of the Builder that holds it now + * unless `attempt` names one; the deploy step's own preparation under none. */ -export const persistBuildLog = Effect.fn("Deployments.persistBuildLog")(function* (deploymentId: string, writes: { steps: readonly BuildStepWrite[]; output: readonly BuildOutputWrite[] }, image: string | null = null) { +export const persistBuildLog = Effect.fn("Deployments.persistBuildLog")(function* (deploymentId: string, writes: { steps: readonly BuildStepWrite[]; output: readonly BuildOutputWrite[] }, image: string | null = null, attempt?: number) { if (!writes.steps.length && !writes.output.length) return; const database = yield* Database; + const builds = environmentDeploymentImageBuild; + const attemptOf = (of: string | null, given?: number) => of === null ? 0 : given ?? sql`coalesce((select jsonb_array_length(${builds.skips}) from ${builds} where ${builds.deploymentId} = ${deploymentId} and ${builds.image} = ${of}), 0)`; yield* database.transaction(Effect.gen(function* () { const { drizzle } = yield* Database; const table = environmentDeploymentBuildStep; - const target = [table.deploymentId, table.image, table.build, table.key]; + const target = [table.deploymentId, table.image, table.attempt, table.build, table.key]; const organizationId = organizationIdForDeployment(deploymentId); const ids = new Map(); if (writes.steps.length) { const excluded = (column: { name: string }) => sql.raw(`excluded."${column.name}"`); - const steps = new Map(writes.steps.map((step) => [`${step.build}:${step.key}`, step])); - const upserted = yield* drizzle.insert(table).values([...steps.values()].map((step) => ({ organizationId, deploymentId, image, ...step }))) + const steps = new Map(writes.steps.map((step) => [`${step.image ?? image}:${step.build}:${step.key}`, step])); + const upserted = yield* drizzle.insert(table).values([...steps.values()].map(({ image: own, ...step }) => own === undefined + ? { organizationId, deploymentId, image, attempt: attemptOf(image, attempt), ...step } + : { organizationId, deploymentId, image: own, attempt: attemptOf(own), ...step })) .onConflictDoUpdate({ target, set: { name: excluded(table.name), startedAt: excluded(table.startedAt), completedAt: excluded(table.completedAt), cached: excluded(table.cached), error: excluded(table.error), updatedAt: new Date() } }) - .returning({ id: table.id, build: table.build, key: table.key }); - for (const step of upserted) ids.set(`${step.build}:${step.key}`, step.id); + .returning({ id: table.id, image: table.image, build: table.build, key: table.key }); + for (const step of upserted) if (step.image === image) ids.set(`${step.build}:${step.key}`, step.id); } if (!writes.output.length) return; const unknown = new Map(writes.output.filter((row) => !ids.has(`${row.build}:${row.step}`)).map((row) => [`${row.build}:${row.step}`, row])); if (unknown.size) { - const found = yield* drizzle.insert(table).values([...unknown.values()].map((row) => ({ organizationId, deploymentId, image, build: row.build, key: row.step, name: row.step }))) + const found = yield* drizzle.insert(table).values([...unknown.values()].map((row) => ({ organizationId, deploymentId, image, attempt: attemptOf(image, attempt), build: row.build, key: row.step, name: row.step }))) .onConflictDoUpdate({ target, set: { key: table.key } }) .returning({ id: table.id, build: table.build, key: table.key }); for (const step of found) ids.set(`${step.build}:${step.key}`, step.id); diff --git a/dashboard/src/modules/deployments/deployment-view.test.ts b/dashboard/src/modules/deployments/deployment-view.test.ts index ae47e4c84f..d38e5deed7 100644 --- a/dashboard/src/modules/deployments/deployment-view.test.ts +++ b/dashboard/src/modules/deployments/deployment-view.test.ts @@ -3,7 +3,7 @@ import type { ContainerId, DeployOperation, MachineId, OperationRow } from "@plo import { resolvedServiceSpecFixture } from "#/modules/runtime/runtime-watch-frame.test-fixture"; import { canonicalJson } from "#/modules/environment-design/canonical-json"; import { parseServiceConfig } from "@ployz/sdk/config"; -import { builtOnLine, deploymentProgressForEvent, deploymentStatusLabel, deploymentView, type AttemptTargetNode, type DeploymentViewInput } from "./deployment-view"; +import { buildLogSections, deploymentProgressForEvent, deploymentStatusLabel, deploymentView, type AttemptTargetNode, type DeploymentViewInput } from "./deployment-view"; function row(index: number, operation?: DeployOperation): OperationRow { const spec = resolvedServiceSpecFixture(); @@ -14,8 +14,8 @@ function row(index: number, operation?: DeployOperation): OperationRow { /** The Engine serializes keys alphabetically, unlike the planned rows. */ const engineOrdered = (value: T): T => JSON.parse(canonicalJson(value)) as T; const context = { serviceIdFor: (name: string | null) => name }; -const step = (id: number, build: number, key: string, name: string, start: number, end: number | null, error: string | null = null, image: string | null = null) => - ({ id, image, build, key, name, startedAt: new Date(start * 1000), completedAt: end === null ? null : new Date(end * 1000), error }); +const step = (id: number, build: number, key: string, name: string, start: number, end: number | null, error: string | null = null, image: string | null = null, attempt = 0) => + ({ id, image, attempt, build, key, name, startedAt: new Date(start * 1000), completedAt: end === null ? null : new Date(end * 1000), error }); const deployment = (status: DeploymentViewInput["deployment"]["status"], extra: Partial = {}): DeploymentViewInput["deployment"] => ({ status, failureMessage: null, deployPreview: null, ...extra }); /** A service in the Attempt Target; `image` makes it a built one. */ @@ -97,43 +97,49 @@ describe("deployment view projection", () => { expect(deploymentStatusLabel(view)).toBe("Failed · 0 of 4 deployed"); }); - it("says which Server each image builds on and why, only once the Engine chose", () => { + it("names only the Builder that holds each image's build, never why it was chosen", () => { const view = deploymentView({ deployment: deployment("queued"), progress: null, - nodes: ["api", "web", "docs", "worker", "site", "blog", "wiki", "shop", "mail", "cron"].map((image) => node({ nodeId: image, changed: true, image })), + nodes: ["api", "site", "worker"].map((image) => node({ nodeId: image, changed: true, image })), buildLog: { steps: [], output: [], imageBuilds: [ - { image: "site", serverChoice: null, github: { runUrl: "https://github.com/o/r/actions/runs/1", reason: "first_in_build_order" }, skips: [] }, + { image: "site", serverChoice: null, github: { runUrl: "https://github.com/o/r/actions/runs/1" }, skips: [] }, { image: "api", serverChoice: { machineName: "nuc", reason: { kind: "had_cache" } }, github: null, skips: [] }, - { image: "web", serverChoice: { machineName: "hel-1", reason: { kind: "spread" } }, github: null, skips: [] }, - { image: "docs", serverChoice: { machineName: "hel-1", reason: { kind: "cache_holder_unavailable", holder: "c".repeat(32) as MachineId, name: "nuc" } }, github: null, skips: [] }, - { image: "worker", serverChoice: null, github: null, skips: [] }, - { image: "blog", serverChoice: null, github: { runUrl: "https://github.com/o/r/actions/runs/2", reason: "next_in_build_order" }, skips: [{ builder: "servers", kind: "not_started", minutes: 3 }] }, - { image: "wiki", serverChoice: null, github: null, skips: [{ builder: "github", kind: "no_workflow", repository: "o/r" }, { builder: "servers", kind: "preferred_unavailable", machineId: "e".repeat(32) as MachineId, name: null }] }, - { image: "shop", serverChoice: { machineName: "fast", reason: { kind: "preferred" } }, github: null, skips: [] }, - { image: "mail", serverChoice: null, github: { runUrl: "https://github.com/o/r/actions/runs/3", reason: "preferred" }, skips: [] }, - { image: "cron", serverChoice: { machineName: "hel-1", reason: { kind: "preferred_unavailable", preferred: "d".repeat(32) as MachineId, name: null } }, github: null, skips: [] }, + { image: "worker", serverChoice: null, github: null, skips: [{ builder: "github", kind: "no_workflow", repository: "o/r" }] }, ], }, }); - expect(view.nodes.map((n) => n.builtOn)).toEqual([ - { server: "nuc", reason: "had this Service's build cache", skipped: [] }, - { server: "hel-1", reason: "spread across Servers", skipped: [] }, - { server: "hel-1", reason: "nuc has the cache but is offline or no longer builds", skipped: [] }, - null, - { server: "GitHub Actions", reason: "first in the build order", runUrl: "https://github.com/o/r/actions/runs/1", skipped: [] }, - { server: "GitHub Actions", reason: "next in the build order", runUrl: "https://github.com/o/r/actions/runs/2", skipped: ["Your servers: none started it in 3 min"] }, - { server: null, reason: null, skipped: ["GitHub: no workflow in o/r", "Preferred server: no longer in the Cluster"] }, - { server: "fast", reason: "preferred builder", skipped: [] }, - { server: "GitHub Actions", reason: "preferred builder", runUrl: "https://github.com/o/r/actions/runs/3", skipped: [] }, - { server: "hel-1", reason: "Preferred server: no longer in the Cluster", skipped: [] }, - ]); - // The skip trail reads after the Builder that took the build, or alone before one did. - expect(view.nodes.slice(5, 7).map((n) => n.builtOn && builtOnLine(n.builtOn))).toEqual([ - "Built on GitHub Actions · next in the build order · skipped Your servers: none started it in 3 min", - "Skipped GitHub: no workflow in o/r · skipped Preferred server: no longer in the Cluster", + expect(view.nodes.map((n) => n.builtOn)).toEqual(["nuc", "GitHub Actions", null]); + }); + + it("tells one timeline per Image Build: a section per Builder, each move one sentence", () => { + const github = [ + step(1, 0, "stage:Builder", "GitHub Actions", 0, 0, null, "web", 1), step(2, 0, "runner", "Waiting for a runner", 0, 20, null, "web", 1), + step(3, 1, "stage:Building", "web", 20, 60, null, "web", 1), + ]; + const hel1 = step(4, 0, "stage:Builder", "hel-1", 61, 61, null, "web", 2); + const servers = [hel1, step(5, 1, "stage:Building", "web", 62, null, null, "web", 2)]; + const steps = [...github, ...servers]; + const evidence = { image: "web", github: null, serverChoice: { machineName: "hel-1", reason: { kind: "spread" as const } }, skips: [ + { builder: "servers" as const, kind: "preferred_unavailable" as const, machineId: "e".repeat(32) as MachineId, name: "hel-2" }, + { builder: "github" as const, kind: "runner_stopped" as const }, + ] }; + expect(buildLogSections(steps, evidence).map(({ title, steps: own }) => [title, own.map((row) => row.id)])).toEqual([ + ["Your preferred server hel-2 is offline or no longer builds. Building on GitHub Actions instead.", [2, 3]], + ["GitHub couldn't finish this build: its runner stopped. Building on hel-1 instead.", [5]], ]); + // A Builder skipped before it wrote a step leaves its sentence alone; the first section names its Builder plainly. + expect(buildLogSections([step(1, 0, "stage:Builder", "hel-1", 0, 0, null, "web", 1)], { ...evidence, skips: [{ builder: "github", kind: "no_workflow", repository: "o/r" }] }) + .map((section) => section.title)).toEqual(["o/r has no Ployz build workflow. Building on hel-1 instead."]); + expect(buildLogSections([step(1, 0, "stage:Builder", "GitHub Actions", 0, 0, null, "web")], { ...evidence, serverChoice: null, skips: [] }) + .map((section) => section.title)).toEqual(["Building on GitHub Actions"]); + // Only the last Builder's go decides the node: GitHub's earlier failure moved on, hel-1 still builds. + const view = deploymentView({ + deployment: deployment("queued"), progress: null, nodes: [node({ nodeId: "web", changed: true, image: "web" })], + buildLog: { steps: [...github, step(6, 0, "install", "Installing ployz", 1, 2, "no such version", "web", 1), hel1], output: [], imageBuilds: [evidence] }, + }); + expect(view.nodes[0]).toMatchObject({ outcome: "building", build: { state: "running" }, builtOn: "hel-1" }); }); it("tails the image building now while the next image waits its turn", () => { diff --git a/dashboard/src/modules/deployments/deployment-view.ts b/dashboard/src/modules/deployments/deployment-view.ts index 85a92b4a86..ecdfe7ff9e 100644 --- a/dashboard/src/modules/deployments/deployment-view.ts +++ b/dashboard/src/modules/deployments/deployment-view.ts @@ -4,8 +4,8 @@ import { canonicalJson } from "#/modules/environment-design/canonical-json"; import { decodeStrict } from "#/modules/environment-design/schema"; import { persistedVolumeConfigSchema, type VolumeConfig } from "#/modules/environment-design/volume-config"; import type { EnvironmentDeploymentStatus, ServerChoice } from "./tables"; -import { preferredServerUnavailableText, skipReasonText, type CandidateReason, type SkipReason } from "./image-build"; -import { BUILDING_KEY, CLEANUP_KEY, TRANSFER_KEY } from "./preparation-progress"; +import { preferredServerUnavailableText, skipReasonText, type SkipReason } from "./image-build"; +import { BUILDER_KEY, BUILDING_KEY, CLEANUP_KEY, TRANSFER_KEY } from "./preparation-progress"; import { executionErrorLabel, progressRowLabel, type DeploymentProgress, type DeploymentProgressRow } from "./deployment-progress"; import { isActiveDeployment } from "./runtime-contract"; @@ -26,13 +26,9 @@ export type DeploymentNodeView = { deploy: Stage; failure: { message: string; containerId: string | null } | null; tail: string[]; - /** The Server the Engine chose for this image and why, once it chose. */ - builtOn: BuiltOn | null; + /** The Builder that holds this image's build: "GitHub Actions" or a Server, once one took it. */ + builtOn: string | null; }; -/** `runUrl` links a build that ran on GitHub Actions; `skipped` is its skip trail, in order. */ -export type BuiltOn = - | { server: string; reason: string; runUrl?: string; skipped: readonly string[] } - | { server: null; reason: null; skipped: readonly string[] }; export type DeploymentViewStatus = "queued" | "building" | "deploying" | "deployed" | "failed" | "cancelled"; export type DeploymentView = { status: DeploymentViewStatus; @@ -40,12 +36,12 @@ export type DeploymentView = { nodes: DeploymentNodeView[]; }; /** The attempt's Build Steps and their output, as the build log read returns them. `image` is null for the deploy step's own. */ -type BuildStep = { id: number; image: string | null; build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; error: string | null }; +type BuildStep = { id: number; image: string | null; attempt: number; build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; error: string | null }; /** What an Image Build recorded about its Builders: the Server choice or GitHub run, and the skip trail. */ export type ImageBuildEvidence = { image: string; serverChoice: ServerChoice | null; - github: { runUrl: string; reason: CandidateReason } | null; + github: { runUrl: string } | null; skips: readonly SkipReason[]; }; export type BuildLog = { @@ -54,43 +50,44 @@ export type BuildLog = { imageBuilds?: readonly ImageBuildEvidence[]; }; -/** Why GitHub Actions took an Image Build, as recorded when it did. */ -const githubReasonText = { - preferred: "preferred builder", - first_in_build_order: "first in the build order", - next_in_build_order: "next in the build order", -} satisfies Record; - -/** Why the Engine chose a Server: recorded evidence, never a prediction. */ -function builderReason(reason: ServerChoice["reason"]): string { - switch (reason.kind) { - case "preferred": return "preferred builder"; - case "had_cache": return "had this Service's build cache"; - case "spread": return "spread across Servers"; - case "cache_holder_unavailable": return `${reason.name ?? "the Server with the cache"} has the cache but is offline or no longer builds`; - case "preferred_unavailable": return preferredServerUnavailableText(reason.name); - case "reused": return "reused the build of this commit"; - } -} - /** - * Where an image builds and why, from what its Image Build recorded: the Server choice or GitHub - * run, and the Builders it skipped on the way. Before a Builder takes it, only the skips are known. + * The Builder that holds an image's build now, from what its Image Build recorded. Why it was + * chosen stays in the data: routine choices are nothing the user acts on. */ -export function builtOn(log: Pick | null | undefined, image: string | null): BuiltOn | null { +export function builtOn(log: Pick | null | undefined, image: string | null): string | null { const row = image ? log?.imageBuilds?.find((candidate) => candidate.image === image) : undefined; - const skipped = (row?.skips ?? []).map(skipReasonText); - if (row?.github) return { server: "GitHub Actions", reason: githubReasonText[row.github.reason], runUrl: row.github.runUrl, skipped }; - if (row?.serverChoice) return { server: row.serverChoice.machineName, reason: builderReason(row.serverChoice.reason), skipped }; - return skipped.length ? { server: null, reason: null, skipped } : null; + if (row?.github) return "GitHub Actions"; + return row?.serverChoice?.machineName ?? null; } -/** "Built on · · skipped ", as the canvas and build log say it. */ -export function builtOnLine({ server, reason, skipped }: BuiltOn) { - const line = [server ? `Built on ${server} · ${reason}` : null, ...skipped.map((skip) => `skipped ${skip}`)] - .filter((part) => part !== null).join(" · "); - return line.charAt(0).toUpperCase() + line.slice(1); +/** One Builder's go at an Image Build, as its log tells it: one plain line, then its steps. */ +export type BuildLogSection = { title: string | null; runUrl: string | null; steps: Step[] }; + +/** + * An Image Build's log as one timeline: a section per Builder's go, in order. The first says where it + * builds; each later one first says, from the skip trail, why the build moved: "GitHub couldn't + * finish this build: its runner stopped. Building on hel-1 instead." A Builder skipped before it + * wrote a step leaves only that sentence. The deploy step's own rows happened after every go, so + * they join the last. + */ +export function buildLogSections(steps: readonly Step[], evidence: ImageBuildEvidence | undefined): BuildLogSection[] { + const skips = evidence?.skips ?? []; + const last = Math.max(skips.length, ...steps.map((step) => step.image === null ? 0 : step.attempt)); + const sections = Array.from({ length: last + 1 }, (_, attempt): BuildLogSection => { + const own = steps.filter((step) => (step.image === null ? last : step.attempt) === attempt); + const builder = own.find((step) => step.key === BUILDER_KEY)?.name ?? null; + const skipped = attempt > 0 ? skips[attempt - 1] : undefined; + const lead = skipped ? [skipReasonText(skipped)] : []; + const choice = attempt === last ? evidence?.serverChoice?.reason : undefined; + // The Engine found the Preferred Server unavailable itself: the same move, one step later. + if (choice?.kind === "preferred_unavailable") lead.push(preferredServerUnavailableText(choice.name)); + const moved = lead.join(" "); + const title = builder === null ? moved || null : moved ? `${moved} Building on ${builder} instead.` : `Building on ${builder}`; + return { title, runUrl: attempt === last ? evidence?.github?.runUrl ?? null : null, steps: own.filter((step) => step.key !== BUILDER_KEY) }; + }); + return sections.filter((section) => section.title !== null || section.steps.length > 0); } + export type DeploymentViewInput = { deployment: { status: EnvironmentDeploymentStatus; @@ -204,16 +201,19 @@ export function imageBuildSteps(steps: readonly Step[], type ImageBuild = { failed: boolean; open: boolean; durationMs: number | undefined; lines: string[]; errorLines: string[] }; +/** An image's build as a node reads it: its latest Builder's go decides how it stands; an earlier one only moved on. */ function imageBuild(log: BuildLog, image: string): ImageBuild | null { const steps = imageBuildSteps(log.steps, image); if (!steps.length) return null; - const failed = steps.filter((step) => step.error !== null); + const current = buildLogSections(steps, log.imageBuilds?.find((row) => row.image === image)).at(-1)?.steps ?? []; + const failed = current.filter((step) => step.error !== null); const output = (ids: ReadonlySet) => logLines(log.output.filter((row) => ids.has(row.stepId)).map((row) => row.text).join("")); const starts = steps.flatMap((step) => step.startedAt ? [step.startedAt.getTime()] : []); const ends = steps.flatMap((step) => step.completedAt ? [step.completedAt.getTime()] : []); return { failed: failed.length > 0, - open: steps.some((step) => step.startedAt && !step.completedAt), + // A Builder that took it but reported nothing yet, or the walk between two, is still at it. + open: !current.length || current.some((step) => step.startedAt && !step.completedAt), durationMs: starts.length && ends.length ? Math.max(...ends) - Math.min(...starts) : undefined, lines: output(new Set(steps.map((step) => step.id))), errorLines: [...output(new Set(failed.map((step) => step.id))), ...failed.flatMap((step) => logLines(step.error ?? ""))], diff --git a/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts b/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts index 6aa70edbce..f74d0bd653 100644 --- a/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts +++ b/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts @@ -8,6 +8,7 @@ import type { MachineId } from "@ployz/sdk"; import { imageBuildWalk, type BuildCandidate } from "#/modules/deployments/build-order"; import type { SkipReason } from "#/modules/deployments/image-build"; import * as buildOrder from "#/modules/deployments/build-order.server"; +import * as deploymentEvents from "#/modules/deployments/deployment-events.server"; import * as githubImageBuilds from "#/modules/deployments/github-image-builds.server"; import * as imageBuilds from "#/modules/deployments/image-builds.server"; import type { ImageBuildAttempt, ImageBuildOutcome, ImageBuildTarget } from "#/modules/deployments/image-builds.server"; @@ -85,6 +86,8 @@ vi.spyOn(githubImageBuilds, "checkGithubImageBuild").mockImplementation((_build, fake.seen.push(seen); return fake.checks.shift() ?? (seen.ended ? settled("built") : { kind: "waiting" }); })); +// The build log is the Postgres tests' concern. +vi.spyOn(deploymentEvents, "persistBuildLog").mockImplementation(() => Effect.void); vi.spyOn(imageBuilds, "settleImageBuild").mockImplementation((build, outcome: ImageBuildOutcome) => Effect.sync(() => { if (outcome.status === "failed") fake.failed.push(outcome.message); return imageBuilds.settled(build, outcome.status); @@ -232,6 +235,6 @@ describe("walking the Build Order", () => { fake.candidates = imageBuildWalk("github-only", undefined); fake.githubStart = skipped({ builder: "github", kind: "no_workflow", repository: "acme/api" }); expect(await outcome()).toMatchObject({ deployed: false, error: expect.objectContaining({ message: "Image Build failed: api." }) }); - expect(fake.failed).toContain("GitHub: no workflow in acme/api"); + expect(fake.failed).toContain("acme/api has no Ployz build workflow."); }); }); diff --git a/dashboard/src/modules/deployments/environment-deployment.inngest.ts b/dashboard/src/modules/deployments/environment-deployment.inngest.ts index 9af777b928..bae761ee11 100644 --- a/dashboard/src/modules/deployments/environment-deployment.inngest.ts +++ b/dashboard/src/modules/deployments/environment-deployment.inngest.ts @@ -37,6 +37,8 @@ import type { BuildCandidate } from "#/modules/deployments/build-order"; import { planImageBuildWalk } from "#/modules/deployments/build-order.server"; import { skipReasonText, type SkipReason } from "#/modules/deployments/image-build"; import { buildOnServers } from "#/modules/deployments/server-image-builds.server"; +import { persistBuildLog } from "#/modules/deployments/deployment-events.server"; +import { ployzStep } from "#/modules/deployments/preparation-progress"; import { cancelGithubImageBuilds, checkGithubImageBuild, @@ -201,8 +203,11 @@ async function runImageBuild( skipped = attempt.reason; } const message = skipped ? skipReasonText(skipped) : "No Builder can take this build."; - const failed = await step.run(`fail-image-build-${build.serviceId}`, () => - runEffect(settleImageBuild(build, { status: "failed", message, machineId: null }))); + // The trail's last sentence says what happened; this step, that nothing else will take it. + const noBuilder = { ...ployzStep("stage:NoBuilder", "Couldn't start the build"), error: "No other Builder in your Build Order can take it." }; + const failed = await step.run(`fail-image-build-${build.serviceId}`, () => runEffect( + persistBuildLog(build.deploymentId, { steps: [noBuilder], output: [] }, build.image).pipe( + Effect.andThen(settleImageBuild(build, { status: "failed", message, machineId: null }))))); return failed.result; } diff --git a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts index 853785129b..32c2fbd190 100644 --- a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts +++ b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts @@ -28,7 +28,7 @@ import { createMarkCancelledRowBackedWorkflow, createProcessEnvironmentDeploymen import type { BuildOrder } from "./build-order"; import { skipReasonText, type SkipReason } from "./image-build"; import { planImageBuildWalk } from "./build-order.server"; -import { builtOn, builtOnLine } from "./deployment-view"; +import { buildLogSections, builtOn } from "./deployment-view"; import { checkGithubImageBuild, checkInGithubBuild, recordGithubBuildSteps, settleOrMoveReportedGithubBuild } from "./github-image-builds.server"; import { settleGithubImageBuild } from "./image-builds.server"; import { persistDeploymentSourcePin } from "./source-pins.server"; @@ -344,7 +344,7 @@ describe("Image Builds on GitHub Actions", () => { const log = await buildLog(); expect(log.steps.filter((step) => step.image === "api").map((step) => step.name)).toEqual(["Reused image"]); // Why nothing built stays on the build, as a Server choice does. - expect(builtOnLine(builtOn(log, "api") ?? { server: null, reason: null, skipped: [] })).toBe("Built on peer · reused the build of this commit"); + expect(builtOn(log, "api")).toBe("peer"); }); it("dispatches the build when the receipt can't be checked", async () => { @@ -449,10 +449,14 @@ describe("Image Builds on GitHub Actions", () => { expect(fake.prepared.at(-1)).toEqual({ api: receipt }); const log = await harness.runEffect(loadDeploymentBuildLog({ organizationId, deploymentId, after: 0, limit: 50 })); - expect(log.steps.filter((step) => step.image === "api").map((step) => step.name)).toEqual(["Building", "RUN make"]); + // Cloud heads GitHub's section and times the wait for a runner; the runner reports the rest. + expect(log.steps.filter((step) => step.image === "api").map((step) => [step.name, step.attempt])).toEqual([ + ["Building", 0], ["GitHub Actions", 0], ["Waiting for a runner", 0], ["RUN make", 0], + ]); + expect(log.steps.find((step) => step.name === "Waiting for a runner")?.completedAt).not.toBeNull(); expect(log.output.map((line) => line.text)).toEqual(["ok\n"]); - // A GitHub build has no Server choice; the log links its run and says why GitHub took it. - expect(log.imageBuilds).toEqual([{ image: "api", serverChoice: null, github: { runUrl: "https://github.com/owner/repo/actions/runs/9001", reason: "first_in_build_order" }, skips: [] }]); + // A GitHub build has no Server choice; the log links its run. + expect(log.imageBuilds).toEqual([{ image: "api", serverChoice: null, github: { runUrl: "https://github.com/owner/repo/actions/runs/9001" }, skips: [] }]); // The build ended: no more Build Steps are taken. expect(await run(Effect.flip(recordGithubBuildSteps(runnerRequest(oidcToken()), built?.id ?? "", JSON.stringify({ from: 3, events: [] }))))) .toMatchObject({ _tag: "Conflict" }); @@ -461,9 +465,12 @@ describe("Image Builds on GitHub Actions", () => { it("shows the runner's Build Steps as they arrive, once each, and only after check-in", async () => { await dispatch(); expect(await refused({ from: 0, events: runnerEvents.slice(0, 1) })).toMatchObject({ _tag: "Conflict" }); + // Until check-in, the log waits for a runner. + expect((await buildLog()).steps.find((step) => step.name === "Waiting for a runner")?.completedAt).toBeNull(); await checkIn(oidcToken()); expect(await post({ from: 0, events: runnerEvents.slice(0, 2) })).toEqual({ received: 2 }); - expect((await buildLog()).steps.filter((step) => step.image === "api").map((step) => step.name)).toEqual(["Building", "RUN make"]); + expect((await buildLog()).steps.filter((step) => step.image === "api" && step.key !== "stage:Builder" && step.key !== "runner").map((step) => step.name)) + .toEqual(["Building", "RUN make"]); // A retried batch repeats lines already taken; only the new one is filed. expect(await post({ from: 1, events: runnerEvents.slice(1) })).toEqual({ received: 3 }); // A batch that skips lines is refused, naming where to resend from. @@ -504,7 +511,10 @@ describe("Image Builds on GitHub Actions", () => { ["the runner stopped before its final report", async () => { await post({ from: 0, events: runnerEvents }); }, { builder: "github", kind: "runner_stopped" }], ["the runner reported no platforms and no failed step", async () => { await report([]); }, { builder: "github", kind: "no_push" }], ["the Machine recorded no push", async () => { fake.noPush = true; await report(["linux/amd64"]); }, { builder: "github", kind: "no_push" }], - ["the runner couldn't install ployz", async () => { await post({ from: 0, events: [], platforms: [], installFailed: "0.1.0-beta.28" }); }, + ["the runner couldn't install ployz", async () => { + const install = { id: "install", name: "Installing ployz", started: "2026-09-26T00:00:00Z", completed: "2026-09-26T00:00:05Z", cached: false, error: "Could not install ployz 0.1.0-beta.28." }; + await post({ from: 0, events: [{ at: 1_000, event: { Build: { Step: install } } }], platforms: [], installFailed: "0.1.0-beta.28" }); + }, { builder: "github", kind: "install_failed", version: "0.1.0-beta.28" }], ["the run ran out of time", async () => { await harness.db.update(schema.environmentDeploymentImageBuild).set({ checkedInAt: new Date(Date.now() - 3 * 60 * 60_000) }); @@ -523,8 +533,12 @@ describe("Image Builds on GitHub Actions", () => { expect(new Set(fake.ended)).toEqual(new Set([grantId(1)])); const log = await buildLog(); expect(log.imageBuilds).toEqual([expect.objectContaining({ image: "api", skips: [reason] })]); - // GitHub's go shows no failed step: the servers took over. - expect(log.steps.filter((step) => step.error !== null)).toEqual([]); + // One timeline: GitHub's go, then one sentence for the move and the servers' go, which alone decides the build. + const steps = log.steps.filter((step) => step.image === "api"); + expect(buildLogSections(steps, log.imageBuilds[0]).map((section) => section.title)) + .toEqual(["Building on GitHub Actions", `${skipReasonText(reason)} Building on ${machine.name} instead.`]); + expect(steps.filter((step) => step.error !== null && step.key !== "install")).toEqual([]); + expect(steps.filter((step) => step.startedAt !== null && step.completedAt === null && step.attempt === 0)).toEqual([]); }, 30_000); it("leaves a build the walk moved to the servers alone when a late GitHub report or settle arrives", async () => { @@ -638,13 +652,13 @@ describe("Image Builds on GitHub Actions", () => { it.each([ ["no workflow", () => fake.githubErrors.set("fetch_workflow", githubError("fetch_workflow", 404, "not_found")), - { builder: "github", kind: "no_workflow", repository: "owner/repo" }, "GitHub: no workflow in owner/repo"], + { builder: "github", kind: "no_workflow", repository: "owner/repo" }, "owner/repo has no Ployz build workflow."], ["no permission", () => fake.githubErrors.set("resolve_repository", githubError("resolve_repository", 403, "request_failed")), - { builder: "github", kind: "no_permission", repository: "owner/repo" }, "GitHub: no permission in owner/repo"], + { builder: "github", kind: "no_permission", repository: "owner/repo" }, "GitHub has no permission in owner/repo."], ["multi-platform", () => { fake.platforms = ["linux/amd64", "linux/arm64"]; }, - { builder: "github", kind: "multi_platform", platforms: ["amd64", "arm64"] }, "GitHub: needs amd64+arm64"], + { builder: "github", kind: "multi_platform", platforms: ["amd64", "arm64"] }, "GitHub builds one platform, and this image needs amd64 and arm64."], ["dispatch error", () => fake.githubErrors.set("dispatch_workflow", githubError("dispatch_workflow", 422, "request_failed")), - { builder: "github", kind: "dispatch_failed", message: "GitHub answered 422." }, "GitHub: could not start the build (GitHub answered 422.)"], + { builder: "github", kind: "dispatch_failed", message: "GitHub answered 422." }, "GitHub couldn't start the build (GitHub answered 422.)."], ] satisfies [string, () => void, SkipReason, string][])("skips GitHub at once for %s, and with GitHub only the build fails with that reason", async (_case, arrange, reason, message) => { arrange(); await queued(); @@ -732,7 +746,7 @@ describe("Image Builds on GitHub Actions", () => { expect(output.error).toBeUndefined(); expect(await row()).toMatchObject({ status: "built", builder: "github" }); expect((await buildLog()).imageBuilds).toEqual([{ - image: "api", serverChoice: null, github: { runUrl: "https://github.com/owner/repo/actions/runs/9001", reason: "next_in_build_order" }, skips: [serversNotStarted], + image: "api", serverChoice: null, github: { runUrl: "https://github.com/owner/repo/actions/runs/9001" }, skips: [serversNotStarted], }]); }, 30_000); describe("a Service's Preferred Builder", () => { diff --git a/dashboard/src/modules/deployments/github-image-builds.server.ts b/dashboard/src/modules/deployments/github-image-builds.server.ts index 606d0b962f..e7225210b6 100644 --- a/dashboard/src/modules/deployments/github-image-builds.server.ts +++ b/dashboard/src/modules/deployments/github-image-builds.server.ts @@ -12,11 +12,11 @@ import type { BuildCandidate } from "./build-order"; import { githubSkipReason, installFailedSchema, type GithubImageBuild } from "./image-build"; import { persistBuildLog } from "./deployment-events.server"; import { - awaitsCheckIn, checkInImageBuild, claimForGithub, loadBuildReceipts, loadGithubImageBuilds, imageBuildNow, loadImageBuild, recordGithubReport, recordServerChoice, settleGithubImageBuild, settleImageBuild, + awaitsCheckIn, checkInImageBuild, closeOpenSteps, claimForGithub, loadBuildReceipts, loadGithubImageBuilds, imageBuildNow, loadImageBuild, recordGithubReport, recordServerChoice, settleGithubImageBuild, settleImageBuild, moveStartedGithubBuild, skipImageBuilder, skipUnstarted, START_WITHIN_MINUTES, type ImageBuildAttempt, type ImageBuildRow, type ImageBuildTarget, } from "./image-builds.server"; -import { preparationProgressCollector, type BuildOutputWrite, type BuildStepWrite } from "./preparation-progress"; +import { builderStep, ployzStep, preparationProgressCollector, type BuildOutputWrite, type BuildStepWrite } from "./preparation-progress"; import { loadDeploymentContext } from "./runtime-hydration.repository.server"; import type { DeploymentContext } from "./runtime-repository.contract"; import { connectedRuntime, oneServiceDeployment } from "./runtime-session.server"; @@ -116,6 +116,9 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil yield* cancelGithubRun({ installationId: source.installationId, fullName: workflow.fullName, runId: run.runId }).pipe(Effect.ignore); return claim satisfies GithubBuildStart; } + // Cloud sees both ends of the wait for a runner: this dispatch and the check-in, which closes it. + const now = new Date(); + yield* persistBuildLog(build.deploymentId, { steps: [builderStep(GITHUB_BUILDER, now), { ...ployzStep("runner", "Waiting for a runner", now), completedAt: null }], output: [] }, build.image); return { kind: "dispatched", runId: run.runId } satisfies GithubBuildStart; }, (effect, build) => effect.pipe( // A GitHub or SDK failure before dispatch is GitHub being unusable, not the build failing. @@ -124,6 +127,9 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil type GithubRow = Extract; +/** GitHub as its build log section names it. */ +const GITHUB_BUILDER = "GitHub Actions"; + /** * One look at a dispatched build, after the Workflow run webhook reported its run `ended` or a wait * timed out. A timeout also asks GitHub whether the run completed, which catches a completion that @@ -231,9 +237,10 @@ export const checkInGithubBuild = Effect.fn("Deployments.checkInGithubBuild")(fu Effect.mapError((cause) => new BuildGrantUnavailable({ cause })), ); return { minted, machine }; - }).pipe(Effect.scoped); + }).pipe(Effect.scoped, Effect.tapError(() => checkInFailed(row))); const grant = { id: minted.id, fingerprint }; - if (!(yield* checkInImageBuild({ imageBuildId: row.id, runId: row.githubRunId, machineId: machine.id, grant }))) { + const checkedIn = yield* checkInImageBuild({ imageBuildId: row.id, runId: row.githubRunId, machineId: machine.id, grant }); + if (!checkedIn) { // Lost to a second check-in or to the start-within skip during a slow mint (rare). The grant's // secret never left Cloud, so a grant that fails to end is unusable anyway. yield* endGrant(row.organizationId, machine.id, minted.id).pipe( @@ -241,10 +248,24 @@ export const checkInGithubBuild = Effect.fn("Deployments.checkInGithubBuild")(fu ); return yield* refused; } + // The runner arrived: its wait ends. + yield* closeOpenSteps(row.id); // The runner installs this version: the process that computed the fingerprint names it, even mid-rollout. return { grant: minted.grant, commit, fingerprint, ployzVersion: ployzVersion(), deployment }; }); +/** + * A check-in the runner arrived for but Cloud couldn't start: the one internal GitHub step the log + * shows, and only failed. Best effort; the run then ends and the walk moves on or fails. + */ +const checkInFailed = (row: GithubRow) => Effect.gen(function* () { + yield* closeOpenSteps(row.id); + const now = new Date(); + yield* persistBuildLog(row.deploymentId, { + steps: [{ ...ployzStep("check-in", "Starting the build", now), error: "Your servers couldn't be reached to receive the image." }], output: [], + }, row.image, row.skips.length); +}).pipe(Effect.catch((error) => Effect.logWarning("Could not log a failed GitHub check-in.", error))); + const buildStepSchema = Schema.Struct({ id: Schema.String, name: Schema.String, started: Schema.NullOr(Schema.String), completed: Schema.NullOr(Schema.String), cached: Schema.Boolean, error: Schema.NullOr(Schema.String), @@ -300,7 +321,8 @@ export const recordGithubBuildSteps = Effect.fn("Deployments.recordGithubBuildSt const platforms = report.platforms ?? null; // A failed Build Step already shows in its row; a build GitHub failed moves on without one. if (platforms) steps.push(...collector.finish()); - yield* persistBuildLog(row.deploymentId, { steps, output }, row.image); + // Filed under this run's section even if the walk moved the build on meanwhile. + yield* persistBuildLog(row.deploymentId, { steps, output }, row.image, row.skips.length); const taken = Math.max(received, report.from + report.events.length); const recorded: Types.Mutable> = { received: taken, collector: collector.checkpoint(), platforms: platforms && [...platforms] }; if (platforms && report.installFailed !== undefined) recorded.installFailed = report.installFailed; @@ -334,14 +356,7 @@ const finishGithubImageBuild = Effect.fn("Deployments.finishGithubImageBuild")(f } const ended = yield* endGithubBuild(build, row, timedOut); if (ended.kind !== "move") return ended; - const moved = yield* moveStartedGithubBuild(build, row.githubRunId, ended.reason); - const report = row.github.report; - if (moved.kind === "skipped" && report && !report.platforms) { - // No final report closed the runner's open steps: close them, so they don't dangle beside the - // next Builder's. Only once moved: a final report that settled it first keeps its own steps. - yield* persistBuildLog(row.deploymentId, { steps: preparationProgressCollector(undefined, report.collector).finish(), output: [] }, row.image); - } - return moved; + return yield* moveStartedGithubBuild(build, row.githubRunId, ended.reason); }); /** diff --git a/dashboard/src/modules/deployments/image-build.ts b/dashboard/src/modules/deployments/image-build.ts index 8b2fbee6db..035d2fad38 100644 --- a/dashboard/src/modules/deployments/image-build.ts +++ b/dashboard/src/modules/deployments/image-build.ts @@ -36,26 +36,28 @@ export type SkipReason = typeof skipReasonSchema.Type; * Engine did when choosing (its reason): the same words either way. `name` is null once it left the Cluster. */ export const preferredServerUnavailableText = (name: string | null) => - name === null ? "Preferred server: no longer in the Cluster" : `Preferred server ${name}: offline or no longer builds`; + name === null ? "Your preferred server left the Cluster." : `Your preferred server ${name} is offline or no longer builds.`; -/** Why a Builder didn't take an Image Build, as the canvas, the build log and a failed build say it. */ +/** + * Why a Builder didn't take an Image Build, as one plain sentence: the build log says it before + * "Building on instead.", and a build no Builder took fails with it. + */ export function skipReasonText(reason: SkipReason): string { - const builder = reason.builder === "github" ? "GitHub" : "Your servers"; switch (reason.kind) { - case "not_connected": return `${builder}: the repository isn't connected through the GitHub App`; - case "no_permission": return `${builder}: no permission in ${reason.repository}`; - case "no_workflow": return `${builder}: no workflow in ${reason.repository}`; - case "multi_platform": return `${builder}: needs ${reason.platforms.join("+")}`; - case "dispatch_failed": return `${builder}: could not start the build (${reason.message})`; - case "ended_before_start": return `${builder}: the run ended before it started`; - case "runner_stopped": return `${builder}: the runner stopped before finishing`; - case "no_push": return `${builder}: the run didn't push an image`; - case "out_of_time": return `${builder}: ran out of time`; - case "install_failed": return `${builder}: couldn't install ployz ${reason.version}`; + case "not_connected": return "GitHub can't reach this repository: it isn't connected through the GitHub App."; + case "no_permission": return `GitHub has no permission in ${reason.repository}.`; + case "no_workflow": return `${reason.repository} has no Ployz build workflow.`; + case "multi_platform": return `GitHub builds one platform, and this image needs ${reason.platforms.join(" and ")}.`; + case "dispatch_failed": return `GitHub couldn't start the build (${reason.message}).`; + case "ended_before_start": return "The GitHub run ended before the build started."; + case "runner_stopped": return "GitHub couldn't finish this build: its runner stopped."; + case "no_push": return "GitHub couldn't finish this build: it pushed no image."; + case "out_of_time": return "GitHub couldn't finish this build in time."; + case "install_failed": return `GitHub couldn't install ployz ${reason.version}.`; case "preferred_unavailable": return preferredServerUnavailableText(reason.name); case "not_started": return reason.builder === "github" - ? `${builder}: no runner in ${reason.minutes} min` - : `${builder}: none started it in ${reason.minutes} min`; + ? `No GitHub runner started within ${reason.minutes} min.` + : `No server started the build within ${reason.minutes} min.`; } } @@ -100,8 +102,9 @@ export type GithubImageBuild = typeof githubImageBuildSchema.Type; * not the build's. Null when a Build Step failed, in any batch: that is final. */ export function githubSkipReason(report: GithubImageBuild["report"], timedOut: boolean): SkipReason | null { - if (report?.collector.stepFailed) return null; + // Before any Build Step ran: its failed "Installing ployz" step is GitHub's, not the build's. if (report?.installFailed !== undefined) return { builder: "github", kind: "install_failed", version: report.installFailed }; + if (report?.collector.stepFailed) return null; if (timedOut) return { builder: "github", kind: "out_of_time" }; return report?.platforms ? { builder: "github", kind: "no_push" } : { builder: "github", kind: "runner_stopped" }; } diff --git a/dashboard/src/modules/deployments/image-builds.server.ts b/dashboard/src/modules/deployments/image-builds.server.ts index f61247c59a..96787b8f68 100644 --- a/dashboard/src/modules/deployments/image-builds.server.ts +++ b/dashboard/src/modules/deployments/image-builds.server.ts @@ -1,6 +1,6 @@ import "@tanstack/react-start/server-only"; import type { BuildGrantId, BuildReceipt, BuildReceipts, MachineId } from "@ployz/sdk"; -import { and, desc, eq, inArray, isNull, sql, type SQL } from "drizzle-orm"; +import { and, desc, eq, inArray, isNotNull, isNull, sql, type SQL } from "drizzle-orm"; import { Effect, Option, Schema } from "effect"; import { organizationIdForDeployment } from "#/db/scope-values.server"; import { rustMachineIdSchema } from "#/modules/machines/enrollment"; @@ -9,7 +9,7 @@ import { SecretEncryption } from "#/utils/encrypted-secret.server"; import { githubImageBuildSchema, type GithubImageBuild, type SkipReason } from "./image-build"; import { ACTIVE_ENVIRONMENT_DEPLOYMENT_STATUSES } from "./runtime-contract"; import type { DeploymentContext } from "./runtime-repository.contract"; -import { environmentDeployment, environmentDeploymentImageBuild as table, type ImageBuildStatus, type ServerChoice } from "./tables"; +import { environmentDeployment, environmentDeploymentBuildStep, environmentDeploymentImageBuild as table, type ImageBuildStatus, type ServerChoice } from "./tables"; /** * Image Build rows and every transition they make. Each transition is one guarded update and @@ -141,6 +141,20 @@ export const claimForGithub = Effect.fn("Deployments.claimImageBuildForGithub")( return claimed ? { kind: "claimed" as const } : settled(build, yield* statusNow(build.id)); }); +/** + * Closes the steps a Builder left open when it stopped holding the build (moved on or settled), so + * none spins beside the next Builder's or after the build ended. Only the holder's steps can be open: + * each go closes its own before the next starts. + */ +export const closeOpenSteps = Effect.fn("Deployments.closeOpenBuildSteps")(function* (imageBuildId: string) { + const { drizzle } = yield* Database; + const steps = environmentDeploymentBuildStep; + yield* drizzle.update(steps).set({ completedAt: sql`now()`, updatedAt: new Date() }).where(and( + sql`(${steps.deploymentId}, ${steps.image}) = (select ${table.deploymentId}, ${table.image} from ${table} where ${table.id} = ${imageBuildId})`, + isNull(steps.completedAt), isNotNull(steps.startedAt), + )); +}); + /** Adds `reason` to the skip trail and clears what the Builder left, so the next one starts clean. */ const skipPatch = (reason: SkipReason) => ({ skips: sql`${table.skips} || ${JSON.stringify([reason])}::jsonb`, @@ -155,7 +169,10 @@ export const skipImageBuilder = Effect.fn("Deployments.skipImageBuilder")(functi const { drizzle } = yield* Database; const [skipped] = yield* drizzle.update(table).set(skipPatch(reason)) .where(and(eq(table.id, build.id), eq(table.status, "building"), isNull(table.checkedInAt))).returning({ id: table.id }); - if (skipped) return { kind: "skipped", reason } satisfies ImageBuildAttempt; + if (skipped) { + yield* closeOpenSteps(build.id); + return { kind: "skipped", reason } satisfies ImageBuildAttempt; + } const status = yield* statusNow(build.id); return status === "building" ? { kind: "started" as const } : settled(build, status); }); @@ -169,7 +186,10 @@ export const moveStartedGithubBuild = Effect.fn("Deployments.moveStartedGithubBu const [moved] = yield* drizzle.update(table).set(skipPatch(reason)) .where(and(eq(table.id, build.id), eq(table.status, "building"), eq(table.githubRunId, runId))).returning({ id: table.id }); const skipped = { kind: "skipped", reason } satisfies ImageBuildAttempt; - if (moved) return skipped; + if (moved) { + yield* closeOpenSteps(build.id); + return skipped; + } const status = yield* statusNow(build.id); // Still building without this run: a retry of this move already moved it. return status === "building" ? skipped : settled(build, status); @@ -250,6 +270,7 @@ const settleWhere = Effect.fn("Deployments.settleImageBuild")(function* (build: : { status: "cancelled" as const }; const [updated] = yield* drizzle.update(table).set({ ...patch, finishedAt: now, updatedAt: now }) .where(and(eq(table.id, build.id), eq(table.status, "building"), holder)).returning({ status: table.status }); + if (updated) yield* closeOpenSteps(build.id); return updated?.status ?? (yield* statusNow(build.id)); }); diff --git a/dashboard/src/modules/deployments/preparation-progress.test.ts b/dashboard/src/modules/deployments/preparation-progress.test.ts index 2b96cbe677..1a5bfd8c0a 100644 --- a/dashboard/src/modules/deployments/preparation-progress.test.ts +++ b/dashboard/src/modules/deployments/preparation-progress.test.ts @@ -1,5 +1,6 @@ import type { MachineId } from "@ployz/sdk"; import { expect, it } from "vitest"; +import { runtimeWatchMachineFixture } from "#/modules/runtime/runtime-watch-frame.test-fixture"; import { preparationProgressCollector } from "./preparation-progress"; const keys = (steps: readonly { build: number; key: string; error: string | null }[]) => steps.map((step) => [step.build, step.key, step.error]); @@ -28,11 +29,28 @@ it("turns phases and BuildKit steps into one step tree with attributed output", expect(progress.event({ Build: { StepOutput: { step: "sha256:a", stderr: false, text: "cached\n" } } }).output.map((row) => row.build)).toEqual([2]); expect(keys(progress.event({ Build: { Stage: "Cleanup" } }).steps)).toEqual([[2, "stage:Building", null], [2, "stage:Cleanup", null]]); expect(keys(progress.event("Transfer").steps)).toEqual([[2, "stage:Cleanup", null], [2, "transfer", null]]); - expect(progress.event({ Delivered: { image: "web:1", machine_id: "m1" as MachineId } }).output).toEqual([{ build: 2, step: "transfer", stderr: false, text: "Delivered web:1 to m1\n" }]); - expect(keys(progress.finish())).toEqual([[2, "transfer", null]]); + // Each image's sending is its own step, filed under that image. + expect(progress.event({ Sending: { service: "web", machines: ["hel-2", "hel-3"] } }).steps).toEqual([ + { build: 2, key: "send:web", name: "Sending image to hel-2, hel-3", startedAt: expect.any(Date), completedAt: null, cached: false, error: null, image: "web" }, + ]); + const delivered = progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m1" as MachineId } }); + expect(delivered.output).toEqual([{ build: 2, step: "transfer", stderr: false, text: "Delivered web:1 to m1\n" }]); + expect(delivered.steps.map((row) => [row.key, row.completedAt !== null])).toEqual([["send:web", true]]); + progress.event({ Sending: { service: "api", machines: ["hel-2"] } }); + // An image still on its way when preparation failed is the one that didn't arrive. + expect(keys(progress.finish("image push failed"))).toEqual([[2, "transfer", "image push failed"], [2, "send:api", "image push failed"]]); expect(progress.finish()).toEqual([]); }); +it("heads a Server's section when the Engine selects it", () => { + const progress = preparationProgressCollector(() => new Date(5_000)); + const machine = runtimeWatchMachineFixture("m1", "hel-1"); + expect(progress.event({ Selected: { machine, reason: { kind: "spread" }, rejections: [] } }).steps).toEqual([ + { build: 0, key: "stage:Builder", name: "hel-1", startedAt: new Date(5_000), completedAt: new Date(5_000), cached: false, error: null }, + ]); + expect(progress.event({ Build: { Stage: "Queued" } }).steps.map((row) => row.name)).toEqual(["Waiting for a free build slot"]); +}); + it("keeps builder messages in their own row, which fails when the engine blames the build", () => { const progress = preparationProgressCollector(() => new Date(5_000)); progress.event({ Build: { Stage: "Building" } }); diff --git a/dashboard/src/modules/deployments/preparation-progress.ts b/dashboard/src/modules/deployments/preparation-progress.ts index 3c5ac9d5e5..bbefdc7bdc 100644 --- a/dashboard/src/modules/deployments/preparation-progress.ts +++ b/dashboard/src/modules/deployments/preparation-progress.ts @@ -2,8 +2,11 @@ import type { PreparationEvent } from "@ployz/sdk"; import { Schema } from "effect"; import type { PreparationProgress } from "./deployment-progress"; -/** A build step as the engine reports it: a BuildKit vertex or a Ployz-owned phase. */ -export type BuildStepWrite = { build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; cached: boolean; error: string | null }; +/** + * A build step as the engine reports it: a BuildKit vertex or a Ployz-owned phase. `image` files a + * deploy-time step under one Image Build instead of the writer's own. + */ +export type BuildStepWrite = { build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; cached: boolean; error: string | null; image?: string }; export type BuildOutputWrite = { build: number; step: string; stderr: boolean; text: string }; export type PreparationWrites = { progress: PreparationProgress | null; steps: BuildStepWrite[]; output: BuildOutputWrite[] }; @@ -27,14 +30,24 @@ export type CollectorCheckpoint = typeof collectorCheckpointSchema.Type; export const BUILD_OUTPUT_KEY = "build-output"; const stageNames = new Map([ - ["Admission", "Waiting for the builder"], ["Queued", "Queued"], ["Upload", "Uploading source"], ["Preparation", "Preparing the builder"], - ["Building", "Building"], ["Output", "Loading images"], ["Cleanup", "Cleaning up"], + ["Admission", "Starting the build"], ["Queued", "Waiting for a free build slot"], ["Upload", "Uploading source"], ["Preparation", "Preparing the builder"], + ["Building", "Building"], ["Output", "Exporting image"], ["Cleanup", "Cleaning up"], ["Push", "Pushing image"], ]); const stageName = (stage: string) => stageNames.get(stage) ?? stage; export const BUILDING_KEY = "stage:Building"; /** Attempt-wide rows the engine files under the last build run: image cleanup and delivery. */ export const CLEANUP_KEY = "stage:Cleanup"; export const TRANSFER_KEY = "transfer"; +/** Heads one Builder's section of an Image Build's log; its name is the Builder: "GitHub Actions" or a Server. */ +export const BUILDER_KEY = "stage:Builder"; +/** Sending one image to the Machines that run it, filed under that image at deploy time. */ +const sendKey = (image: string) => `send:${image}`; + +/** A Ployz-owned step starting at `at`, which the caller may leave open or fail. */ +export const ployzStep = (key: string, name: string, at = new Date()): BuildStepWrite => + ({ build: 0, key, name, startedAt: at, completedAt: at, cached: false, error: null }); +/** The heading row of a Builder's section, as the walk writes it when the Builder takes the build. */ +export const builderStep = (builder: string, at?: Date) => ployzStep(BUILDER_KEY, builder, at); /** * Which step a failed attempt is pinned on: the failed BuildKit step already @@ -108,11 +121,22 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), } if ("Selected" in event) { current = { ...current, machineId: event.Selected.machine.id, machineName: event.Selected.machine.name, message: "Builder selected" }; - return { progress: current, steps: [], output: [] }; + return { progress: current, steps: [builderStep(event.Selected.machine.name, now())], output: [] }; + } + if ("Sending" in event) { + const { service, machines } = event.Sending; + const row = { ...create(sendKey(service), `Sending image to ${machines.join(", ")}`), image: service }; + rows.set(rowId(build, row.key), row); + return { progress: null, steps: [{ ...row }], output: [] }; } if ("Delivered" in event) { + const sent = rows.get(rowId(build, sendKey(event.Delivered.service))); + if (sent) sent.completedAt ??= now(); const row = open === null ? undefined : rows.get(open); - return { progress: null, steps: [], output: row ? [{ build: row.build, step: row.key, stderr: false, text: `Delivered ${event.Delivered.image} to ${event.Delivered.machine_id}\n` }] : [] }; + return { + progress: null, steps: sent ? [{ ...sent }] : [], + output: row ? [{ build: row.build, step: row.key, stderr: false, text: `Delivered ${event.Delivered.image} to ${event.Delivered.machine_id}\n` }] : [], + }; } if ("Platforms" in event) return none(); const build_ = event.Build; @@ -156,6 +180,8 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), } return [...rows].flatMap(([id, row]) => { if (row.completedAt !== null && id !== blamed) return []; + // An image still being sent when preparation failed is the one that failed to arrive. + if (error && row.key.startsWith("send:")) row.error = error; row.completedAt ??= now(); return [{ ...row }]; }); diff --git a/dashboard/src/modules/deployments/server-image-builds.server.ts b/dashboard/src/modules/deployments/server-image-builds.server.ts index 1450ca07fc..bb218d8362 100644 --- a/dashboard/src/modules/deployments/server-image-builds.server.ts +++ b/dashboard/src/modules/deployments/server-image-builds.server.ts @@ -11,16 +11,13 @@ import { imageBuildWanted, loadBuildReceipts, recordServerChoice, settleImageBuild, skipUnstarted, START_WITHIN_MINUTES, type ImageBuildTarget, } from "./image-builds.server"; -import { preparationProgressCollector, type BuildStepWrite } from "./preparation-progress"; +import { ployzStep, preparationProgressCollector, type BuildStepWrite } from "./preparation-progress"; import { loadDeploymentContext } from "./runtime-hydration.repository.server"; import { connectedRuntime, oneServiceDeployment, watchDeploymentCancellation } from "./runtime-session.server"; import { acquireDeploymentSources } from "./runtime-sources.server"; /** A build that reused an image still leaves this one Build Step as its evidence. */ -export const reusedImageStep = (): BuildStepWrite => { - const now = new Date(); - return { build: 0, key: "stage:Reused", name: "Reused image", startedAt: now, completedAt: now, cached: true, error: null }; -}; +export const reusedImageStep = (): BuildStepWrite => ({ ...ployzStep("stage:Reused", "Reused image"), cached: true }); /** How one go on the Cluster ended, before the Image Build records it. */ type ClusterBuild = diff --git a/dashboard/src/modules/deployments/tables.ts b/dashboard/src/modules/deployments/tables.ts index 3d06c6dde8..f90a403f49 100644 --- a/dashboard/src/modules/deployments/tables.ts +++ b/dashboard/src/modules/deployments/tables.ts @@ -228,6 +228,11 @@ export const environmentDeploymentBuildStep = pgTable("environment_deployment_bu deploymentId: uuid("deployment_id").notNull().references(() => environmentDeployment.id, { onDelete: "cascade" }), /** The Image Build that reported the step, by image name; null for the deploy step's own preparation. */ image: text("image"), + /** + * Which Builder's go at the Image Build the step belongs to: the length of its skip trail then. + * Each is a section of the build log. 0 for the deploy step's own preparation. + */ + attempt: integer("attempt").notNull().default(0), /** Which BuildKit run of the Image Build the step belongs to; 0 before the first. One build may run several (per platform). */ build: integer("build").notNull().default(0), /** BuildKit digest or `stage:`; stable across repeated reports within one run. */ @@ -239,7 +244,7 @@ export const environmentDeploymentBuildStep = pgTable("environment_deployment_bu error: text("error"), createdAt, updatedAt, -}, (table) => [unique("environment_deployment_build_step_key_unique").on(table.deploymentId, table.image, table.build, table.key).nullsNotDistinct()]); +}, (table) => [unique("environment_deployment_build_step_key_unique").on(table.deploymentId, table.image, table.attempt, table.build, table.key).nullsNotDistinct()]); /** Append-only output attributed to one build step. */ export const environmentDeploymentBuildOutput = pgTable("environment_deployment_build_output", { diff --git a/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/DeploymentServicePanel.tsx b/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/DeploymentServicePanel.tsx index 84508532ad..be9b72bb97 100644 --- a/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/DeploymentServicePanel.tsx +++ b/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/DeploymentServicePanel.tsx @@ -11,7 +11,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "#/components/ui/tabs"; import type { DeploymentAttempt } from "#/modules/deployments/deployment.collection"; import { isActiveDeployment } from "#/modules/deployments/runtime-contract"; import { outcomeBadges } from "#/components/deployment-outcome-badges"; -import { builtOnLine, nodeOutcomeLabels, shortDeploymentId, type DeploymentNodeView } from "#/modules/deployments/deployment-view"; +import { nodeOutcomeLabels, shortDeploymentId, type DeploymentNodeView } from "#/modules/deployments/deployment-view"; import { DEPLOYMENT_SERVICE_PAGES, deploymentServicePageSchema, type DeploymentServicePage, } from "../services/$serviceId/-components/service-pages"; @@ -149,7 +149,7 @@ function DeploymentServiceDetails({ organizationSlug, deployment, serviceId, vie ] : [["Source", "None"]]} /> {source.type === "git" ? : null} diff --git a/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/canvas/DeploymentNode.tsx b/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/canvas/DeploymentNode.tsx index 1325f2bb08..5982c31549 100644 --- a/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/canvas/DeploymentNode.tsx +++ b/dashboard/src/routes/_protected/cloud/$organizationSlug/_project/$projectSlug/$environmentSlug/-components/canvas/DeploymentNode.tsx @@ -11,7 +11,7 @@ import { Spinner } from "#/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "#/components/ui/tooltip"; import { cn } from "#/lib/utils"; import { outcomeBadges } from "#/components/deployment-outcome-badges"; -import { builtOnLine, nodeOutcomeLabels, type Stage } from "#/modules/deployments/deployment-view"; +import { nodeOutcomeLabels, type Stage } from "#/modules/deployments/deployment-view"; import { canvasNodeTransition } from "./constants"; import { getServiceIcon, getServiceSubtitle } from "./service-node-helpers"; import type { CanvasDeploymentNodeData } from "./types"; @@ -77,10 +77,10 @@ export function DeploymentNodeCard({ data, className }: { data: CanvasDeployment {view.builtOn ? ( - }> + }> - {builtOnLine(view.builtOn)} + Built on {view.builtOn} ) : null} From a1e5b3b2179da995a811019a6765e00c22435149 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 26 Sep 2026 03:34:46 +0000 Subject: [PATCH 2/4] Typed Push stage and SDK names for sending; push failures as a plain list --- core/crates/ployz-build/src/execution.rs | 2 ++ core/crates/ployz/src/deploy/pipeline.rs | 15 ++++-------- core/crates/ployz/src/handlers/build.rs | 31 +++++++++++------------- core/crates/ployz/src/sdk/prepare.rs | 12 ++++----- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/core/crates/ployz-build/src/execution.rs b/core/crates/ployz-build/src/execution.rs index 28e6d0d431..0b6d018fef 100644 --- a/core/crates/ployz-build/src/execution.rs +++ b/core/crates/ployz-build/src/execution.rs @@ -15,6 +15,8 @@ pub enum Stage { Building, Output, Cleanup, + /// Pushing the built image out of the host, after the Build itself (`ployz build`). + Push, } /// Bounded output and work observations from the execution host. diff --git a/core/crates/ployz/src/deploy/pipeline.rs b/core/crates/ployz/src/deploy/pipeline.rs index 12178d8d1c..9ea3f6cce0 100644 --- a/core/crates/ployz/src/deploy/pipeline.rs +++ b/core/crates/ployz/src/deploy/pipeline.rs @@ -248,11 +248,6 @@ fn invalid_argument(message: String) -> RpcError { } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct PushOutcome { - pub failures: Vec, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct PushedImage { pub image: String, @@ -274,7 +269,7 @@ pub(crate) async fn push_project_images( preview: &DeployPlan, cancellation: &CancellationToken, progress: &impl Fn(crate::sdk::prepare::Progress), -) -> Result { +) -> Result, crate::sdk::prepare::PreparationError> { let mut failures = Vec::new(); // Check every actual destination before any image or application changes. let deliveries = builds.iter().map(|service| { @@ -301,11 +296,11 @@ pub(crate) async fn push_project_images( .iter() .filter(|machine| machine.machine.id != service.machine_id) .filter(|machine| targets.contains(&machine.machine.id.to_string())) - .map(|machine| machine.machine.name.to_string()) + .map(|machine| machine.machine.name.clone()) .collect::>(); if !receivers.is_empty() { progress(crate::sdk::prepare::Progress::Sending { - service: service.name.to_string(), + service: service.name.clone(), machines: receivers, }); } @@ -314,7 +309,7 @@ pub(crate) async fn push_project_images( for image in images { progress(crate::sdk::prepare::Progress::Delivered { image: image.image, - service: service.name.to_string(), + service: service.name.clone(), machine_id: image.machine_id, }); } @@ -323,7 +318,7 @@ pub(crate) async fn push_project_images( Err(error) => failures.push(format!("{}: {error}", service.image)), } } - Ok(PushOutcome { failures }) + Ok(failures) } pub(crate) async fn plan_project( diff --git a/core/crates/ployz/src/handlers/build.rs b/core/crates/ployz/src/handlers/build.rs index eca2c91365..27818b21d9 100644 --- a/core/crates/ployz/src/handlers/build.rs +++ b/core/crates/ployz/src/handlers/build.rs @@ -95,9 +95,7 @@ pub(super) fn build(root: &ArgMatches) -> Result<(), Error> { let export = cancellation.clone(); let building = events.clone(); let (build, built) = tokio::task::spawn_blocking(move || { - let built = build.execute_local(&cancellation, &|event| { - building.write(&json!({"Build": event})); - }); + let built = build.execute_local(&cancellation, &|event| building.write(event)); (build, built) }) .await @@ -117,32 +115,31 @@ pub(super) fn build(root: &ArgMatches) -> Result<(), Error> { }) } -/// The `--events` file: one `{"at": , "event": }` line per -/// event, the shape Cloud's build log reads. Progress is best effort: a failed write never -/// fails the build. +/// The `--events` file: one `{"at": , "event": {"Build": }}` line per +/// event, the SDK preparation event shape Cloud's build log reads. Progress is best effort: +/// a failed write never fails the build. struct Events(Option>); impl Events { - fn write(&self, event: &Value) { + fn write(&self, event: &ployz_build::Progress) { use std::io::Write as _; let Some(file) = &self.0 else { return }; let at = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .map_or(0, |elapsed| elapsed.as_millis()); if let Ok(mut file) = file.lock() { - let _ = writeln!(file, "{}", json!({"at": at, "event": event})); + let _ = writeln!(file, "{}", json!({"at": at, "event": {"Build": event}})); } } - /// A line of the push, as output of its `Push` stage. - fn push_output(&self, text: &str) { - self.write(&json!({"Build": {"StepOutput": {"step": PUSH_STEP, "stderr": false, "text": format!("{text}\n")}}})); + /// One line of the push: output of the open `Push` stage. + fn push_line(&self, line: &str) { + self.write(&ployz_build::Progress::Output( + format!("{line}\n").into_bytes(), + )); } } -/// The push is a Ployz stage of its own; `ployz_build` knows only the build's stages. -const PUSH_STEP: &str = "stage:Push"; - /// The deployment's single Git-sourced Service: the one this command builds. fn git_service(deployment: &Value) -> Result { let mut services = deployment @@ -201,7 +198,7 @@ async fn push(grant: &BuildGrant, built: &LocalImage, events: &Events) -> Result .strip_prefix("sha256:") .ok_or(BuildCommandError::NotDigestIdentified)?; let tag = format!("{}:{RETAINED_DIGEST_TAG_PREFIX}{hex}", built.repository); - events.write(&json!({"Build": {"Stage": "Push"}})); + events.write(&ployz_build::Progress::Stage(ployz_build::Stage::Push)); let registry = open_grant_registry(grant, &ManagementRelay::default()).await?; let local = format!("{}/{tag}", registry.address()); docker(&["tag", digest, &local]).await?; @@ -213,7 +210,7 @@ async fn push(grant: &BuildGrant, built: &LocalImage, events: &Events) -> Result BuildCommandError::GrantRefused(refusal).into() })); } - events.push_output(&format!("Pushed {digest}")); + events.push_line(&format!("Pushed {digest}")); // The Machine stores a tagged manifest only when its bytes hash to the tag's // digest, so a completed push is the Machine's confirmation of `digest`. println!( @@ -245,7 +242,7 @@ async fn docker_push(reference: &str, events: &Events) -> Result<(), Error> { if let Some(stdout) = child.stdout.take() { let mut lines = tokio::io::BufReader::new(stdout).lines(); while let Ok(Some(line)) = lines.next_line().await { - events.push_output(&line); + events.push_line(&line); } } let status = child.wait().await?; diff --git a/core/crates/ployz/src/sdk/prepare.rs b/core/crates/ployz/src/sdk/prepare.rs index 14b0d58fe1..5f4110122d 100644 --- a/core/crates/ployz/src/sdk/prepare.rs +++ b/core/crates/ployz/src/sdk/prepare.rs @@ -222,13 +222,13 @@ pub enum Progress { Transfer, /// One Service's image starts going to these Machines, by name. Sending { - service: String, - machines: Vec, + service: ployz_core::ServiceName, + machines: Vec, }, /// One Machine received a Service's image. Delivered { image: String, - service: String, + service: ployz_core::ServiceName, machine_id: ployz_core::MachineId, }, } @@ -279,7 +279,7 @@ pub async fn prepare( if !builds.is_empty() { progress(Progress::Transfer); } - let outcome = crate::deploy::pipeline::push_project_images( + let failures = crate::deploy::pipeline::push_project_images( client, &builds, &machines, @@ -291,10 +291,10 @@ pub async fn prepare( if cancellation.is_cancelled() { return Err(PreparationError::Cancelled); } - if !outcome.failures.is_empty() { + if !failures.is_empty() { return Err(PreparationError::Delivery(format!( "image push failed: {}", - outcome.failures.join("; ") + failures.join("; ") ))); } Ok(Prepared { plan, builds }) From d361bde5ef0067e5fc6105922b1f7cbea1151301 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 26 Sep 2026 03:41:33 +0000 Subject: [PATCH 3/4] Build log review: one explicit attempt per write, Builder named by the view, sending in the deploy log The walk passes each go's attempt; the skip-trail subquery and per-step overrides are gone. Section headings come from the go's Builder, not a heading row. The runner wait opens once, before dispatch. Sending an image is the deploy's: it reads in that Service's deploy log and a delivery failure no longer reddens the build. A failed check-in names its cause. --- .../src/components/deployment-logs.test.tsx | 4 +- dashboard/src/components/deployment-logs.tsx | 7 +++ .../modules/deployments/build-order.server.ts | 4 +- .../src/modules/deployments/build-order.ts | 15 +++--- .../deployments/deployment-events.server.ts | 36 ++++++++------- .../deployments/deployment-view.test.ts | 18 ++++---- .../modules/deployments/deployment-view.ts | 25 ++++++---- ...loyment.build-order.inngest-engine.test.ts | 2 +- .../environment-deployment.inngest.ts | 3 +- .../github-image-builds.postgres.test.ts | 28 ++++++----- .../deployments/github-image-builds.server.ts | 44 ++++++++++-------- .../deployments/preparation-progress.test.ts | 32 +++++++------ .../deployments/preparation-progress.ts | 46 ++++++++----------- .../deployments/runtime-activities.server.ts | 3 +- .../deployments/server-image-builds.server.ts | 4 +- 15 files changed, 152 insertions(+), 119 deletions(-) diff --git a/dashboard/src/components/deployment-logs.test.tsx b/dashboard/src/components/deployment-logs.test.tsx index 98a274952f..24b57e8d9e 100644 --- a/dashboard/src/components/deployment-logs.test.tsx +++ b/dashboard/src/components/deployment-logs.test.tsx @@ -97,8 +97,8 @@ it("tells each Builder's go as a plain line above its steps", () => { const html = renderToStaticMarkup(createElement(BuildLogs, { timeZone: "UTC", finished: false, output: [], now: at(9).getTime(), steps: [ - step(1, "GitHub Actions", { image: "web", key: "stage:Builder", completedAt: at(1) }), step(2, "Waiting for a runner", { image: "web", key: "runner", completedAt: at(4) }), - step(5, "hel-1", { image: "web", attempt: 1, key: "stage:Builder", completedAt: at(5) }), step(6, "Uploading source", { image: "web", attempt: 1, key: "stage:Upload" }), + step(2, "Waiting for a runner", { image: "web", key: "runner", completedAt: at(4) }), + step(6, "Uploading source", { image: "web", attempt: 1, key: "stage:Upload" }), ], evidence: { image: "web", serverChoice: { machineName: "hel-1", reason: { kind: "spread" } }, github: null, skips: [{ builder: "github", kind: "not_started", minutes: 3 }] }, })); diff --git a/dashboard/src/components/deployment-logs.tsx b/dashboard/src/components/deployment-logs.tsx index c4cd976897..460b6cfe99 100644 --- a/dashboard/src/components/deployment-logs.tsx +++ b/dashboard/src/components/deployment-logs.tsx @@ -118,6 +118,7 @@ function StepRow({ step, time, lines, now, open: toggledOpen, onToggle }: { function lifecycleLogs(events: readonly { id: number; createdAt: Date; progress: DeploymentProgress }[], serviceId: string): ContainerLogRow[] { const previous = new Map(); const logs: ContainerLogRow[] = []; + let sending: string | null = null; for (const event of events) { for (const row of event.progress.rows) { if (row.serviceId !== serviceId && row.serviceId !== null) continue; @@ -127,6 +128,12 @@ function lifecycleLogs(events: readonly { id: number; createdAt: Date; progress: previous.set(row.index, label); logs.push({ id: `lifecycle:${event.id}:${row.index}`, timestamp: String(BigInt(event.createdAt.getTime()) * 1_000_000n), channel: "lifecycle", machineId: row.machineId, machineName: row.machineName ?? row.machineId, containerId: row.target ?? "", serviceName: row.serviceName ?? "Environment", message: label }); } + // Sending the image to the Machines that run it happens at deploy time, so it reads here. + const preparation = event.progress.preparation; + if (preparation?.phase === "transfer" && preparation.serviceId === serviceId && preparation.message && sending !== preparation.message) { + sending = preparation.message; + logs.push({ id: `lifecycle:${event.id}:transfer`, timestamp: String(BigInt(event.createdAt.getTime()) * 1_000_000n), channel: "lifecycle", machineId: "", machineName: "", containerId: "", serviceName: "Deployment", message: preparation.message }); + } event.progress.compensation.forEach((message, i) => logs.push({ id: `lifecycle:${event.id}:recovery:${i}`, timestamp: String(BigInt(event.createdAt.getTime()) * 1_000_000n), channel: "lifecycle", machineId: "", machineName: "", containerId: "", serviceName: "Deployment", message })); } return logs; diff --git a/dashboard/src/modules/deployments/build-order.server.ts b/dashboard/src/modules/deployments/build-order.server.ts index d7aab8108e..213aba3cab 100644 --- a/dashboard/src/modules/deployments/build-order.server.ts +++ b/dashboard/src/modules/deployments/build-order.server.ts @@ -64,14 +64,16 @@ export const planImageBuildWalk = Effect.fn("Deployments.planImageBuildWalk")(fu yield* Effect.logWarning("A Service's policy does not decode; its build follows the Build Order.", { serviceId: build.serviceId }); } let preferred = Option.isSome(policy) ? policy.value.preferredBuilder : undefined; + let skipped = 0; if (preferred !== undefined && preferred !== "github") { const unavailable = yield* preferredServerUnavailable(row.organizationId, preferred); if (unavailable) { yield* skipUnstarted(build, unavailable); preferred = undefined; + skipped = 1; } } - return imageBuildWalk(yield* loadBuildOrder(row.organizationId), preferred); + return imageBuildWalk(yield* loadBuildOrder(row.organizationId), preferred, skipped); }); export const setBuildOrder = Effect.fn("Deployments.setBuildOrder")(function* ( diff --git a/dashboard/src/modules/deployments/build-order.ts b/dashboard/src/modules/deployments/build-order.ts index 2c47525815..b6e8b4d300 100644 --- a/dashboard/src/modules/deployments/build-order.ts +++ b/dashboard/src/modules/deployments/build-order.ts @@ -28,9 +28,10 @@ export const defaultBuildOrder = (githubSetUp: boolean): BuildOrder => githubSet /** * One Builder an Image Build may try, and why it is in the walk. `machineId` is a Preferred Server: - * the Cluster's first choice. + * the Cluster's first choice. `attempt` is its go's place in the Image Build's log: the skip trail's + * length when it starts, as each go before it adds one skip. */ -export type BuildCandidate = { builder: "servers" | "github"; reason: CandidateReason; machineId?: MachineId }; +export type BuildCandidate = { builder: "servers" | "github"; reason: CandidateReason; attempt: number; machineId?: MachineId }; /** The Builders a Build Order tries, in turn. */ const ORDERED_BUILDERS = { @@ -42,14 +43,16 @@ const ORDERED_BUILDERS = { /** * The Builders one Image Build walks: the Service's Preferred Builder, then the Build Order without - * that Builder. A Preferred Server stands in for "your servers", with it first. + * that Builder. A Preferred Server stands in for "your servers", with it first. `skipped`: skips + * already on the trail before the walk starts. */ -export const imageBuildWalk = (order: BuildOrder, preferred: "github" | MachineId | undefined): BuildCandidate[] => { - const walk: BuildCandidate[] = []; +export const imageBuildWalk = (order: BuildOrder, preferred: "github" | MachineId | undefined, skipped = 0): BuildCandidate[] => { + const walk: Omit[] = []; if (preferred === "github") walk.push({ builder: "github", reason: "preferred" }); else if (preferred !== undefined) walk.push({ builder: "servers", reason: "preferred", machineId: preferred }); const rest = ORDERED_BUILDERS[order].filter((builder) => !walk.some((first) => first.builder === builder)); - return [...walk, ...rest.map((builder, index): BuildCandidate => ({ builder, reason: index === 0 ? "first_in_build_order" : "next_in_build_order" }))]; + return [...walk, ...rest.map((builder, index) => ({ builder, reason: index === 0 ? "first_in_build_order" as const : "next_in_build_order" as const }))] + .map((candidate, index) => ({ ...candidate, attempt: skipped + index })); }; export const buildOrderEditSchema = Schema.Struct({ diff --git a/dashboard/src/modules/deployments/deployment-events.server.ts b/dashboard/src/modules/deployments/deployment-events.server.ts index 43b91f658a..1014305e23 100644 --- a/dashboard/src/modules/deployments/deployment-events.server.ts +++ b/dashboard/src/modules/deployments/deployment-events.server.ts @@ -58,47 +58,49 @@ export const loadDeploymentBuildLog = Effect.fn("Deployments.buildLog")(function return { steps, output, imageBuilds, finished: deployment.finishedAt !== null, nextSequence: input.tail === undefined && output.length === input.limit && last ? String(last.id) : null }; }); -/** - * Upsert steps by key and append output. Output may precede its step; a placeholder holds its place. - * Each Image Build files its steps under its image, in the section of the Builder that holds it now - * unless `attempt` names one; the deploy step's own preparation under none. - */ -export const persistBuildLog = Effect.fn("Deployments.persistBuildLog")(function* (deploymentId: string, writes: { steps: readonly BuildStepWrite[]; output: readonly BuildOutputWrite[] }, image: string | null = null, attempt?: number) { +/** Whose Build Steps a write files: one Builder's go (`attempt`) at one Image Build (`image`); null is the deploy step's own. */ +export type BuildStepOwner = { image: string; attempt: number } | null; + +/** Upsert steps by key and append output. Output may precede its step; a placeholder holds its place. */ +export const persistBuildLog = Effect.fn("Deployments.persistBuildLog")(function* (deploymentId: string, writes: { steps: readonly BuildStepWrite[]; output: readonly BuildOutputWrite[] }, owner: BuildStepOwner = null) { if (!writes.steps.length && !writes.output.length) return; const database = yield* Database; - const builds = environmentDeploymentImageBuild; - const attemptOf = (of: string | null, given?: number) => of === null ? 0 : given ?? sql`coalesce((select jsonb_array_length(${builds.skips}) from ${builds} where ${builds.deploymentId} = ${deploymentId} and ${builds.image} = ${of}), 0)`; + const filed = { organizationId: organizationIdForDeployment(deploymentId), deploymentId, image: owner?.image ?? null, attempt: owner?.attempt ?? 0 }; yield* database.transaction(Effect.gen(function* () { const { drizzle } = yield* Database; const table = environmentDeploymentBuildStep; const target = [table.deploymentId, table.image, table.attempt, table.build, table.key]; - const organizationId = organizationIdForDeployment(deploymentId); const ids = new Map(); if (writes.steps.length) { const excluded = (column: { name: string }) => sql.raw(`excluded."${column.name}"`); - const steps = new Map(writes.steps.map((step) => [`${step.image ?? image}:${step.build}:${step.key}`, step])); - const upserted = yield* drizzle.insert(table).values([...steps.values()].map(({ image: own, ...step }) => own === undefined - ? { organizationId, deploymentId, image, attempt: attemptOf(image, attempt), ...step } - : { organizationId, deploymentId, image: own, attempt: attemptOf(own), ...step })) + const steps = new Map(writes.steps.map((step) => [`${step.build}:${step.key}`, step])); + const upserted = yield* drizzle.insert(table).values([...steps.values()].map((step) => ({ ...filed, ...step }))) .onConflictDoUpdate({ target, set: { name: excluded(table.name), startedAt: excluded(table.startedAt), completedAt: excluded(table.completedAt), cached: excluded(table.cached), error: excluded(table.error), updatedAt: new Date() } }) - .returning({ id: table.id, image: table.image, build: table.build, key: table.key }); - for (const step of upserted) if (step.image === image) ids.set(`${step.build}:${step.key}`, step.id); + .returning({ id: table.id, build: table.build, key: table.key }); + for (const step of upserted) ids.set(`${step.build}:${step.key}`, step.id); } if (!writes.output.length) return; const unknown = new Map(writes.output.filter((row) => !ids.has(`${row.build}:${row.step}`)).map((row) => [`${row.build}:${row.step}`, row])); if (unknown.size) { - const found = yield* drizzle.insert(table).values([...unknown.values()].map((row) => ({ organizationId, deploymentId, image, attempt: attemptOf(image, attempt), build: row.build, key: row.step, name: row.step }))) + const found = yield* drizzle.insert(table).values([...unknown.values()].map((row) => ({ ...filed, build: row.build, key: row.step, name: row.step }))) .onConflictDoUpdate({ target, set: { key: table.key } }) .returning({ id: table.id, build: table.build, key: table.key }); for (const step of found) ids.set(`${step.build}:${step.key}`, step.id); } yield* drizzle.insert(environmentDeploymentBuildOutput).values(writes.output.flatMap((row) => { const stepId = ids.get(`${row.build}:${row.step}`); - return stepId === undefined ? [] : [{ organizationId, deploymentId, stepId, stderr: row.stderr, text: row.text }]; + return stepId === undefined ? [] : [{ organizationId: filed.organizationId, deploymentId, stepId, stderr: row.stderr, text: row.text }]; })); })); }); +/** Opens a step once: a retried write leaves it as it is, even closed since. */ +export const openBuildStep = Effect.fn("Deployments.openBuildStep")(function* (deploymentId: string, owner: NonNullable, step: BuildStepWrite) { + const { drizzle } = yield* Database; + yield* drizzle.insert(environmentDeploymentBuildStep) + .values({ organizationId: organizationIdForDeployment(deploymentId), deploymentId, ...owner, ...step }).onConflictDoNothing(); +}); + export const persistDeploymentProgress = Effect.fn("Deployments.persistProgress")(function* (deploymentId: string, progress: typeof environmentDeploymentEvent.$inferInsert.progress) { const { drizzle } = yield* Database; yield* drizzle.insert(environmentDeploymentEvent).values({ organizationId: organizationIdForDeployment(deploymentId), deploymentId, progress }); diff --git a/dashboard/src/modules/deployments/deployment-view.test.ts b/dashboard/src/modules/deployments/deployment-view.test.ts index d38e5deed7..e9de6152b0 100644 --- a/dashboard/src/modules/deployments/deployment-view.test.ts +++ b/dashboard/src/modules/deployments/deployment-view.test.ts @@ -114,30 +114,30 @@ describe("deployment view projection", () => { }); it("tells one timeline per Image Build: a section per Builder, each move one sentence", () => { - const github = [ - step(1, 0, "stage:Builder", "GitHub Actions", 0, 0, null, "web", 1), step(2, 0, "runner", "Waiting for a runner", 0, 20, null, "web", 1), - step(3, 1, "stage:Building", "web", 20, 60, null, "web", 1), - ]; - const hel1 = step(4, 0, "stage:Builder", "hel-1", 61, 61, null, "web", 2); - const servers = [hel1, step(5, 1, "stage:Building", "web", 62, null, null, "web", 2)]; + const github = [step(2, 0, "runner", "Waiting for a runner", 0, 20, null, "web", 1), step(3, 1, "stage:Building", "web", 20, 60, null, "web", 1)]; + const servers = [step(5, 1, "stage:Building", "web", 62, null, null, "web", 2)]; const steps = [...github, ...servers]; const evidence = { image: "web", github: null, serverChoice: { machineName: "hel-1", reason: { kind: "spread" as const } }, skips: [ { builder: "servers" as const, kind: "preferred_unavailable" as const, machineId: "e".repeat(32) as MachineId, name: "hel-2" }, { builder: "github" as const, kind: "runner_stopped" as const }, ] }; + // Each go's Builder comes from its skip, or the holder for the last; the planning skip wrote no step. expect(buildLogSections(steps, evidence).map(({ title, steps: own }) => [title, own.map((row) => row.id)])).toEqual([ ["Your preferred server hel-2 is offline or no longer builds. Building on GitHub Actions instead.", [2, 3]], ["GitHub couldn't finish this build: its runner stopped. Building on hel-1 instead.", [5]], ]); // A Builder skipped before it wrote a step leaves its sentence alone; the first section names its Builder plainly. - expect(buildLogSections([step(1, 0, "stage:Builder", "hel-1", 0, 0, null, "web", 1)], { ...evidence, skips: [{ builder: "github", kind: "no_workflow", repository: "o/r" }] }) + expect(buildLogSections([step(1, 1, "stage:Building", "web", 0, null, null, "web", 1)], { ...evidence, skips: [{ builder: "github", kind: "no_workflow", repository: "o/r" }] }) .map((section) => section.title)).toEqual(["o/r has no Ployz build workflow. Building on hel-1 instead."]); - expect(buildLogSections([step(1, 0, "stage:Builder", "GitHub Actions", 0, 0, null, "web")], { ...evidence, serverChoice: null, skips: [] }) + expect(buildLogSections([step(1, 0, "runner", "Waiting for a runner", 0, null, null, "web")], { ...evidence, serverChoice: null, github: { runUrl: "u" }, skips: [] }) .map((section) => section.title)).toEqual(["Building on GitHub Actions"]); + // A Server go that moved on no longer knows its Server. + expect(buildLogSections([step(1, 0, "stage:Queued", "Waiting for a free build slot", 0, 180, null, "web")], { ...evidence, skips: [{ builder: "servers", kind: "not_started", minutes: 3 }] }) + .map((section) => section.title)).toEqual(["Building on your servers", "No server started the build within 3 min."]); // Only the last Builder's go decides the node: GitHub's earlier failure moved on, hel-1 still builds. const view = deploymentView({ deployment: deployment("queued"), progress: null, nodes: [node({ nodeId: "web", changed: true, image: "web" })], - buildLog: { steps: [...github, step(6, 0, "install", "Installing ployz", 1, 2, "no such version", "web", 1), hel1], output: [], imageBuilds: [evidence] }, + buildLog: { steps: [...github, step(6, 0, "install", "Installing ployz", 1, 2, "no such version", "web", 1), ...servers], output: [], imageBuilds: [evidence] }, }); expect(view.nodes[0]).toMatchObject({ outcome: "building", build: { state: "running" }, builtOn: "hel-1" }); }); diff --git a/dashboard/src/modules/deployments/deployment-view.ts b/dashboard/src/modules/deployments/deployment-view.ts index ecdfe7ff9e..3bc431766b 100644 --- a/dashboard/src/modules/deployments/deployment-view.ts +++ b/dashboard/src/modules/deployments/deployment-view.ts @@ -5,7 +5,7 @@ import { decodeStrict } from "#/modules/environment-design/schema"; import { persistedVolumeConfigSchema, type VolumeConfig } from "#/modules/environment-design/volume-config"; import type { EnvironmentDeploymentStatus, ServerChoice } from "./tables"; import { preferredServerUnavailableText, skipReasonText, type SkipReason } from "./image-build"; -import { BUILDER_KEY, BUILDING_KEY, CLEANUP_KEY, TRANSFER_KEY } from "./preparation-progress"; +import { BUILDING_KEY, CLEANUP_KEY, TRANSFER_KEY } from "./preparation-progress"; import { executionErrorLabel, progressRowLabel, type DeploymentProgress, type DeploymentProgressRow } from "./deployment-progress"; import { isActiveDeployment } from "./runtime-contract"; @@ -60,22 +60,31 @@ export function builtOn(log: Pick | null | undefined, i return row?.serverChoice?.machineName ?? null; } +/** Who took one go at an Image Build: the Builder its skip names, or the one that holds the build now. Null for a reused image. */ +function goBuilder(skipped: SkipReason | undefined, evidence: ImageBuildEvidence | undefined): string | null { + if (skipped) return skipped.builder === "github" ? "GitHub Actions" : "your servers"; + if (evidence?.github) return "GitHub Actions"; + const choice = evidence?.serverChoice; + return choice && choice.reason.kind !== "reused" ? choice.machineName : null; +} + /** One Builder's go at an Image Build, as its log tells it: one plain line, then its steps. */ export type BuildLogSection = { title: string | null; runUrl: string | null; steps: Step[] }; /** * An Image Build's log as one timeline: a section per Builder's go, in order. The first says where it * builds; each later one first says, from the skip trail, why the build moved: "GitHub couldn't - * finish this build: its runner stopped. Building on hel-1 instead." A Builder skipped before it - * wrote a step leaves only that sentence. The deploy step's own rows happened after every go, so - * they join the last. + * finish this build: its runner stopped. Building on hel-1 instead." A go's Builder is the one its + * skip names, or the current holder for the last; a Server go that moved on no longer names its + * Server. A Builder skipped before it wrote a step leaves only that sentence. The deploy step's own + * rows happened after every go, so they join the last. */ export function buildLogSections(steps: readonly Step[], evidence: ImageBuildEvidence | undefined): BuildLogSection[] { const skips = evidence?.skips ?? []; const last = Math.max(skips.length, ...steps.map((step) => step.image === null ? 0 : step.attempt)); const sections = Array.from({ length: last + 1 }, (_, attempt): BuildLogSection => { const own = steps.filter((step) => (step.image === null ? last : step.attempt) === attempt); - const builder = own.find((step) => step.key === BUILDER_KEY)?.name ?? null; + const builder = own.length ? goBuilder(attempt < skips.length ? skips[attempt] : undefined, evidence) : null; const skipped = attempt > 0 ? skips[attempt - 1] : undefined; const lead = skipped ? [skipReasonText(skipped)] : []; const choice = attempt === last ? evidence?.serverChoice?.reason : undefined; @@ -83,7 +92,7 @@ export function buildLogSections(steps: readonly Step[], if (choice?.kind === "preferred_unavailable") lead.push(preferredServerUnavailableText(choice.name)); const moved = lead.join(" "); const title = builder === null ? moved || null : moved ? `${moved} Building on ${builder} instead.` : `Building on ${builder}`; - return { title, runUrl: attempt === last ? evidence?.github?.runUrl ?? null : null, steps: own.filter((step) => step.key !== BUILDER_KEY) }; + return { title, runUrl: attempt === last ? evidence?.github?.runUrl ?? null : null, steps: own }; }); return sections.filter((section) => section.title !== null || section.steps.length > 0); } @@ -241,8 +250,8 @@ type AttemptFacts = { function attemptBuildStage({ deployment, progress, nodes }: DeploymentViewInput, succeeded: boolean, building: boolean): StageState { // Prebuilt images only: nothing to build. if (!nodes.some((node) => node.image)) return "none"; - // Preparation said ready, the Engine planned the rollout, or the attempt succeeded: every image exists. - if (progress?.preparation?.phase === "ready" || deployment.deployPreview || succeeded) return "done"; + // Preparation said ready or is delivering images, the Engine planned the rollout, or the attempt succeeded: every image exists. + if (progress?.preparation?.phase === "ready" || progress?.preparation?.phase === "transfer" || deployment.deployPreview || succeeded) return "done"; if (deployment.status === "failed") return "failed"; if (deployment.status === "cancelled") return "skipped"; // Image Builds report steps from admission, and preparation reports progress once deploy starts; until then the attempt waits. diff --git a/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts b/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts index f74d0bd653..f979ea9dae 100644 --- a/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts +++ b/dashboard/src/modules/deployments/environment-deployment.build-order.inngest-engine.test.ts @@ -136,7 +136,7 @@ describe("walking the Build Order", () => { it("tells GitHub why it is in the walk, so it records the reason when it takes the build", async () => { fake.candidates = imageBuildWalk("servers-then-github", "github"); - expect(fake.candidates).toEqual([{ builder: "github", reason: "preferred" }, { builder: "servers", reason: "first_in_build_order" }]); + expect(fake.candidates).toEqual([{ builder: "github", reason: "preferred", attempt: 0 }, { builder: "servers", reason: "first_in_build_order", attempt: 1 }]); fake.runCompletes = [true]; expect(await outcome()).toMatchObject({ deployed: true }); expect(fake.githubReasons).toEqual(["preferred"]); diff --git a/dashboard/src/modules/deployments/environment-deployment.inngest.ts b/dashboard/src/modules/deployments/environment-deployment.inngest.ts index bae761ee11..7da4d2de98 100644 --- a/dashboard/src/modules/deployments/environment-deployment.inngest.ts +++ b/dashboard/src/modules/deployments/environment-deployment.inngest.ts @@ -205,8 +205,9 @@ async function runImageBuild( const message = skipped ? skipReasonText(skipped) : "No Builder can take this build."; // The trail's last sentence says what happened; this step, that nothing else will take it. const noBuilder = { ...ployzStep("stage:NoBuilder", "Couldn't start the build"), error: "No other Builder in your Build Order can take it." }; + const attempt = (candidates.at(-1)?.attempt ?? -1) + 1; const failed = await step.run(`fail-image-build-${build.serviceId}`, () => runEffect( - persistBuildLog(build.deploymentId, { steps: [noBuilder], output: [] }, build.image).pipe( + persistBuildLog(build.deploymentId, { steps: [noBuilder], output: [] }, { image: build.image, attempt }).pipe( Effect.andThen(settleImageBuild(build, { status: "failed", message, machineId: null }))))); return failed.result; } diff --git a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts index 32c2fbd190..0364b863cb 100644 --- a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts +++ b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts @@ -23,12 +23,13 @@ import { makeInngestEffectRunner, type runInngestEffect } from "#/server/run.ser import { noPairingChanges } from "#/test/organization-runtime"; import { type PostgresTestHarness, startPostgresTestHarness } from "#/test/postgres"; import { makeSecretEncryption, SecretEncryption } from "#/utils/encrypted-secret.server"; -import { loadDeploymentBuildLog } from "./deployment-events.server"; +import { loadDeploymentBuildLog, openBuildStep } from "./deployment-events.server"; import { createMarkCancelledRowBackedWorkflow, createProcessEnvironmentDeployment } from "./environment-deployment.inngest"; import type { BuildOrder } from "./build-order"; import { skipReasonText, type SkipReason } from "./image-build"; import { planImageBuildWalk } from "./build-order.server"; import { buildLogSections, builtOn } from "./deployment-view"; +import { ployzStep } from "./preparation-progress"; import { checkGithubImageBuild, checkInGithubBuild, recordGithubBuildSteps, settleOrMoveReportedGithubBuild } from "./github-image-builds.server"; import { settleGithubImageBuild } from "./image-builds.server"; import { persistDeploymentSourcePin } from "./source-pins.server"; @@ -449,9 +450,9 @@ describe("Image Builds on GitHub Actions", () => { expect(fake.prepared.at(-1)).toEqual({ api: receipt }); const log = await harness.runEffect(loadDeploymentBuildLog({ organizationId, deploymentId, after: 0, limit: 50 })); - // Cloud heads GitHub's section and times the wait for a runner; the runner reports the rest. + // Cloud times the wait for a runner; the runner reports the rest. expect(log.steps.filter((step) => step.image === "api").map((step) => [step.name, step.attempt])).toEqual([ - ["Building", 0], ["GitHub Actions", 0], ["Waiting for a runner", 0], ["RUN make", 0], + ["Building", 0], ["Waiting for a runner", 0], ["RUN make", 0], ]); expect(log.steps.find((step) => step.name === "Waiting for a runner")?.completedAt).not.toBeNull(); expect(log.output.map((line) => line.text)).toEqual(["ok\n"]); @@ -468,8 +469,11 @@ describe("Image Builds on GitHub Actions", () => { // Until check-in, the log waits for a runner. expect((await buildLog()).steps.find((step) => step.name === "Waiting for a runner")?.completedAt).toBeNull(); await checkIn(oidcToken()); + // A retried start never reopens the wait the check-in closed. + await run(openBuildStep(deploymentId, { image: "api", attempt: 0 }, ployzStep("runner", "Waiting for a runner", new Date(), null))); + expect((await buildLog()).steps.find((step) => step.name === "Waiting for a runner")?.completedAt).not.toBeNull(); expect(await post({ from: 0, events: runnerEvents.slice(0, 2) })).toEqual({ received: 2 }); - expect((await buildLog()).steps.filter((step) => step.image === "api" && step.key !== "stage:Builder" && step.key !== "runner").map((step) => step.name)) + expect((await buildLog()).steps.filter((step) => step.image === "api" && step.key !== "runner").map((step) => step.name)) .toEqual(["Building", "RUN make"]); // A retried batch repeats lines already taken; only the new one is filed. expect(await post({ from: 1, events: runnerEvents.slice(1) })).toEqual({ received: 3 }); @@ -762,24 +766,24 @@ describe("Image Builds on GitHub Actions", () => { it("defaults to GitHub first once a repository has the build workflow, and to the servers until then", async () => { await harness.db.delete(schema.organizationBuildOrder); - expect(await plan()).toEqual([{ builder: "servers", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "servers", reason: "first_in_build_order", attempt: 0 }]); // The latest Saved State builds from owner/repo through the GitHub App, whose workflow is ready. await harness.pool.query(`update environment_saved_state_snapshot set intent = jsonb_set(intent, '{services}', '[{"config":{"source":{"type":"git","repository":"owner/repo","repositoryId":42,"access":{"type":"github-installation","installationId":7}}}}]')`); - expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order" }, { builder: "servers", reason: "next_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order", attempt: 0 }, { builder: "servers", reason: "next_in_build_order", attempt: 1 }]); fake.githubErrors.set("fetch_workflow", githubError("fetch_workflow", 404, "not_found")); - expect(await plan()).toEqual([{ builder: "servers", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "servers", reason: "first_in_build_order", attempt: 0 }]); }); it("walks the Build Order alone on Auto", async () => { await buildOrder("github-then-servers"); - expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order" }, { builder: "servers", reason: "next_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order", attempt: 0 }, { builder: "servers", reason: "next_in_build_order", attempt: 1 }]); }); it("tries GitHub first, then the Build Order without it, and records that GitHub was preferred", async () => { await buildOrder("servers-then-github"); await prefer("github"); - expect(await plan()).toEqual([{ builder: "github", reason: "preferred" }, { builder: "servers", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "github", reason: "preferred", attempt: 0 }, { builder: "servers", reason: "first_in_build_order", attempt: 1 }]); await dispatch(); expect(await github()).toMatchObject({ reason: "preferred" }); }); @@ -788,7 +792,7 @@ describe("Image Builds on GitHub Actions", () => { fake.machines = [machine, fast]; await buildOrder("github-only"); await prefer(fast.id); - expect(await plan()).toEqual([{ builder: "servers", reason: "preferred", machineId: fast.id }, { builder: "github", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "servers", reason: "preferred", machineId: fast.id, attempt: 0 }, { builder: "github", reason: "first_in_build_order", attempt: 1 }]); // The whole walk: the preferred Server's go has a start limit, then GitHub gets it. await harness.db.delete(schema.environmentDeploymentImageBuild); await queued(); @@ -803,11 +807,11 @@ describe("Image Builds on GitHub Actions", () => { await buildOrder("github-only"); await prefer(fast.id); fake.machines = [machine, { ...fast, accepts_builds: false }]; - expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order", attempt: 1 }]); expect(await row()).toMatchObject({ skips: [{ builder: "servers", kind: "preferred_unavailable", machineId: fast.id, name: "fast" }] }); fake.machines = [machine]; // GitHub only: a gone preferred Server never puts the build on your servers. - expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order" }]); + expect(await plan()).toEqual([{ builder: "github", reason: "first_in_build_order", attempt: 1 }]); expect(await row()).toMatchObject({ skips: [{ builder: "servers", kind: "preferred_unavailable", machineId: fast.id, name: null }] }); expect((await buildLog()).imageBuilds).toEqual([expect.objectContaining({ skips: [expect.objectContaining({ kind: "preferred_unavailable" })] })]); }); diff --git a/dashboard/src/modules/deployments/github-image-builds.server.ts b/dashboard/src/modules/deployments/github-image-builds.server.ts index e7225210b6..ef62935c50 100644 --- a/dashboard/src/modules/deployments/github-image-builds.server.ts +++ b/dashboard/src/modules/deployments/github-image-builds.server.ts @@ -10,16 +10,16 @@ import { AppConfig } from "#/server/config.server"; import { BuildGrantUnavailable, Conflict, Forbidden, NotFound, Unauthorized, Validation } from "#/server/public-error"; import type { BuildCandidate } from "./build-order"; import { githubSkipReason, installFailedSchema, type GithubImageBuild } from "./image-build"; -import { persistBuildLog } from "./deployment-events.server"; +import { openBuildStep, persistBuildLog } from "./deployment-events.server"; import { awaitsCheckIn, checkInImageBuild, closeOpenSteps, claimForGithub, loadBuildReceipts, loadGithubImageBuilds, imageBuildNow, loadImageBuild, recordGithubReport, recordServerChoice, settleGithubImageBuild, settleImageBuild, moveStartedGithubBuild, skipImageBuilder, skipUnstarted, START_WITHIN_MINUTES, type ImageBuildAttempt, type ImageBuildRow, type ImageBuildTarget, } from "./image-builds.server"; -import { builderStep, ployzStep, preparationProgressCollector, type BuildOutputWrite, type BuildStepWrite } from "./preparation-progress"; +import { ployzStep, preparationProgressCollector, type BuildOutputWrite, type BuildStepWrite } from "./preparation-progress"; import { loadDeploymentContext } from "./runtime-hydration.repository.server"; import type { DeploymentContext } from "./runtime-repository.contract"; -import { connectedRuntime, oneServiceDeployment } from "./runtime-session.server"; +import { connectedRuntime, DeploymentRuntimeUnavailable, oneServiceDeployment } from "./runtime-session.server"; import { pinSourceCommit } from "./runtime-sources.server"; import { reusedImageStep } from "./server-image-builds.server"; @@ -69,7 +69,8 @@ const grantRepository = (image: string) => `ployz-build/${image}`; * through the GitHub App or lacks permission, has no workflow, needs several platforms, or the * dispatch fails. */ -export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuild")(function* (build: ImageBuildTarget, candidate: Pick) { +export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuild")(function* (build: ImageBuildTarget, candidate: Pick) { + const owner = { image: build.image, attempt: candidate.attempt }; const context = yield* loadDeploymentContext(build.deploymentId); const snapshot = context?.snapshots.find((candidate) => candidate.serviceId === build.serviceId); const source = installedSource(snapshot); @@ -94,7 +95,7 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil }).pipe(Effect.scoped); if (outside.kind === "reuse") { yield* recordServerChoice(build.id, outside.receipt.machine_id, { machineName: outside.machine_name, reason: { kind: "reused" } }); - yield* persistBuildLog(build.deploymentId, { steps: [reusedImageStep()], output: [] }, build.image); + yield* persistBuildLog(build.deploymentId, { steps: [reusedImageStep()], output: [] }, owner); return yield* settleImageBuild(build, { status: "built", receipt: outside.receipt }); } const { platforms } = outside; @@ -104,6 +105,9 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil // With no visible placement, deploy's coverage check decides, as it does after a server build. const runner = platforms[0] === "linux/arm64" ? "ubuntu-24.04-arm" : "ubuntu-latest"; const config = yield* AppConfig; + // Cloud sees both ends of the wait for a runner. It opens before the dispatch, so even the fastest + // check-in finds it to close, and only once, so a retried start never reopens a closed wait. + yield* openBuildStep(build.deploymentId, owner, ployzStep("runner", "Waiting for a runner", new Date(), null)); const run = yield* dispatchGithubBuildWorkflow({ installationId: source.installationId, fullName: workflow.fullName, defaultBranch: workflow.defaultBranch, inputs: { build: build.id, cloud: config.app.url.origin, runner }, @@ -114,11 +118,10 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil if (claim.kind === "settled") { // Settled (cancelled) while dispatching: the run must not build. yield* cancelGithubRun({ installationId: source.installationId, fullName: workflow.fullName, runId: run.runId }).pipe(Effect.ignore); + // It settled before the wait opened: nothing else closes it. + yield* closeOpenSteps(build.id); return claim satisfies GithubBuildStart; } - // Cloud sees both ends of the wait for a runner: this dispatch and the check-in, which closes it. - const now = new Date(); - yield* persistBuildLog(build.deploymentId, { steps: [builderStep(GITHUB_BUILDER, now), { ...ployzStep("runner", "Waiting for a runner", now), completedAt: null }], output: [] }, build.image); return { kind: "dispatched", runId: run.runId } satisfies GithubBuildStart; }, (effect, build) => effect.pipe( // A GitHub or SDK failure before dispatch is GitHub being unusable, not the build failing. @@ -127,9 +130,6 @@ export const startGithubImageBuild = Effect.fn("Deployments.startGithubImageBuil type GithubRow = Extract; -/** GitHub as its build log section names it. */ -const GITHUB_BUILDER = "GitHub Actions"; - /** * One look at a dispatched build, after the Workflow run webhook reported its run `ended` or a wait * timed out. A timeout also asks GitHub whether the run completed, which catches a completion that @@ -237,7 +237,7 @@ export const checkInGithubBuild = Effect.fn("Deployments.checkInGithubBuild")(fu Effect.mapError((cause) => new BuildGrantUnavailable({ cause })), ); return { minted, machine }; - }).pipe(Effect.scoped, Effect.tapError(() => checkInFailed(row))); + }).pipe(Effect.scoped, Effect.tapError((error) => checkInFailed(row, error))); const grant = { id: minted.id, fingerprint }; const checkedIn = yield* checkInImageBuild({ imageBuildId: row.id, runId: row.githubRunId, machineId: machine.id, grant }); if (!checkedIn) { @@ -254,16 +254,20 @@ export const checkInGithubBuild = Effect.fn("Deployments.checkInGithubBuild")(fu return { grant: minted.grant, commit, fingerprint, ployzVersion: ployzVersion(), deployment }; }); +/** Why Cloud couldn't start a build its runner checked in for, in the words the log shows. */ +const checkInFailure = (error: Error) => error instanceof BuildGrantUnavailable ? "Your Machine couldn't issue a grant to receive the image." + : error instanceof DeploymentRuntimeUnavailable ? "Cloud couldn't reach your Machine to receive the image." + : "Cloud couldn't start the build on GitHub."; + /** * A check-in the runner arrived for but Cloud couldn't start: the one internal GitHub step the log - * shows, and only failed. Best effort; the run then ends and the walk moves on or fails. + * shows, and only failed. Best effort; the run then ends and the walk moves on or fails. While + * GitHub holds the build, its go is the skip trail's length. */ -const checkInFailed = (row: GithubRow) => Effect.gen(function* () { +const checkInFailed = (row: GithubRow, error: Error) => Effect.gen(function* () { yield* closeOpenSteps(row.id); - const now = new Date(); - yield* persistBuildLog(row.deploymentId, { - steps: [{ ...ployzStep("check-in", "Starting the build", now), error: "Your servers couldn't be reached to receive the image." }], output: [], - }, row.image, row.skips.length); + yield* persistBuildLog(row.deploymentId, { steps: [{ ...ployzStep("check-in", "Starting the build"), error: checkInFailure(error) }], output: [] }, + { image: row.image, attempt: row.skips.length }); }).pipe(Effect.catch((error) => Effect.logWarning("Could not log a failed GitHub check-in.", error))); const buildStepSchema = Schema.Struct({ @@ -321,8 +325,8 @@ export const recordGithubBuildSteps = Effect.fn("Deployments.recordGithubBuildSt const platforms = report.platforms ?? null; // A failed Build Step already shows in its row; a build GitHub failed moves on without one. if (platforms) steps.push(...collector.finish()); - // Filed under this run's section even if the walk moved the build on meanwhile. - yield* persistBuildLog(row.deploymentId, { steps, output }, row.image, row.skips.length); + // GitHub's go, which holds the build as long as it takes reports: filed there even if the walk moves it on meanwhile. + yield* persistBuildLog(row.deploymentId, { steps, output }, { image: row.image, attempt: row.skips.length }); const taken = Math.max(received, report.from + report.events.length); const recorded: Types.Mutable> = { received: taken, collector: collector.checkpoint(), platforms: platforms && [...platforms] }; if (platforms && report.installFailed !== undefined) recorded.installFailed = report.installFailed; diff --git a/dashboard/src/modules/deployments/preparation-progress.test.ts b/dashboard/src/modules/deployments/preparation-progress.test.ts index 1a5bfd8c0a..bdfe5ee489 100644 --- a/dashboard/src/modules/deployments/preparation-progress.test.ts +++ b/dashboard/src/modules/deployments/preparation-progress.test.ts @@ -29,26 +29,32 @@ it("turns phases and BuildKit steps into one step tree with attributed output", expect(progress.event({ Build: { StepOutput: { step: "sha256:a", stderr: false, text: "cached\n" } } }).output.map((row) => row.build)).toEqual([2]); expect(keys(progress.event({ Build: { Stage: "Cleanup" } }).steps)).toEqual([[2, "stage:Building", null], [2, "stage:Cleanup", null]]); expect(keys(progress.event("Transfer").steps)).toEqual([[2, "stage:Cleanup", null], [2, "transfer", null]]); - // Each image's sending is its own step, filed under that image. - expect(progress.event({ Sending: { service: "web", machines: ["hel-2", "hel-3"] } }).steps).toEqual([ - { build: 2, key: "send:web", name: "Sending image to hel-2, hel-3", startedAt: expect.any(Date), completedAt: null, cached: false, error: null, image: "web" }, - ]); const delivered = progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m1" as MachineId } }); expect(delivered.output).toEqual([{ build: 2, step: "transfer", stderr: false, text: "Delivered web:1 to m1\n" }]); - expect(delivered.steps.map((row) => [row.key, row.completedAt !== null])).toEqual([["send:web", true]]); - progress.event({ Sending: { service: "api", machines: ["hel-2"] } }); - // An image still on its way when preparation failed is the one that didn't arrive. - expect(keys(progress.finish("image push failed"))).toEqual([[2, "transfer", "image push failed"], [2, "send:api", "image push failed"]]); + expect(keys(progress.finish())).toEqual([[2, "transfer", null]]); expect(progress.finish()).toEqual([]); }); -it("heads a Server's section when the Engine selects it", () => { +it("says a waited-for build slot plainly and files the push's lines under Pushing image", () => { const progress = preparationProgressCollector(() => new Date(5_000)); - const machine = runtimeWatchMachineFixture("m1", "hel-1"); - expect(progress.event({ Selected: { machine, reason: { kind: "spread" }, rejections: [] } }).steps).toEqual([ - { build: 0, key: "stage:Builder", name: "hel-1", startedAt: new Date(5_000), completedAt: new Date(5_000), cached: false, error: null }, - ]); + expect(progress.event({ Selected: { machine: runtimeWatchMachineFixture("m1", "hel-1"), reason: { kind: "spread" }, rejections: [] } }).steps).toEqual([]); expect(progress.event({ Build: { Stage: "Queued" } }).steps.map((row) => row.name)).toEqual(["Waiting for a free build slot"]); + progress.event({ Build: { Stage: "Building" } }); + expect(progress.event({ Build: { Stage: "Push" } }).steps.map((row) => row.name)).toEqual(["Building", "Pushing image"]); + expect(progress.event({ Build: { Output: Array.from(Buffer.from("5f70bf18a086: Pushed\n")) } })).toMatchObject({ + steps: [], output: [{ build: 1, step: "stage:Push", text: "5f70bf18a086: Pushed\n" }], + }); +}); + +it("tells the deploy log, not the build, which Machines each image goes to", () => { + const progress = preparationProgressCollector(() => new Date(5_000), undefined, (name) => name === "web" ? "service-web" : null); + progress.event("Transfer"); + expect(progress.event({ Sending: { service: "web", machines: ["hel-2", "hel-3"] } })).toMatchObject({ + steps: [], progress: { phase: "transfer", serviceId: "service-web", message: "Sending image to hel-2, hel-3" }, + }); + expect(progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m1" as MachineId } }).progress).toMatchObject({ serviceId: "service-web", message: "Image sent" }); + // One line per image, however many Machines received it. + expect(progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m2" as MachineId } }).progress).toBeNull(); }); it("keeps builder messages in their own row, which fails when the engine blames the build", () => { diff --git a/dashboard/src/modules/deployments/preparation-progress.ts b/dashboard/src/modules/deployments/preparation-progress.ts index bbefdc7bdc..ba0deb5a03 100644 --- a/dashboard/src/modules/deployments/preparation-progress.ts +++ b/dashboard/src/modules/deployments/preparation-progress.ts @@ -2,11 +2,8 @@ import type { PreparationEvent } from "@ployz/sdk"; import { Schema } from "effect"; import type { PreparationProgress } from "./deployment-progress"; -/** - * A build step as the engine reports it: a BuildKit vertex or a Ployz-owned phase. `image` files a - * deploy-time step under one Image Build instead of the writer's own. - */ -export type BuildStepWrite = { build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; cached: boolean; error: string | null; image?: string }; +/** A build step as the engine reports it: a BuildKit vertex or a Ployz-owned phase. */ +export type BuildStepWrite = { build: number; key: string; name: string; startedAt: Date | null; completedAt: Date | null; cached: boolean; error: string | null }; export type BuildOutputWrite = { build: number; step: string; stderr: boolean; text: string }; export type PreparationWrites = { progress: PreparationProgress | null; steps: BuildStepWrite[]; output: BuildOutputWrite[] }; @@ -38,16 +35,9 @@ export const BUILDING_KEY = "stage:Building"; /** Attempt-wide rows the engine files under the last build run: image cleanup and delivery. */ export const CLEANUP_KEY = "stage:Cleanup"; export const TRANSFER_KEY = "transfer"; -/** Heads one Builder's section of an Image Build's log; its name is the Builder: "GitHub Actions" or a Server. */ -export const BUILDER_KEY = "stage:Builder"; -/** Sending one image to the Machines that run it, filed under that image at deploy time. */ -const sendKey = (image: string) => `send:${image}`; - -/** A Ployz-owned step starting at `at`, which the caller may leave open or fail. */ -export const ployzStep = (key: string, name: string, at = new Date()): BuildStepWrite => - ({ build: 0, key, name, startedAt: at, completedAt: at, cached: false, error: null }); -/** The heading row of a Builder's section, as the walk writes it when the Builder takes the build. */ -export const builderStep = (builder: string, at?: Date) => ployzStep(BUILDER_KEY, builder, at); +/** A Ployz-owned step starting at `at`; it ends at once unless `completedAt` is null (open). */ +export const ployzStep = (key: string, name: string, at = new Date(), completedAt: Date | null = at): BuildStepWrite => + ({ build: 0, key, name, startedAt: at, completedAt, cached: false, error: null }); /** * Which step a failed attempt is pinned on: the failed BuildKit step already @@ -73,7 +63,7 @@ const rowId = (build: number, key: string) => `${build}:${key}`; * image, attributing the run's steps and output to that Image Build. * Provider errors and rejection dumps are never logs. */ -export function preparationProgressCollector(now: () => Date = () => new Date(), resume?: CollectorCheckpoint) { +export function preparationProgressCollector(now: () => Date = () => new Date(), resume?: CollectorCheckpoint, serviceIdFor: (name: string) => string | null = () => null) { // ponytail: a multibyte character split across two resumed batches decodes as replacement characters. const decoder = new TextDecoder(); let current: PreparationProgress = { phase: "selection", serviceId: null, machineId: null, machineName: null, message: null }; @@ -121,20 +111,21 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), } if ("Selected" in event) { current = { ...current, machineId: event.Selected.machine.id, machineName: event.Selected.machine.name, message: "Builder selected" }; - return { progress: current, steps: [builderStep(event.Selected.machine.name, now())], output: [] }; + return { progress: current, steps: [], output: [] }; } + // Sending an image is the deploy's, not its build's: it shows in that Service's deploy log. if ("Sending" in event) { const { service, machines } = event.Sending; - const row = { ...create(sendKey(service), `Sending image to ${machines.join(", ")}`), image: service }; - rows.set(rowId(build, row.key), row); - return { progress: null, steps: [{ ...row }], output: [] }; + current = { ...current, phase: "transfer", serviceId: serviceIdFor(service), message: `Sending image to ${machines.join(", ")}` }; + return { progress: current, steps: [], output: [] }; } if ("Delivered" in event) { - const sent = rows.get(rowId(build, sendKey(event.Delivered.service))); - if (sent) sent.completedAt ??= now(); const row = open === null ? undefined : rows.get(open); + const serviceId = serviceIdFor(event.Delivered.service); + const sent = current.phase === "transfer" && current.serviceId === serviceId && current.message?.startsWith("Sending") === true; + if (sent) current = { ...current, message: "Image sent" }; return { - progress: null, steps: sent ? [{ ...sent }] : [], + progress: sent ? current : null, steps: [], output: row ? [{ build: row.build, step: row.key, stderr: false, text: `Delivered ${event.Delivered.image} to ${event.Delivered.machine_id}\n` }] : [], }; } @@ -154,7 +145,12 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), heading.name = build_.Target.name; return { progress: null, steps: [{ ...heading }], output: [] }; } - if ("Output" in build_) return builderLine(decoder.decode(Uint8Array.from(build_.Output), { stream: true })); + if ("Output" in build_) { + const text = decoder.decode(Uint8Array.from(build_.Output), { stream: true }); + // The push reports its own lines, outside any BuildKit step. + const pushing = open === rowId(build, stageKey("Push")) ? rows.get(open) : undefined; + return pushing ? { progress: null, steps: [], output: [{ build, step: pushing.key, stderr: false, text }] } : builderLine(text); + } if ("Step" in build_) { const step = build_.Step; stepFailed ||= step.error !== null; @@ -180,8 +176,6 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), } return [...rows].flatMap(([id, row]) => { if (row.completedAt !== null && id !== blamed) return []; - // An image still being sent when preparation failed is the one that failed to arrive. - if (error && row.key.startsWith("send:")) row.error = error; row.completedAt ??= now(); return [{ ...row }]; }); diff --git a/dashboard/src/modules/deployments/runtime-activities.server.ts b/dashboard/src/modules/deployments/runtime-activities.server.ts index a5eea7a593..8f21a24978 100644 --- a/dashboard/src/modules/deployments/runtime-activities.server.ts +++ b/dashboard/src/modules/deployments/runtime-activities.server.ts @@ -131,7 +131,8 @@ export const executeEnvironmentDeployment = Effect.fn( const progressContext = yield* Effect.context(); const runReport = Effect.runPromiseWith(progressContext); const persistProgress = (program: Effect.Effect) => runReport(reporting.write(program)); - const collector = preparationProgressCollector(); + const collector = preparationProgressCollector(undefined, undefined, + (name) => context.snapshots.find((snapshot) => snapshot.config.privateDns === name)?.serviceId ?? null); const cancelled = Effect.callback((resume) => { const abort = () => resume(Effect.interrupt); if (cancellation.signal.aborted) abort(); diff --git a/dashboard/src/modules/deployments/server-image-builds.server.ts b/dashboard/src/modules/deployments/server-image-builds.server.ts index bb218d8362..eef0865024 100644 --- a/dashboard/src/modules/deployments/server-image-builds.server.ts +++ b/dashboard/src/modules/deployments/server-image-builds.server.ts @@ -33,7 +33,7 @@ type ClusterBuild = * row on every exit. It stops when its attempt ends or is cancelled. */ export const buildOnServers = Effect.fn("Deployments.buildOnServers")(function* ( - build: ImageBuildTarget, candidate: Pick, startWithinMs?: number, + build: ImageBuildTarget, candidate: Pick, startWithinMs?: number, ) { const cancellation = new AbortController(); const collector = preparationProgressCollector(); @@ -42,7 +42,7 @@ export const buildOnServers = Effect.fn("Deployments.buildOnServers")(function* let logged = false; const log = (writes: { steps: BuildStepWrite[]; output: { build: number; step: string; stderr: boolean; text: string }[] }) => { logged ||= writes.steps.length > 0; - return reporting.write(persistBuildLog(build.deploymentId, writes, build.image)); + return reporting.write(persistBuildLog(build.deploymentId, writes, { image: build.image, attempt: candidate.attempt })); }; const outcome: ClusterBuild = yield* Effect.gen(function* () { const context = yield* loadDeploymentContext(build.deploymentId); From 1732650e5ae9bddafb7d83692051d31496d7cbf6 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 26 Sep 2026 03:46:35 +0000 Subject: [PATCH 4/4] Build log review 2: moved Server go keeps its name, runner steps land with their report, sending tracked apart A servers not_started skip carries the Server the Engine chose, so its section still reads "Building on hel-1". A runner report files its steps in the same transaction as the guard that takes it, so one that loses to a move leaves nothing open in GitHub's section. The collector tracks the Service being sent on its own and never leaks it into later phases. --- .../deployments/deployment-view.test.ts | 7 ++++-- .../modules/deployments/deployment-view.ts | 6 ++--- .../github-image-builds.postgres.test.ts | 22 +++++++++++++++++-- .../deployments/github-image-builds.server.ts | 16 +++++++++----- .../src/modules/deployments/image-build.ts | 4 +++- .../deployments/preparation-progress.test.ts | 2 ++ .../deployments/preparation-progress.ts | 17 +++++++++----- .../deployments/server-image-builds.server.ts | 10 +++++++-- 8 files changed, 63 insertions(+), 21 deletions(-) diff --git a/dashboard/src/modules/deployments/deployment-view.test.ts b/dashboard/src/modules/deployments/deployment-view.test.ts index e9de6152b0..53cb95a5eb 100644 --- a/dashboard/src/modules/deployments/deployment-view.test.ts +++ b/dashboard/src/modules/deployments/deployment-view.test.ts @@ -131,8 +131,11 @@ describe("deployment view projection", () => { .map((section) => section.title)).toEqual(["o/r has no Ployz build workflow. Building on hel-1 instead."]); expect(buildLogSections([step(1, 0, "runner", "Waiting for a runner", 0, null, null, "web")], { ...evidence, serverChoice: null, github: { runUrl: "u" }, skips: [] }) .map((section) => section.title)).toEqual(["Building on GitHub Actions"]); - // A Server go that moved on no longer knows its Server. - expect(buildLogSections([step(1, 0, "stage:Queued", "Waiting for a free build slot", 0, 180, null, "web")], { ...evidence, skips: [{ builder: "servers", kind: "not_started", minutes: 3 }] }) + // A Server go that moved on keeps its Server's name on its skip; before the Engine chose one, it is "your servers". + const queued = [step(1, 0, "stage:Queued", "Waiting for a free build slot", 0, 180, null, "web")]; + expect(buildLogSections(queued, { ...evidence, skips: [{ builder: "servers", kind: "not_started", minutes: 3, machineName: "hel-1" }] }) + .map((section) => section.title)).toEqual(["Building on hel-1", "No server started the build within 3 min."]); + expect(buildLogSections(queued, { ...evidence, skips: [{ builder: "servers", kind: "not_started", minutes: 3 }] }) .map((section) => section.title)).toEqual(["Building on your servers", "No server started the build within 3 min."]); // Only the last Builder's go decides the node: GitHub's earlier failure moved on, hel-1 still builds. const view = deploymentView({ diff --git a/dashboard/src/modules/deployments/deployment-view.ts b/dashboard/src/modules/deployments/deployment-view.ts index 3bc431766b..6f22fd2ef8 100644 --- a/dashboard/src/modules/deployments/deployment-view.ts +++ b/dashboard/src/modules/deployments/deployment-view.ts @@ -62,7 +62,8 @@ export function builtOn(log: Pick | null | undefined, i /** Who took one go at an Image Build: the Builder its skip names, or the one that holds the build now. Null for a reused image. */ function goBuilder(skipped: SkipReason | undefined, evidence: ImageBuildEvidence | undefined): string | null { - if (skipped) return skipped.builder === "github" ? "GitHub Actions" : "your servers"; + if (skipped?.builder === "github") return "GitHub Actions"; + if (skipped) return skipped.kind === "not_started" && skipped.machineName !== undefined ? skipped.machineName : "your servers"; if (evidence?.github) return "GitHub Actions"; const choice = evidence?.serverChoice; return choice && choice.reason.kind !== "reused" ? choice.machineName : null; @@ -75,8 +76,7 @@ export type BuildLogSection = { title: string | null; runUrl: string | nul * An Image Build's log as one timeline: a section per Builder's go, in order. The first says where it * builds; each later one first says, from the skip trail, why the build moved: "GitHub couldn't * finish this build: its runner stopped. Building on hel-1 instead." A go's Builder is the one its - * skip names, or the current holder for the last; a Server go that moved on no longer names its - * Server. A Builder skipped before it wrote a step leaves only that sentence. The deploy step's own + * skip names, or the current holder for the last. A Builder skipped before it wrote a step leaves only that sentence. The deploy step's own * rows happened after every go, so they join the last. */ export function buildLogSections(steps: readonly Step[], evidence: ImageBuildEvidence | undefined): BuildLogSection[] { diff --git a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts index 0364b863cb..b96665c65d 100644 --- a/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts +++ b/dashboard/src/modules/deployments/github-image-builds.postgres.test.ts @@ -31,7 +31,8 @@ import { planImageBuildWalk } from "./build-order.server"; import { buildLogSections, builtOn } from "./deployment-view"; import { ployzStep } from "./preparation-progress"; import { checkGithubImageBuild, checkInGithubBuild, recordGithubBuildSteps, settleOrMoveReportedGithubBuild } from "./github-image-builds.server"; -import { settleGithubImageBuild } from "./image-builds.server"; +import * as imageBuilds from "./image-builds.server"; +import { loadImageBuild, moveStartedGithubBuild, settleGithubImageBuild } from "./image-builds.server"; import { persistDeploymentSourcePin } from "./source-pins.server"; const organizationId = "00000000-0000-4000-8000-000000000801"; @@ -43,7 +44,8 @@ const deploymentId = "00000000-0000-4000-8000-000000000806"; const serviceId = "00000000-0000-4000-8000-000000000807"; const runId = "github-build-run"; const githubRunId = 9001; -const serversNotStarted: SkipReason = { builder: "servers", kind: "not_started", minutes: 3 }; +// The servers queued it on the Server the Engine chose, which the skip keeps for the log. +const serversNotStarted: SkipReason = { builder: "servers", kind: "not_started", minutes: 3, machineName: "entry" }; const githubNotStarted: SkipReason = { builder: "github", kind: "not_started", minutes: 3 }; const commit = "a".repeat(40); const pushed = `sha256:${"e".repeat(64)}`; @@ -545,6 +547,22 @@ describe("Image Builds on GitHub Actions", () => { expect(steps.filter((step) => step.startedAt !== null && step.completedAt === null && step.attempt === 0)).toEqual([]); }, 30_000); + it("files nothing from a report that loses to a move, so GitHub's section stays closed", async () => { + await buildOrder("github-then-servers"); + await dispatch(); + await checkIn(oidcToken()); + await post({ from: 0, events: runnerEvents.slice(0, 1) }); + // The report loaded the row while GitHub still held it; the walk moves the build before the report lands. + const stale = await run(loadImageBuild(await imageBuildId())); + expect(await run(moveStartedGithubBuild(await target(), githubRunId, { builder: "github", kind: "runner_stopped" }))).toMatchObject({ kind: "skipped" }); + const load = vi.spyOn(imageBuilds, "loadImageBuild").mockReturnValueOnce(Effect.succeed(stale)); + expect(await refused({ from: 1, events: runnerEvents.slice(1) })).toMatchObject({ _tag: "Conflict" }); + load.mockRestore(); + const steps = (await buildLog()).steps.filter((step) => step.image === "api"); + expect(steps.map((step) => step.name)).not.toContain("RUN make"); + expect(steps.filter((step) => step.startedAt !== null && step.completedAt === null)).toEqual([]); + }); + it("leaves a build the walk moved to the servers alone when a late GitHub report or settle arrives", async () => { await buildOrder("github-then-servers"); await dispatch(); diff --git a/dashboard/src/modules/deployments/github-image-builds.server.ts b/dashboard/src/modules/deployments/github-image-builds.server.ts index ef62935c50..fd780f683f 100644 --- a/dashboard/src/modules/deployments/github-image-builds.server.ts +++ b/dashboard/src/modules/deployments/github-image-builds.server.ts @@ -7,6 +7,7 @@ import { sendInngestEvent } from "#/modules/inngest/client"; import { createGithubBuildRunCompletedEvent } from "#/modules/inngest/events"; import { buildFingerprints, buildGrantTag, ployzVersion } from "#/modules/runtime/ployz.server"; import { AppConfig } from "#/server/config.server"; +import { Database } from "#/server/database.server"; import { BuildGrantUnavailable, Conflict, Forbidden, NotFound, Unauthorized, Validation } from "#/server/public-error"; import type { BuildCandidate } from "./build-order"; import { githubSkipReason, installFailedSchema, type GithubImageBuild } from "./image-build"; @@ -325,14 +326,19 @@ export const recordGithubBuildSteps = Effect.fn("Deployments.recordGithubBuildSt const platforms = report.platforms ?? null; // A failed Build Step already shows in its row; a build GitHub failed moves on without one. if (platforms) steps.push(...collector.finish()); - // GitHub's go, which holds the build as long as it takes reports: filed there even if the walk moves it on meanwhile. - yield* persistBuildLog(row.deploymentId, { steps, output }, { image: row.image, attempt: row.skips.length }); const taken = Math.max(received, report.from + report.events.length); const recorded: Types.Mutable> = { received: taken, collector: collector.checkpoint(), platforms: platforms && [...platforms] }; if (platforms && report.installFailed !== undefined) recorded.installFailed = report.installFailed; - if (!(yield* recordGithubReport(row.id, received, recorded))) { - return yield* new Conflict({ message: "Another report of this build was taken first." }); - } + // The steps land with the report that takes them, in one transaction: a report that lost to a move + // (or another report) files nothing, so none reopens in GitHub's section after the move closed it. + // While GitHub holds the build, its go is the skip trail's length. + const database = yield* Database; + const accepted = yield* database.transaction(Effect.gen(function* () { + if (!(yield* recordGithubReport(row.id, received, recorded))) return false; + yield* persistBuildLog(row.deploymentId, { steps, output }, { image: row.image, attempt: row.skips.length }); + return true; + })); + if (!accepted) return yield* new Conflict({ message: "Another report of this build was taken first." }); if (platforms) { // The runner reports its end once the image is pushed, before its run completes (it still // uploads build cache), so the report settles the build and wakes the waiting walk. A build diff --git a/dashboard/src/modules/deployments/image-build.ts b/dashboard/src/modules/deployments/image-build.ts index 035d2fad38..ef2265f66b 100644 --- a/dashboard/src/modules/deployments/image-build.ts +++ b/dashboard/src/modules/deployments/image-build.ts @@ -27,7 +27,9 @@ export const skipReasonSchema = Schema.Union([ /** The Service's Preferred Server is gone (`name` null) or no longer accepts builds: the walk went back to Auto. */ Schema.Struct({ builder: Schema.Literal("servers"), kind: Schema.Literal("preferred_unavailable"), machineId: rustMachineIdSchema, name: Schema.NullOr(Schema.String) }), /** It hadn't started the build within its "start within" limit. */ - Schema.Struct({ builder: Schema.Literals(["github", "servers"]), kind: Schema.Literal("not_started"), minutes: Schema.Number }), + Schema.Struct({ builder: Schema.Literal("github"), kind: Schema.Literal("not_started"), minutes: Schema.Number }), + /** No Server started it in time; `machineName` is the Server it waited on, once the Engine chose one. */ + Schema.Struct({ builder: Schema.Literal("servers"), kind: Schema.Literal("not_started"), minutes: Schema.Number, machineName: Schema.optionalKey(Schema.String) }), ]); export type SkipReason = typeof skipReasonSchema.Type; diff --git a/dashboard/src/modules/deployments/preparation-progress.test.ts b/dashboard/src/modules/deployments/preparation-progress.test.ts index bdfe5ee489..9c15530f56 100644 --- a/dashboard/src/modules/deployments/preparation-progress.test.ts +++ b/dashboard/src/modules/deployments/preparation-progress.test.ts @@ -55,6 +55,8 @@ it("tells the deploy log, not the build, which Machines each image goes to", () expect(progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m1" as MachineId } }).progress).toMatchObject({ serviceId: "service-web", message: "Image sent" }); // One line per image, however many Machines received it. expect(progress.event({ Delivered: { image: "web:1", service: "web", machine_id: "m2" as MachineId } }).progress).toBeNull(); + // The Service stays out of whatever phase comes next. + expect(progress.current().serviceId).toBeNull(); }); it("keeps builder messages in their own row, which fails when the engine blames the build", () => { diff --git a/dashboard/src/modules/deployments/preparation-progress.ts b/dashboard/src/modules/deployments/preparation-progress.ts index ba0deb5a03..271bcddab9 100644 --- a/dashboard/src/modules/deployments/preparation-progress.ts +++ b/dashboard/src/modules/deployments/preparation-progress.ts @@ -74,6 +74,8 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), let open: string | null = resume?.open ?? null; let build = resume?.build ?? 0; let stepFailed = resume?.stepFailed ?? false; + /** The Service whose image is on its way; only its own progress names it, never a later phase. */ + let sending: string | null = null; let finished = false; const create = (key: string, name: string): BuildStepWrite => { const row = { build, key, name, startedAt: now(), completedAt: null, cached: false, error: null }; @@ -116,16 +118,19 @@ export function preparationProgressCollector(now: () => Date = () => new Date(), // Sending an image is the deploy's, not its build's: it shows in that Service's deploy log. if ("Sending" in event) { const { service, machines } = event.Sending; - current = { ...current, phase: "transfer", serviceId: serviceIdFor(service), message: `Sending image to ${machines.join(", ")}` }; - return { progress: current, steps: [], output: [] }; + sending = serviceIdFor(service); + current = { ...current, phase: "transfer", message: `Sending image to ${machines.join(", ")}` }; + return { progress: { ...current, serviceId: sending }, steps: [], output: [] }; } if ("Delivered" in event) { const row = open === null ? undefined : rows.get(open); - const serviceId = serviceIdFor(event.Delivered.service); - const sent = current.phase === "transfer" && current.serviceId === serviceId && current.message?.startsWith("Sending") === true; - if (sent) current = { ...current, message: "Image sent" }; + const sent = sending !== null && sending === serviceIdFor(event.Delivered.service) ? sending : null; + if (sent !== null) { + sending = null; + current = { ...current, message: "Image sent" }; + } return { - progress: sent ? current : null, steps: [], + progress: sent === null ? null : { ...current, serviceId: sent }, steps: [], output: row ? [{ build: row.build, step: row.key, stderr: false, text: `Delivered ${event.Delivered.image} to ${event.Delivered.machine_id}\n` }] : [], }; } diff --git a/dashboard/src/modules/deployments/server-image-builds.server.ts b/dashboard/src/modules/deployments/server-image-builds.server.ts index eef0865024..b8af437e90 100644 --- a/dashboard/src/modules/deployments/server-image-builds.server.ts +++ b/dashboard/src/modules/deployments/server-image-builds.server.ts @@ -1,6 +1,7 @@ import "@tanstack/react-start/server-only"; import type { BuildReceipt, MachineId } from "@ployz/sdk"; -import { Effect } from "effect"; +import { Effect, type Types } from "effect"; +import type { SkipReason } from "./image-build"; import { errorEvidenceFrom } from "#/lib/error-evidence"; import { PloyzPreparationError } from "#/modules/runtime/ployz.server"; import { Database, ReportingDatabase } from "#/server/database.server"; @@ -39,6 +40,7 @@ export const buildOnServers = Effect.fn("Deployments.buildOnServers")(function* const collector = preparationProgressCollector(); const reporting = deploymentReporting(); let machineId: MachineId | null = null; + let machineName: string | undefined; let logged = false; const log = (writes: { steps: BuildStepWrite[]; output: { build: number; step: string; stderr: boolean; text: string }[] }) => { logged ||= writes.steps.length > 0; @@ -60,6 +62,7 @@ export const buildOnServers = Effect.fn("Deployments.buildOnServers")(function* if (event !== "Transfer" && "Selected" in event) { const { machine, reason } = event.Selected; machineId = machine.id; + machineName = machine.name; await Effect.runPromiseWith(progressContext)(recordServerChoice(build.id, machine.id, { machineName: machine.name, reason })); } const writes = collector.event(event); @@ -85,7 +88,10 @@ export const buildOnServers = Effect.fn("Deployments.buildOnServers")(function* yield* log({ steps, output: [] }); switch (outcome.kind) { case "queued": { - return yield* skipUnstarted(build, { builder: "servers", kind: "not_started", minutes: START_WITHIN_MINUTES }); + // The Server's name leaves the row with the skip; the skip keeps it for the log's heading. + const reason: Types.Mutable = { builder: "servers", kind: "not_started", minutes: START_WITHIN_MINUTES }; + if (machineName !== undefined) reason.machineName = machineName; + return yield* skipUnstarted(build, reason); } case "built": return yield* settleImageBuild(build, { status: "built", receipt: outcome.receipt }); case "failed": return yield* settleImageBuild(build, { status: "failed", message: outcome.message, machineId });