diff --git a/.playwright-mcp/console-2026-03-15T17-34-15-943Z.log b/.playwright-mcp/console-2026-03-15T17-34-15-943Z.log new file mode 100644 index 0000000..21d69a5 --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T17-34-15-943Z.log @@ -0,0 +1,29 @@ +[ 4219ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 73166ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 74210ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 76255ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 80299ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 121662ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 122707ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 124752ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 132300ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 133345ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 135391ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 163462ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 165512ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 169559ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 206249ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 208295ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 229228ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 231273ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 240041ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 242090ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 246135ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 254184ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 270230ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 300275ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 335152ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 337223ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 341292ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 349341ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 365388ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 diff --git a/.playwright-mcp/console-2026-03-15T17-40-47-271Z.log b/.playwright-mcp/console-2026-03-15T17-40-47-271Z.log new file mode 100644 index 0000000..9ea53d1 --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T17-40-47-271Z.log @@ -0,0 +1 @@ +[ 80ms] [ERROR] Failed to load resource: the server responded with a status of 500 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example:0 diff --git a/.playwright-mcp/console-2026-03-15T17-40-57-477Z.log b/.playwright-mcp/console-2026-03-15T17-40-57-477Z.log new file mode 100644 index 0000000..e6e3d54 --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T17-40-57-477Z.log @@ -0,0 +1,70 @@ +[ 28066ms] [ERROR] Failed to load resource: net::ERR_NETWORK_CHANGED @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 29193ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 31313ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 36650ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 38721ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 180246ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 181291ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 183336ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 187382ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 195427ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 211472ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 308815ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 309884ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 311956ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 316026ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 324096ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 400986ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 411491ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 445156ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 649304ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 659749ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 675977ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 678046ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 754725ms] [ERROR] Failed to load resource: net::ERR_NETWORK_CHANGED @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 755871ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 757999ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 763330ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 765397ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 978143ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 980214ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 984281ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1080173ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1082246ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1086330ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1121684ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1123773ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1141045ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1143115ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1147188ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1155258ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1502223ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1504292ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1513751ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1515821ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1519891ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1527937ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1543982ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1574052ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1654209ms] [ERROR] Failed to load resource: net::ERR_NETWORK_CHANGED @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1655305ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1657399ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1661443ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1775829ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1776875ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1778919ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1783494ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1784539ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1786584ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1821582ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1822627ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1824676ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1861759ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1864040ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1865688ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1867222ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1868813ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1870286ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1871945ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1873590ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 1875131ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 diff --git a/.playwright-mcp/console-2026-03-15T18-28-02-722Z.log b/.playwright-mcp/console-2026-03-15T18-28-02-722Z.log new file mode 100644 index 0000000..4a96684 --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T18-28-02-722Z.log @@ -0,0 +1,7 @@ +[ 8921ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 93203ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 94247ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 96291ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 124526ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 126598ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 130667ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 diff --git a/.playwright-mcp/console-2026-03-15T18-30-49-631Z.log b/.playwright-mcp/console-2026-03-15T18-30-49-631Z.log new file mode 100644 index 0000000..b0c1d8e --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T18-30-49-631Z.log @@ -0,0 +1,6 @@ +[ 9418ms] [ERROR] Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 10488ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 12557ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 16626ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 24672ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 +[ 40718ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example/events:0 diff --git a/.playwright-mcp/console-2026-03-15T18-31-57-555Z.log b/.playwright-mcp/console-2026-03-15T18-31-57-555Z.log new file mode 100644 index 0000000..0b062b6 --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T18-31-57-555Z.log @@ -0,0 +1 @@ +[ 53ms] [ERROR] Failed to load resource: the server responded with a status of 502 () @ chrome-error://chromewebdata/:0 diff --git a/.playwright-mcp/console-2026-03-15T18-32-21-150Z.log b/.playwright-mcp/console-2026-03-15T18-32-21-150Z.log new file mode 100644 index 0000000..e9f56ea --- /dev/null +++ b/.playwright-mcp/console-2026-03-15T18-32-21-150Z.log @@ -0,0 +1 @@ +[ 55ms] [ERROR] Failed to load resource: the server responded with a status of 500 () @ https://client.dev.forage.sh/orgs/rawpotion/projects/service-example:0 diff --git a/CLAUDE.md b/CLAUDE.md index 50afd0f..b3644c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,6 +90,7 @@ Follow the VSDD pipeline **religiously**. No shortcuts, no skipping phases. - Routes are organized by feature in `routes/` modules - All public API endpoints return proper HTTP status codes - Configuration via environment variables with sensible defaults +- **Forms with conditional sections**: When a form has multiple sections toggled by a dropdown (e.g. policy type), inputs in hidden sections **must be disabled** so they are excluded from submission. Duplicate `name` attributes across sections cause axum's form deserializer to fail with "unsupported value". Always call the toggle function on page load to disable hidden inputs from the start. - **Tests live in separate files**, never inline in the main source file: - Unit tests for private functions: `#[cfg(test)] mod tests` in the same file (e.g., `forest_client.rs`) - Route/integration tests: `src/tests/` directory with files per feature area (e.g., `auth_tests.rs`, `platform_tests.rs`) diff --git a/crates/forage-core/src/platform/mod.rs b/crates/forage-core/src/platform/mod.rs index 4341d7e..609a00e 100644 --- a/crates/forage-core/src/platform/mod.rs +++ b/crates/forage-core/src/platform/mod.rs @@ -247,6 +247,10 @@ pub enum PolicyConfig { target_environment: String, branch_pattern: String, }, + Approval { + target_environment: String, + required_approvals: i32, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -267,6 +271,24 @@ pub struct PolicyEvaluation { pub policy_type: String, pub passed: bool, pub reason: String, + #[serde(default)] + pub approval_state: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApprovalState { + pub required_approvals: i32, + pub current_approvals: i32, + pub decisions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ApprovalDecisionEntry { + pub user_id: String, + pub username: String, + pub decision: String, + pub decided_at: String, + pub comment: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -567,6 +589,45 @@ pub trait ForestPlatform: Send + Sync { channel: &str, enabled: bool, ) -> Result<(), PlatformError>; + + async fn evaluate_policies( + &self, + access_token: &str, + organisation: &str, + project: &str, + target_environment: &str, + release_intent_id: Option<&str>, + ) -> Result, PlatformError>; + + async fn approve_release( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + comment: Option<&str>, + force_bypass: bool, + ) -> Result; + + async fn reject_release( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + comment: Option<&str>, + ) -> Result; + + async fn get_approval_state( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + ) -> Result; } #[cfg(test)] diff --git a/crates/forage-grpc/src/grpc/forest/v1/forest.v1.rs b/crates/forage-grpc/src/grpc/forest/v1/forest.v1.rs index 7aca494..81938ec 100644 --- a/crates/forage-grpc/src/grpc/forest/v1/forest.v1.rs +++ b/crates/forage-grpc/src/grpc/forest/v1/forest.v1.rs @@ -167,8 +167,10 @@ pub struct CommitArtifactResponse { } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetArtifactFilesRequest { + /// The artifact_id (UUID from annotations/artifacts table) #[prost(string, tag="1")] pub artifact_id: ::prost::alloc::string::String, + /// Optional filter: "deployment", "spec", "attachment". Empty = all categories. #[prost(string, optional, tag="2")] pub category: ::core::option::Option<::prost::alloc::string::String>, } @@ -197,10 +199,775 @@ pub struct GetArtifactSpecRequest { } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetArtifactSpecResponse { + /// The spec file content (forest.cue), empty string if no spec was uploaded. #[prost(string, tag="1")] pub content: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct AnnotateReleaseRequest { + #[prost(string, tag="1")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(map="string, string", tag="2")] + pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, optional, tag="3")] + pub source: ::core::option::Option, + #[prost(message, optional, tag="4")] + pub context: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub project: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub r#ref: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AnnotateReleaseResponse { + #[prost(message, optional, tag="1")] + pub artifact: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetArtifactBySlugRequest { + #[prost(string, tag="1")] + pub slug: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetArtifactBySlugResponse { + #[prost(message, optional, tag="1")] + pub artifact: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetArtifactsByProjectRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetArtifactsByProjectResponse { + #[prost(message, repeated, tag="1")] + pub artifact: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseRequest { + #[prost(string, tag="1")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(string, repeated, tag="2")] + pub destinations: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="3")] + pub environments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, tag="4")] + pub force: bool, + /// When true, use the project's release pipeline (DAG) instead of + /// deploying directly to the specified destinations/environments. + #[prost(bool, tag="5")] + pub use_pipeline: bool, + /// When true, create a plan-only pipeline (single Plan stage, no deploy). + #[prost(bool, tag="6")] + pub prepare_only: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReleaseResponse { + /// List of release intents created (one per destination) + #[prost(message, repeated, tag="1")] + pub intents: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseIntent { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub destination: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub environment: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct WaitReleaseRequest { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct WaitReleaseEvent { + #[prost(oneof="wait_release_event::Event", tags="1, 2, 3")] + pub event: ::core::option::Option, +} +/// Nested message and enum types in `WaitReleaseEvent`. +pub mod wait_release_event { + #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum Event { + #[prost(message, tag="1")] + StatusUpdate(super::ReleaseStatusUpdate), + #[prost(message, tag="2")] + LogLine(super::ReleaseLogLine), + #[prost(message, tag="3")] + StageUpdate(super::PipelineStageUpdate), + } +} +/// Streamed in WaitRelease for pipeline releases: reports stage status changes. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PipelineStageUpdate { + #[prost(string, tag="1")] + pub stage_id: ::prost::alloc::string::String, + /// "deploy", "wait" + #[prost(string, tag="2")] + pub stage_type: ::prost::alloc::string::String, + /// PENDING, ACTIVE, SUCCEEDED, FAILED, CANCELLED + #[prost(string, tag="3")] + pub status: ::prost::alloc::string::String, + #[prost(string, optional, tag="4")] + pub queued_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub started_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub completed_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub wait_until: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="9")] + pub approval_status: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseStatusUpdate { + #[prost(string, tag="1")] + pub destination: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub status: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseLogLine { + #[prost(string, tag="1")] + pub destination: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub line: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub timestamp: ::prost::alloc::string::String, + #[prost(enumeration="LogChannel", tag="4")] + pub channel: i32, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetOrganisationsRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOrganisationsResponse { + #[prost(message, repeated, tag="1")] + pub organisations: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetProjectsRequest { + #[prost(oneof="get_projects_request::Query", tags="1")] + pub query: ::core::option::Option, +} +/// Nested message and enum types in `GetProjectsRequest`. +pub mod get_projects_request { + #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum Query { + #[prost(message, tag="1")] + Organisation(super::OrganisationRef), + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetProjectsResponse { + #[prost(string, repeated, tag="1")] + pub projects: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CreateProjectRequest { + #[prost(string, tag="1")] + pub organisation: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub project: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CreateProjectResponse { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetReleasesByActorRequest { + /// user_id or app_id + #[prost(string, tag="1")] + pub actor_id: ::prost::alloc::string::String, + /// "user" or "app" + #[prost(string, tag="2")] + pub actor_type: ::prost::alloc::string::String, + #[prost(int32, tag="3")] + pub page_size: i32, + #[prost(string, tag="4")] + pub page_token: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetReleasesByActorResponse { + #[prost(message, repeated, tag="1")] + pub releases: ::prost::alloc::vec::Vec, + #[prost(string, tag="2")] + pub next_page_token: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReleaseIntentSummary { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(message, optional, tag="3")] + pub project: ::core::option::Option, + #[prost(message, repeated, tag="4")] + pub destinations: ::prost::alloc::vec::Vec, + #[prost(string, tag="5")] + pub created_at: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseDestinationStatus { + #[prost(string, tag="1")] + pub destination: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub environment: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub status: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetDestinationStatesRequest { + #[prost(string, tag="1")] + pub organisation: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub project: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDestinationStatesResponse { + #[prost(message, repeated, tag="1")] + pub destinations: ::prost::alloc::vec::Vec, + /// Active pipeline runs affecting these destinations (if any). + #[prost(message, repeated, tag="2")] + pub pipeline_runs: ::prost::alloc::vec::Vec, +} +// ── Release intent states (release-centric view) ───────────────────── + +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetReleaseIntentStatesRequest { + #[prost(string, tag="1")] + pub organisation: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub project: ::core::option::Option<::prost::alloc::string::String>, + /// When true, also include recently completed release intents. + #[prost(bool, tag="3")] + pub include_completed: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetReleaseIntentStatesResponse { + #[prost(message, repeated, tag="1")] + pub release_intents: ::prost::alloc::vec::Vec, +} +/// Full state of a release intent: pipeline stages + individual release steps. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReleaseIntentState { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub project: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub created_at: ::prost::alloc::string::String, + /// Pipeline stages (empty for non-pipeline releases). + #[prost(message, repeated, tag="5")] + pub stages: ::prost::alloc::vec::Vec, + /// All release_states rows for this intent (deploy steps). + #[prost(message, repeated, tag="6")] + pub steps: ::prost::alloc::vec::Vec, +} +/// Status of a single pipeline stage (saga coordinator view). +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PipelineStageState { + #[prost(string, tag="1")] + pub stage_id: ::prost::alloc::string::String, + #[prost(string, repeated, tag="2")] + pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(enumeration="PipelineRunStageType", tag="3")] + pub stage_type: i32, + #[prost(enumeration="PipelineRunStageStatus", tag="4")] + pub status: i32, + /// Consistent timestamps for all stage types. + #[prost(string, optional, tag="5")] + pub queued_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub started_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub completed_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, + /// Type-specific context. + /// + /// deploy/plan stages + #[prost(string, optional, tag="9")] + pub environment: ::core::option::Option<::prost::alloc::string::String>, + /// wait stages + #[prost(int64, optional, tag="10")] + pub duration_seconds: ::core::option::Option, + /// wait stages + #[prost(string, optional, tag="11")] + pub wait_until: ::core::option::Option<::prost::alloc::string::String>, + /// deploy/plan stages: individual release IDs + #[prost(string, repeated, tag="12")] + pub release_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// plan stages: AWAITING_APPROVAL, APPROVED, REJECTED + #[prost(string, optional, tag="13")] + pub approval_status: ::core::option::Option<::prost::alloc::string::String>, + /// plan stages + #[prost(bool, optional, tag="14")] + pub auto_approve: ::core::option::Option, +} +/// Status of a single release step (release_states row). +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ReleaseStepState { + #[prost(string, tag="1")] + pub release_id: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub stage_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, tag="3")] + pub destination_name: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub environment: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub status: ::prost::alloc::string::String, + #[prost(string, optional, tag="6")] + pub queued_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub assigned_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub started_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="9")] + pub completed_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="10")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DestinationState { + #[prost(string, tag="1")] + pub destination_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub destination_name: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub environment: ::prost::alloc::string::String, + #[prost(string, optional, tag="4")] + pub release_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub artifact_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub status: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub queued_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="9")] + pub completed_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(int32, optional, tag="10")] + pub queue_position: ::core::option::Option, + /// Pipeline context: set when this release was created by a pipeline stage. + #[prost(string, optional, tag="11")] + pub release_intent_id: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="12")] + pub stage_id: ::core::option::Option<::prost::alloc::string::String>, + /// When a runner was assigned to this release. + #[prost(string, optional, tag="13")] + pub assigned_at: ::core::option::Option<::prost::alloc::string::String>, + /// When the runner actually started executing. + #[prost(string, optional, tag="14")] + pub started_at: ::core::option::Option<::prost::alloc::string::String>, +} +// ── Pipeline run progress ──────────────────────────────────────────── + +/// Snapshot of an active (or recently completed) pipeline run. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PipelineRunState { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub created_at: ::prost::alloc::string::String, + #[prost(message, repeated, tag="4")] + pub stages: ::prost::alloc::vec::Vec, +} +/// Status of a single stage within a pipeline run. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PipelineRunStage { + #[prost(string, tag="1")] + pub stage_id: ::prost::alloc::string::String, + #[prost(string, repeated, tag="2")] + pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(enumeration="PipelineRunStageType", tag="3")] + pub stage_type: i32, + #[prost(enumeration="PipelineRunStageStatus", tag="4")] + pub status: i32, + /// Type-specific context + /// + /// deploy stages + #[prost(string, optional, tag="5")] + pub environment: ::core::option::Option<::prost::alloc::string::String>, + /// wait stages + #[prost(int64, optional, tag="6")] + pub duration_seconds: ::core::option::Option, + /// when dependencies were met + #[prost(string, optional, tag="7")] + pub queued_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub started_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="9")] + pub completed_at: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="10")] + pub error_message: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="11")] + pub wait_until: ::core::option::Option<::prost::alloc::string::String>, + /// deploy stages: individual release IDs + #[prost(string, repeated, tag="12")] + pub release_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// plan stages: AWAITING_APPROVAL, APPROVED, REJECTED + #[prost(string, optional, tag="13")] + pub approval_status: ::core::option::Option<::prost::alloc::string::String>, + /// plan stages + #[prost(bool, optional, tag="14")] + pub auto_approve: ::core::option::Option, +} +// ── Plan stage approval ────────────────────────────────────────────── + +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ApprovePlanStageRequest { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub stage_id: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ApprovePlanStageResponse { +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RejectPlanStageRequest { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub stage_id: ::prost::alloc::string::String, + #[prost(string, optional, tag="3")] + pub reason: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RejectPlanStageResponse { +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetPlanOutputRequest { + #[prost(string, tag="1")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub stage_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetPlanOutputResponse { + #[prost(string, tag="1")] + pub plan_output: ::prost::alloc::string::String, + /// RUNNING, AWAITING_APPROVAL, APPROVED, REJECTED + #[prost(string, tag="2")] + pub status: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Source { + #[prost(string, optional, tag="1")] + pub user: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="2")] + pub email: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="3")] + pub source_type: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub run_url: ::core::option::Option<::prost::alloc::string::String>, + /// The actor ID (user, app, or service account UUID) that created this annotation. + #[prost(string, optional, tag="5")] + pub user_id: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ArtifactContext { + #[prost(string, tag="1")] + pub title: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub description: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="3")] + pub web: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub pr: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Artifact { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub artifact_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub slug: ::prost::alloc::string::String, + #[prost(map="string, string", tag="4")] + pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, optional, tag="5")] + pub source: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub context: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub project: ::core::option::Option, + #[prost(message, repeated, tag="8")] + pub destinations: ::prost::alloc::vec::Vec, + #[prost(string, tag="9")] + pub created_at: ::prost::alloc::string::String, + #[prost(message, optional, tag="10")] + pub r#ref: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ArtifactDestination { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub environment: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub type_organisation: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub type_name: ::prost::alloc::string::String, + #[prost(uint64, tag="5")] + pub type_version: u64, + #[prost(string, tag="6")] + pub status: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Project { + #[prost(string, tag="1")] + pub organisation: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub project: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Ref { + #[prost(string, tag="1")] + pub commit_sha: ::prost::alloc::string::String, + #[prost(string, optional, tag="2")] + pub branch: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="3")] + pub commit_message: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub version: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub repo_url: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct OrganisationRef { + #[prost(string, tag="1")] + pub organisation: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum LogChannel { + Unspecified = 0, + Stdout = 1, + Stderr = 2, +} +impl LogChannel { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "LOG_CHANNEL_UNSPECIFIED", + Self::Stdout => "LOG_CHANNEL_STDOUT", + Self::Stderr => "LOG_CHANNEL_STDERR", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "LOG_CHANNEL_UNSPECIFIED" => Some(Self::Unspecified), + "LOG_CHANNEL_STDOUT" => Some(Self::Stdout), + "LOG_CHANNEL_STDERR" => Some(Self::Stderr), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum PipelineRunStageType { + Unspecified = 0, + Deploy = 1, + Wait = 2, + Plan = 3, +} +impl PipelineRunStageType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "PIPELINE_RUN_STAGE_TYPE_UNSPECIFIED", + Self::Deploy => "PIPELINE_RUN_STAGE_TYPE_DEPLOY", + Self::Wait => "PIPELINE_RUN_STAGE_TYPE_WAIT", + Self::Plan => "PIPELINE_RUN_STAGE_TYPE_PLAN", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "PIPELINE_RUN_STAGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified), + "PIPELINE_RUN_STAGE_TYPE_DEPLOY" => Some(Self::Deploy), + "PIPELINE_RUN_STAGE_TYPE_WAIT" => Some(Self::Wait), + "PIPELINE_RUN_STAGE_TYPE_PLAN" => Some(Self::Plan), + _ => None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum PipelineRunStageStatus { + Unspecified = 0, + Pending = 1, + Active = 2, + Succeeded = 3, + Failed = 4, + Cancelled = 5, + AwaitingApproval = 6, +} +impl PipelineRunStageStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "PIPELINE_RUN_STAGE_STATUS_UNSPECIFIED", + Self::Pending => "PIPELINE_RUN_STAGE_STATUS_PENDING", + Self::Active => "PIPELINE_RUN_STAGE_STATUS_ACTIVE", + Self::Succeeded => "PIPELINE_RUN_STAGE_STATUS_SUCCEEDED", + Self::Failed => "PIPELINE_RUN_STAGE_STATUS_FAILED", + Self::Cancelled => "PIPELINE_RUN_STAGE_STATUS_CANCELLED", + Self::AwaitingApproval => "PIPELINE_RUN_STAGE_STATUS_AWAITING_APPROVAL", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "PIPELINE_RUN_STAGE_STATUS_UNSPECIFIED" => Some(Self::Unspecified), + "PIPELINE_RUN_STAGE_STATUS_PENDING" => Some(Self::Pending), + "PIPELINE_RUN_STAGE_STATUS_ACTIVE" => Some(Self::Active), + "PIPELINE_RUN_STAGE_STATUS_SUCCEEDED" => Some(Self::Succeeded), + "PIPELINE_RUN_STAGE_STATUS_FAILED" => Some(Self::Failed), + "PIPELINE_RUN_STAGE_STATUS_CANCELLED" => Some(Self::Cancelled), + "PIPELINE_RUN_STAGE_STATUS_AWAITING_APPROVAL" => Some(Self::AwaitingApproval), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct AutoReleasePolicy { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(bool, tag="3")] + pub enabled: bool, + #[prost(string, optional, tag="4")] + pub branch_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub title_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub author_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub commit_message_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub source_type_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, repeated, tag="9")] + pub target_environments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="10")] + pub target_destinations: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, tag="11")] + pub force_release: bool, + #[prost(string, tag="12")] + pub created_at: ::prost::alloc::string::String, + #[prost(string, tag="13")] + pub updated_at: ::prost::alloc::string::String, + /// When true, trigger the project's release pipeline instead of + /// deploying directly to target destinations/environments. + #[prost(bool, tag="14")] + pub use_pipeline: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CreateAutoReleasePolicyRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(string, optional, tag="3")] + pub branch_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub title_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub author_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub commit_message_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub source_type_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, repeated, tag="8")] + pub target_environments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="9")] + pub target_destinations: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, tag="10")] + pub force_release: bool, + #[prost(bool, tag="11")] + pub use_pipeline: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CreateAutoReleasePolicyResponse { + #[prost(message, optional, tag="1")] + pub policy: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UpdateAutoReleasePolicyRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(bool, optional, tag="3")] + pub enabled: ::core::option::Option, + #[prost(string, optional, tag="4")] + pub branch_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="5")] + pub title_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="6")] + pub author_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="7")] + pub commit_message_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="8")] + pub source_type_pattern: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, repeated, tag="9")] + pub target_environments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="10")] + pub target_destinations: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(bool, optional, tag="11")] + pub force_release: ::core::option::Option, + #[prost(bool, optional, tag="12")] + pub use_pipeline: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UpdateAutoReleasePolicyResponse { + #[prost(message, optional, tag="1")] + pub policy: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DeleteAutoReleasePolicyRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DeleteAutoReleasePolicyResponse { +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListAutoReleasePoliciesRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListAutoReleasePoliciesResponse { + #[prost(message, repeated, tag="1")] + pub policies: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreateDestinationRequest { #[prost(string, tag="1")] pub name: ::prost::alloc::string::String, @@ -525,6 +1292,1101 @@ pub struct ListEventSubscriptionsResponse { #[prost(message, repeated, tag="1")] pub subscriptions: ::prost::alloc::vec::Vec, } +/// --------------------------------------------------------------------------- +/// Apply +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyResourcesRequest { + /// Caller-chosen idempotency key (release_state id works well). + #[prost(string, tag="1")] + pub apply_id: ::prost::alloc::string::String, + /// Namespace / tenant isolation — maps to the forest organisation. + #[prost(string, tag="2")] + pub namespace: ::prost::alloc::string::String, + /// The ordered list of resources to reconcile. Forage processes them in + /// order so that dependencies (e.g. Service before HTTPRoute) are met. + #[prost(message, repeated, tag="3")] + pub resources: ::prost::alloc::vec::Vec, + /// Labels propagated to every resource for bookkeeping. + #[prost(map="string, string", tag="4")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ApplyResourcesResponse { + /// Server-generated rollout id for status tracking. + #[prost(string, tag="1")] + pub rollout_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct WatchRolloutRequest { + #[prost(string, tag="1")] + pub rollout_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RolloutEvent { + #[prost(string, tag="1")] + pub resource_name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub resource_kind: ::prost::alloc::string::String, + #[prost(enumeration="RolloutStatus", tag="3")] + pub status: i32, + #[prost(string, tag="4")] + pub message: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteResourcesRequest { + #[prost(string, tag="1")] + pub namespace: ::prost::alloc::string::String, + /// Selector labels — all resources matching these labels are removed. + #[prost(map="string, string", tag="2")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DeleteResourcesResponse { +} +/// =========================================================================== +/// Resource envelope — every item in the apply list is one of these. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ForageResource { + /// Unique name within the namespace (e.g. "my-api", "my-api-worker"). + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(oneof="forage_resource::Spec", tags="10, 11, 12, 13, 14")] + pub spec: ::core::option::Option, +} +/// Nested message and enum types in `ForageResource`. +pub mod forage_resource { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Spec { + #[prost(message, tag="10")] + ContainerService(super::ContainerServiceSpec), + #[prost(message, tag="11")] + Service(super::ServiceSpec), + #[prost(message, tag="12")] + Route(super::RouteSpec), + #[prost(message, tag="13")] + CronJob(super::CronJobSpec), + #[prost(message, tag="14")] + Job(super::JobSpec), + } +} +/// =========================================================================== +/// ContainerServiceSpec — the primary workload. +/// Combines the concerns of Deployment + Pod in a single cohesive spec. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContainerServiceSpec { + /// ---- Scheduling & scaling ------------------------------------------------ + #[prost(message, optional, tag="1")] + pub scaling: ::core::option::Option, + /// ---- Pod-level settings -------------------------------------------------- + /// Main application container (exactly one required). + #[prost(message, optional, tag="2")] + pub container: ::core::option::Option, + /// Optional sidecar containers that share the pod network. + #[prost(message, repeated, tag="3")] + pub sidecars: ::prost::alloc::vec::Vec, + /// Init containers run sequentially before the main container starts. + #[prost(message, repeated, tag="4")] + pub init_containers: ::prost::alloc::vec::Vec, + /// ---- Volumes available to all containers in the pod ---------------------- + #[prost(message, repeated, tag="5")] + pub volumes: ::prost::alloc::vec::Vec, + /// ---- Update strategy ----------------------------------------------------- + #[prost(message, optional, tag="6")] + pub update_strategy: ::core::option::Option, + /// ---- Pod-level configuration --------------------------------------------- + #[prost(message, optional, tag="7")] + pub pod_config: ::core::option::Option, +} +/// --------------------------------------------------------------------------- +/// Container — describes a single OCI container. +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Container { + /// Human-readable name (must be unique within the pod). + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + /// OCI image reference, e.g. "registry.forage.sh/org/app:v1.2.3". + #[prost(string, tag="2")] + pub image: ::prost::alloc::string::String, + /// Override the image entrypoint. + #[prost(string, repeated, tag="3")] + pub command: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Arguments passed to the entrypoint. + #[prost(string, repeated, tag="4")] + pub args: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Working directory inside the container. + #[prost(string, tag="5")] + pub working_dir: ::prost::alloc::string::String, + /// Environment variables — static values and references. + #[prost(message, repeated, tag="6")] + pub env: ::prost::alloc::vec::Vec, + /// Ports the container listens on. + #[prost(message, repeated, tag="7")] + pub ports: ::prost::alloc::vec::Vec, + /// Resource requests and limits. + #[prost(message, optional, tag="8")] + pub resources: ::core::option::Option, + /// Volume mounts into this container's filesystem. + #[prost(message, repeated, tag="9")] + pub volume_mounts: ::prost::alloc::vec::Vec, + /// Health probes. + #[prost(message, optional, tag="10")] + pub liveness_probe: ::core::option::Option, + #[prost(message, optional, tag="11")] + pub readiness_probe: ::core::option::Option, + #[prost(message, optional, tag="12")] + pub startup_probe: ::core::option::Option, + /// Lifecycle hooks. + #[prost(message, optional, tag="13")] + pub lifecycle: ::core::option::Option, + /// Security context for this container. + #[prost(message, optional, tag="14")] + pub security_context: ::core::option::Option, + /// Image pull policy: "Always", "IfNotPresent", "Never". + #[prost(string, tag="15")] + pub image_pull_policy: ::prost::alloc::string::String, + /// Whether stdin / tty are allocated (usually false for services). + #[prost(bool, tag="16")] + pub stdin: bool, + #[prost(bool, tag="17")] + pub tty: bool, +} +/// --------------------------------------------------------------------------- +/// Environment variables +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct EnvVar { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(oneof="env_var::ValueSource", tags="2, 3, 4, 5, 6")] + pub value_source: ::core::option::Option, +} +/// Nested message and enum types in `EnvVar`. +pub mod env_var { + #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] + pub enum ValueSource { + /// Literal value. + #[prost(string, tag="2")] + Value(::prost::alloc::string::String), + /// Reference to a secret key. + #[prost(message, tag="3")] + SecretRef(super::SecretKeyRef), + /// Reference to a config-map key. + #[prost(message, tag="4")] + ConfigRef(super::ConfigKeyRef), + /// Downward-API field (e.g. "metadata.name", "status.podIP"). + #[prost(string, tag="5")] + FieldRef(::prost::alloc::string::String), + /// Resource field (e.g. "limits.cpu"). + #[prost(string, tag="6")] + ResourceFieldRef(::prost::alloc::string::String), + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SecretKeyRef { + #[prost(string, tag="1")] + pub secret_name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub key: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ConfigKeyRef { + #[prost(string, tag="1")] + pub config_name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub key: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Ports +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ContainerPort { + /// Friendly name (e.g. "http", "grpc", "metrics"). + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + /// The port number inside the container. + #[prost(uint32, tag="2")] + pub container_port: u32, + /// Protocol: TCP (default), UDP, SCTP. + #[prost(string, tag="3")] + pub protocol: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Resources +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResourceRequirements { + #[prost(message, optional, tag="1")] + pub requests: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub limits: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResourceList { + /// CPU in Kubernetes quantity format: "100m", "0.5", "2". + #[prost(string, tag="1")] + pub cpu: ::prost::alloc::string::String, + /// Memory in Kubernetes quantity format: "128Mi", "1Gi". + #[prost(string, tag="2")] + pub memory: ::prost::alloc::string::String, + /// Ephemeral storage: "1Gi". + #[prost(string, tag="3")] + pub ephemeral_storage: ::prost::alloc::string::String, + /// GPU / accelerator requests (e.g. "nvidia.com/gpu": "1"). + #[prost(map="string, string", tag="4")] + pub extended: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +/// --------------------------------------------------------------------------- +/// Volumes & mounts +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Volume { + /// Volume name referenced by VolumeMount.name. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(oneof="volume::Source", tags="10, 11, 12, 13, 14, 15")] + pub source: ::core::option::Option, +} +/// Nested message and enum types in `Volume`. +pub mod volume { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Source { + #[prost(message, tag="10")] + EmptyDir(super::EmptyDirVolume), + #[prost(message, tag="11")] + Secret(super::SecretVolume), + #[prost(message, tag="12")] + ConfigMap(super::ConfigMapVolume), + #[prost(message, tag="13")] + Pvc(super::PvcVolume), + #[prost(message, tag="14")] + HostPath(super::HostPathVolume), + #[prost(message, tag="15")] + Nfs(super::NfsVolume), + } +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct EmptyDirVolume { + /// "Memory" for tmpfs, empty for node disk. + #[prost(string, tag="1")] + pub medium: ::prost::alloc::string::String, + /// Size limit (e.g. "256Mi"). Empty means node default. + #[prost(string, tag="2")] + pub size_limit: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecretVolume { + #[prost(string, tag="1")] + pub secret_name: ::prost::alloc::string::String, + /// Optional: mount only specific keys. + #[prost(message, repeated, tag="2")] + pub items: ::prost::alloc::vec::Vec, + /// Octal file mode (e.g. 0644). Default 0644. + #[prost(uint32, tag="3")] + pub default_mode: u32, + #[prost(bool, tag="4")] + pub optional: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConfigMapVolume { + #[prost(string, tag="1")] + pub config_map_name: ::prost::alloc::string::String, + #[prost(message, repeated, tag="2")] + pub items: ::prost::alloc::vec::Vec, + #[prost(uint32, tag="3")] + pub default_mode: u32, + #[prost(bool, tag="4")] + pub optional: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct KeyToPath { + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub path: ::prost::alloc::string::String, + #[prost(uint32, tag="3")] + pub mode: u32, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PvcVolume { + #[prost(string, tag="1")] + pub claim_name: ::prost::alloc::string::String, + #[prost(bool, tag="2")] + pub read_only: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct HostPathVolume { + #[prost(string, tag="1")] + pub path: ::prost::alloc::string::String, + /// "Directory", "File", "DirectoryOrCreate", "FileOrCreate", etc. + #[prost(string, tag="2")] + pub r#type: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct NfsVolume { + #[prost(string, tag="1")] + pub server: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub path: ::prost::alloc::string::String, + #[prost(bool, tag="3")] + pub read_only: bool, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct VolumeMount { + /// Must match a Volume.name. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + /// Absolute path inside the container. + #[prost(string, tag="2")] + pub mount_path: ::prost::alloc::string::String, + /// Optional sub-path within the volume. + #[prost(string, tag="3")] + pub sub_path: ::prost::alloc::string::String, + #[prost(bool, tag="4")] + pub read_only: bool, +} +/// --------------------------------------------------------------------------- +/// Probes +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Probe { + #[prost(uint32, tag="10")] + pub initial_delay_seconds: u32, + #[prost(uint32, tag="11")] + pub period_seconds: u32, + #[prost(uint32, tag="12")] + pub timeout_seconds: u32, + #[prost(uint32, tag="13")] + pub success_threshold: u32, + #[prost(uint32, tag="14")] + pub failure_threshold: u32, + #[prost(oneof="probe::Handler", tags="1, 2, 3, 4")] + pub handler: ::core::option::Option, +} +/// Nested message and enum types in `Probe`. +pub mod probe { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Handler { + #[prost(message, tag="1")] + HttpGet(super::HttpGetProbe), + #[prost(message, tag="2")] + TcpSocket(super::TcpSocketProbe), + #[prost(message, tag="3")] + Exec(super::ExecProbe), + #[prost(message, tag="4")] + Grpc(super::GrpcProbe), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HttpGetProbe { + #[prost(string, tag="1")] + pub path: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub port: u32, + /// "HTTP" or "HTTPS" + #[prost(string, tag="3")] + pub scheme: ::prost::alloc::string::String, + #[prost(message, repeated, tag="4")] + pub http_headers: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct HttpHeader { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub value: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct TcpSocketProbe { + #[prost(uint32, tag="1")] + pub port: u32, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExecProbe { + #[prost(string, repeated, tag="1")] + pub command: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GrpcProbe { + #[prost(uint32, tag="1")] + pub port: u32, + #[prost(string, tag="2")] + pub service: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Lifecycle hooks +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Lifecycle { + #[prost(message, optional, tag="1")] + pub post_start: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub pre_stop: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LifecycleHandler { + #[prost(oneof="lifecycle_handler::Action", tags="1, 2, 3")] + pub action: ::core::option::Option, +} +/// Nested message and enum types in `LifecycleHandler`. +pub mod lifecycle_handler { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Action { + #[prost(message, tag="1")] + Exec(super::ExecProbe), + #[prost(message, tag="2")] + HttpGet(super::HttpGetProbe), + #[prost(message, tag="3")] + TcpSocket(super::TcpSocketProbe), + } +} +/// --------------------------------------------------------------------------- +/// Security +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ContainerSecurityContext { + #[prost(bool, tag="1")] + pub run_as_non_root: bool, + #[prost(int64, tag="2")] + pub run_as_user: i64, + #[prost(int64, tag="3")] + pub run_as_group: i64, + #[prost(bool, tag="4")] + pub read_only_root_filesystem: bool, + #[prost(bool, tag="5")] + pub privileged: bool, + #[prost(bool, tag="6")] + pub allow_privilege_escalation: bool, + #[prost(message, optional, tag="7")] + pub capabilities: ::core::option::Option, + /// SELinux options (optional). + #[prost(string, tag="8")] + pub se_linux_type: ::prost::alloc::string::String, + /// Seccomp profile: "RuntimeDefault", "Unconfined", or a localhost path. + #[prost(string, tag="9")] + pub seccomp_profile: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Capabilities { + #[prost(string, repeated, tag="1")] + pub add: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="2")] + pub drop: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PodSecurityContext { + #[prost(int64, tag="1")] + pub run_as_user: i64, + #[prost(int64, tag="2")] + pub run_as_group: i64, + #[prost(bool, tag="3")] + pub run_as_non_root: bool, + #[prost(int64, tag="4")] + pub fs_group: i64, + /// Supplemental groups for all containers. + #[prost(int64, repeated, tag="5")] + pub supplemental_groups: ::prost::alloc::vec::Vec, + /// "OnRootMismatch" or "Always". + #[prost(string, tag="6")] + pub fs_group_change_policy: ::prost::alloc::string::String, + #[prost(string, tag="7")] + pub seccomp_profile: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Scaling +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ScalingPolicy { + /// Fixed replica count (used when autoscaling is not configured). + #[prost(uint32, tag="1")] + pub replicas: u32, + /// Optional horizontal autoscaler. + #[prost(message, optional, tag="2")] + pub autoscaling: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AutoscalingPolicy { + #[prost(uint32, tag="1")] + pub min_replicas: u32, + #[prost(uint32, tag="2")] + pub max_replicas: u32, + /// Target average CPU utilisation percentage (e.g. 70). + #[prost(uint32, tag="3")] + pub target_cpu_utilization_percent: u32, + /// Target average memory utilisation percentage. + #[prost(uint32, tag="4")] + pub target_memory_utilization_percent: u32, + /// Custom metrics (e.g. queue depth, RPS). + #[prost(message, repeated, tag="5")] + pub custom_metrics: ::prost::alloc::vec::Vec, + /// Scale-down stabilisation window. + #[prost(message, optional, tag="6")] + pub scale_down_stabilization: ::core::option::Option<::prost_types::Duration>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CustomMetric { + /// Metric name as exposed by the metrics adapter. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + /// One of "Value", "AverageValue", "Utilization". + #[prost(string, tag="2")] + pub target_type: ::prost::alloc::string::String, + /// Target threshold (interpretation depends on target_type). + #[prost(string, tag="3")] + pub target_value: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Update strategy +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UpdateStrategy { + /// "RollingUpdate" (default) or "Recreate". + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + #[prost(message, optional, tag="2")] + pub rolling_update: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RollingUpdateConfig { + /// Absolute number or percentage (e.g. "1", "25%"). + #[prost(string, tag="1")] + pub max_unavailable: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub max_surge: ::prost::alloc::string::String, +} +/// --------------------------------------------------------------------------- +/// Pod-level configuration +/// --------------------------------------------------------------------------- +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PodConfig { + /// Service account name for RBAC / workload identity. + #[prost(string, tag="1")] + pub service_account_name: ::prost::alloc::string::String, + /// Restart policy: "Always" (default for services), "OnFailure", "Never". + #[prost(string, tag="2")] + pub restart_policy: ::prost::alloc::string::String, + /// Graceful shutdown window. + #[prost(uint32, tag="3")] + pub termination_grace_period_seconds: u32, + /// DNS policy: "ClusterFirst" (default), "Default", "None". + #[prost(string, tag="4")] + pub dns_policy: ::prost::alloc::string::String, + #[prost(message, optional, tag="5")] + pub dns_config: ::core::option::Option, + /// Host networking (rare, but needed for some infra workloads). + #[prost(bool, tag="6")] + pub host_network: bool, + /// Node scheduling. + #[prost(map="string, string", tag="7")] + pub node_selector: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, repeated, tag="8")] + pub tolerations: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="9")] + pub affinity: ::core::option::Option, + /// Topology spread constraints for HA. + #[prost(message, repeated, tag="10")] + pub topology_spread_constraints: ::prost::alloc::vec::Vec, + /// Image pull secrets. + #[prost(string, repeated, tag="11")] + pub image_pull_secrets: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Pod-level security context. + #[prost(message, optional, tag="12")] + pub security_context: ::core::option::Option, + /// Priority class name for preemption. + #[prost(string, tag="13")] + pub priority_class_name: ::prost::alloc::string::String, + /// Runtime class (e.g. "gvisor", "kata"). + #[prost(string, tag="14")] + pub runtime_class_name: ::prost::alloc::string::String, + /// Annotations passed to the pod template (not the workload resource). + #[prost(map="string, string", tag="15")] + pub annotations: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// Labels passed to the pod template. + #[prost(map="string, string", tag="16")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PodDnsConfig { + #[prost(string, repeated, tag="1")] + pub nameservers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, repeated, tag="2")] + pub searches: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, repeated, tag="3")] + pub options: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DnsOption { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub value: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct Toleration { + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + /// "Equal" or "Exists". + #[prost(string, tag="2")] + pub operator: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub value: ::prost::alloc::string::String, + /// "NoSchedule", "PreferNoSchedule", "NoExecute". + #[prost(string, tag="4")] + pub effect: ::prost::alloc::string::String, + /// Toleration seconds for NoExecute. + #[prost(int64, tag="5")] + pub toleration_seconds: i64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Affinity { + #[prost(message, optional, tag="1")] + pub node_affinity: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub pod_affinity: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub pod_anti_affinity: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NodeAffinity { + #[prost(message, repeated, tag="1")] + pub preferred: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="2")] + pub required: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PreferredSchedulingTerm { + #[prost(int32, tag="1")] + pub weight: i32, + #[prost(message, optional, tag="2")] + pub preference: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NodeSelector { + #[prost(message, repeated, tag="1")] + pub terms: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NodeSelectorTerm { + #[prost(message, repeated, tag="1")] + pub match_expressions: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub match_fields: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct NodeSelectorRequirement { + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + /// "In", "NotIn", "Exists", "DoesNotExist", "Gt", "Lt". + #[prost(string, tag="2")] + pub operator: ::prost::alloc::string::String, + #[prost(string, repeated, tag="3")] + pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PodAffinity { + #[prost(message, repeated, tag="1")] + pub preferred: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub required: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PodAntiAffinity { + #[prost(message, repeated, tag="1")] + pub preferred: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub required: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WeightedPodAffinityTerm { + #[prost(int32, tag="1")] + pub weight: i32, + #[prost(message, optional, tag="2")] + pub term: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PodAffinityTerm { + #[prost(message, optional, tag="1")] + pub label_selector: ::core::option::Option, + #[prost(string, tag="2")] + pub topology_key: ::prost::alloc::string::String, + #[prost(string, repeated, tag="3")] + pub namespaces: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LabelSelector { + #[prost(map="string, string", tag="1")] + pub match_labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, repeated, tag="2")] + pub match_expressions: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct LabelSelectorRequirement { + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + /// "In", "NotIn", "Exists", "DoesNotExist". + #[prost(string, tag="2")] + pub operator: ::prost::alloc::string::String, + #[prost(string, repeated, tag="3")] + pub values: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TopologySpreadConstraint { + /// Max difference in spread (e.g. 1 for even distribution). + #[prost(int32, tag="1")] + pub max_skew: i32, + /// "zone", "hostname", or any node label. + #[prost(string, tag="2")] + pub topology_key: ::prost::alloc::string::String, + /// "DoNotSchedule" or "ScheduleAnyway". + #[prost(string, tag="3")] + pub when_unsatisfiable: ::prost::alloc::string::String, + #[prost(message, optional, tag="4")] + pub label_selector: ::core::option::Option, +} +/// =========================================================================== +/// ServiceSpec — L4 load balancing & service discovery. +/// Combines Service + optional gateway route into one resource when desired. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ServiceSpec { + /// The ContainerServiceSpec name this service fronts. + #[prost(string, tag="1")] + pub target: ::prost::alloc::string::String, + /// Service type: "ClusterIP" (default), "NodePort", "LoadBalancer", "Headless". + #[prost(string, tag="2")] + pub r#type: ::prost::alloc::string::String, + #[prost(message, repeated, tag="3")] + pub ports: ::prost::alloc::vec::Vec, + /// Session affinity: "None" (default), "ClientIP". + #[prost(string, tag="4")] + pub session_affinity: ::prost::alloc::string::String, + /// Optional: expose this service externally via the gateway. + /// Setting this is equivalent to creating a separate RouteSpec. + /// Allows combining Service + Route into one resource for simpler configs. + #[prost(message, optional, tag="5")] + pub inline_route: ::core::option::Option, + /// Extra annotations on the Service object (e.g. cloud LB configs). + #[prost(map="string, string", tag="6")] + pub annotations: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ServicePort { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub port: u32, + #[prost(uint32, tag="3")] + pub target_port: u32, + /// TCP, UDP, SCTP + #[prost(string, tag="4")] + pub protocol: ::prost::alloc::string::String, + /// Only for NodePort type. + #[prost(uint32, tag="5")] + pub node_port: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InlineRoute { + /// Hostname(s) to match (e.g. "api.example.com"). + #[prost(string, repeated, tag="1")] + pub hostnames: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Path matching rules. If empty, matches all paths to the first port. + #[prost(message, repeated, tag="2")] + pub rules: ::prost::alloc::vec::Vec, + /// TLS configuration. + #[prost(message, optional, tag="3")] + pub tls: ::core::option::Option, +} +/// =========================================================================== +/// RouteSpec — Gateway API HTTPRoute (standalone). +/// Use this when you need routing rules separate from the service definition. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RouteSpec { + /// The ServiceSpec name this route targets. + #[prost(string, tag="1")] + pub target_service: ::prost::alloc::string::String, + /// Hostname(s) this route matches. + #[prost(string, repeated, tag="2")] + pub hostnames: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Matching & routing rules. + #[prost(message, repeated, tag="3")] + pub rules: ::prost::alloc::vec::Vec, + /// TLS termination config. + #[prost(message, optional, tag="4")] + pub tls: ::core::option::Option, + /// Which gateway to attach to (empty = cluster default). + #[prost(string, tag="5")] + pub gateway_ref: ::prost::alloc::string::String, + /// Route priority / ordering. + #[prost(int32, tag="6")] + pub priority: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RouteRule { + /// Path matching. + #[prost(message, repeated, tag="1")] + pub matches: ::prost::alloc::vec::Vec, + /// Backend(s) traffic is sent to. + #[prost(message, repeated, tag="2")] + pub backends: ::prost::alloc::vec::Vec, + /// Request / response filters applied to this rule. + #[prost(message, repeated, tag="3")] + pub filters: ::prost::alloc::vec::Vec, + /// Timeout for the entire request. + #[prost(message, optional, tag="4")] + pub timeout: ::core::option::Option<::prost_types::Duration>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RouteMatch { + /// Path match. + #[prost(message, optional, tag="1")] + pub path: ::core::option::Option, + /// Header conditions. + #[prost(message, repeated, tag="2")] + pub headers: ::prost::alloc::vec::Vec, + /// Query parameter conditions. + #[prost(message, repeated, tag="3")] + pub query_params: ::prost::alloc::vec::Vec, + /// HTTP method constraint. + #[prost(string, tag="4")] + pub method: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PathMatch { + /// "Exact", "PathPrefix" (default), "RegularExpression". + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub value: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct HeaderMatch { + /// "Exact" (default), "RegularExpression". + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub value: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct QueryParamMatch { + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub value: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RouteBackend { + /// Service name. + #[prost(string, tag="1")] + pub service: ::prost::alloc::string::String, + /// Port on the backend service. + #[prost(uint32, tag="2")] + pub port: u32, + /// Traffic weight for canary / blue-green (1-100). + #[prost(uint32, tag="3")] + pub weight: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RouteFilter { + #[prost(oneof="route_filter::Filter", tags="1, 2, 3, 4, 5")] + pub filter: ::core::option::Option, +} +/// Nested message and enum types in `RouteFilter`. +pub mod route_filter { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Filter { + #[prost(message, tag="1")] + RequestHeaderModifier(super::RequestHeaderModifier), + #[prost(message, tag="2")] + ResponseHeaderModifier(super::ResponseHeaderModifier), + #[prost(message, tag="3")] + RequestRedirect(super::RequestRedirect), + #[prost(message, tag="4")] + UrlRewrite(super::UrlRewrite), + #[prost(message, tag="5")] + RequestMirror(super::RequestMirror), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestHeaderModifier { + #[prost(map="string, string", tag="1")] + pub set: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(map="string, string", tag="2")] + pub add: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(string, repeated, tag="3")] + pub remove: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResponseHeaderModifier { + #[prost(map="string, string", tag="1")] + pub set: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(map="string, string", tag="2")] + pub add: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(string, repeated, tag="3")] + pub remove: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RequestRedirect { + #[prost(string, tag="1")] + pub scheme: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub hostname: ::prost::alloc::string::String, + #[prost(uint32, tag="3")] + pub port: u32, + #[prost(string, tag="4")] + pub path: ::prost::alloc::string::String, + /// 301, 302, etc. + #[prost(uint32, tag="5")] + pub status_code: u32, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UrlRewrite { + #[prost(string, tag="1")] + pub hostname: ::prost::alloc::string::String, + #[prost(message, optional, tag="2")] + pub path: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RequestMirror { + #[prost(string, tag="1")] + pub service: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub port: u32, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RouteTls { + /// "Terminate" (default) or "Passthrough". + #[prost(string, tag="1")] + pub mode: ::prost::alloc::string::String, + /// Secret name containing the TLS certificate. + #[prost(string, tag="2")] + pub certificate_ref: ::prost::alloc::string::String, +} +/// =========================================================================== +/// CronJobSpec — scheduled workload. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CronJobSpec { + /// Cron schedule (e.g. "*/5 * * * *"). + #[prost(string, tag="1")] + pub schedule: ::prost::alloc::string::String, + /// Timezone (e.g. "Europe/Copenhagen"). Empty = UTC. + #[prost(string, tag="2")] + pub timezone: ::prost::alloc::string::String, + /// Container that runs the job. + #[prost(message, optional, tag="3")] + pub container: ::core::option::Option, + /// Volumes for the job pod. + #[prost(message, repeated, tag="4")] + pub volumes: ::prost::alloc::vec::Vec, + /// Job-level config. + #[prost(message, optional, tag="5")] + pub job_config: ::core::option::Option, + /// Pod-level config (node selector, tolerations, etc.). + #[prost(message, optional, tag="6")] + pub pod_config: ::core::option::Option, + /// "Allow", "Forbid", "Replace". + #[prost(string, tag="7")] + pub concurrency_policy: ::prost::alloc::string::String, + /// Number of successful/failed jobs to retain. + #[prost(uint32, tag="8")] + pub successful_jobs_history_limit: u32, + #[prost(uint32, tag="9")] + pub failed_jobs_history_limit: u32, + /// Suspend the cron schedule. + #[prost(bool, tag="10")] + pub suspend: bool, + /// Deadline in seconds for starting the job if it missed its schedule. + #[prost(int64, tag="11")] + pub starting_deadline_seconds: i64, +} +/// =========================================================================== +/// JobSpec — one-shot workload. +/// =========================================================================== +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct JobSpec { + /// Container that runs the job. + #[prost(message, optional, tag="1")] + pub container: ::core::option::Option, + /// Volumes for the job pod. + #[prost(message, repeated, tag="2")] + pub volumes: ::prost::alloc::vec::Vec, + /// Job-level config. + #[prost(message, optional, tag="3")] + pub job_config: ::core::option::Option, + /// Pod-level config. + #[prost(message, optional, tag="4")] + pub pod_config: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct JobConfig { + /// Number of times the job should complete successfully. + #[prost(uint32, tag="1")] + pub completions: u32, + /// Max parallel pods. + #[prost(uint32, tag="2")] + pub parallelism: u32, + /// "NonIndexed" (default) or "Indexed". + #[prost(string, tag="3")] + pub completion_mode: ::prost::alloc::string::String, + /// Number of retries before marking failed. + #[prost(uint32, tag="4")] + pub backoff_limit: u32, + /// Active deadline (seconds) — job killed if it runs longer. + #[prost(int64, tag="5")] + pub active_deadline_seconds: i64, + /// TTL after finished (seconds) — auto-cleanup. + #[prost(int64, tag="6")] + pub ttl_seconds_after_finished: i64, + /// Restart policy: "OnFailure" (default) or "Never". + #[prost(string, tag="7")] + pub restart_policy: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum RolloutStatus { + Unspecified = 0, + Pending = 1, + InProgress = 2, + Succeeded = 3, + Failed = 4, + RolledBack = 5, +} +impl RolloutStatus { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "ROLLOUT_STATUS_UNSPECIFIED", + Self::Pending => "ROLLOUT_STATUS_PENDING", + Self::InProgress => "ROLLOUT_STATUS_IN_PROGRESS", + Self::Succeeded => "ROLLOUT_STATUS_SUCCEEDED", + Self::Failed => "ROLLOUT_STATUS_FAILED", + Self::RolledBack => "ROLLOUT_STATUS_ROLLED_BACK", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ROLLOUT_STATUS_UNSPECIFIED" => Some(Self::Unspecified), + "ROLLOUT_STATUS_PENDING" => Some(Self::Pending), + "ROLLOUT_STATUS_IN_PROGRESS" => Some(Self::InProgress), + "ROLLOUT_STATUS_SUCCEEDED" => Some(Self::Succeeded), + "ROLLOUT_STATUS_FAILED" => Some(Self::Failed), + "ROLLOUT_STATUS_ROLLED_BACK" => Some(Self::RolledBack), + _ => None, + } + } +} #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetStatusRequest { } @@ -554,6 +2416,8 @@ pub struct ReleaseContext { pub source_username: ::prost::alloc::string::String, #[prost(string, tag="9")] pub source_email: ::prost::alloc::string::String, + #[prost(string, tag="17")] + pub source_user_id: ::prost::alloc::string::String, /// Git ref #[prost(string, tag="10")] pub commit_sha: ::prost::alloc::string::String, @@ -572,8 +2436,6 @@ pub struct ReleaseContext { /// Number of destinations involved #[prost(int32, tag="16")] pub destination_count: i32, - #[prost(string, tag="17")] - pub source_user_id: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Notification { @@ -854,590 +2716,6 @@ pub struct ListMembersResponse { #[prost(int32, tag="3")] pub total_count: i32, } -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AnnotateReleaseRequest { - #[prost(string, tag="1")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(map="string, string", tag="2")] - pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, - #[prost(message, optional, tag="3")] - pub source: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub context: ::core::option::Option, - #[prost(message, optional, tag="5")] - pub project: ::core::option::Option, - #[prost(message, optional, tag="6")] - pub r#ref: ::core::option::Option, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AnnotateReleaseResponse { - #[prost(message, optional, tag="1")] - pub artifact: ::core::option::Option, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetArtifactBySlugRequest { - #[prost(string, tag="1")] - pub slug: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetArtifactBySlugResponse { - #[prost(message, optional, tag="1")] - pub artifact: ::core::option::Option, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetArtifactsByProjectRequest { - #[prost(message, optional, tag="1")] - pub project: ::core::option::Option, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetArtifactsByProjectResponse { - #[prost(message, repeated, tag="1")] - pub artifact: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseRequest { - #[prost(string, tag="1")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(string, repeated, tag="2")] - pub destinations: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, repeated, tag="3")] - pub environments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(bool, tag="4")] - pub force: bool, - /// When true, use the project's release pipeline (DAG) instead of - /// deploying directly to the specified destinations/environments. - #[prost(bool, tag="5")] - pub use_pipeline: bool, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ReleaseResponse { - /// List of release intents created (one per destination) - #[prost(message, repeated, tag="1")] - pub intents: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseIntent { - #[prost(string, tag="1")] - pub release_intent_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub destination: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub environment: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct WaitReleaseRequest { - #[prost(string, tag="1")] - pub release_intent_id: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct WaitReleaseEvent { - #[prost(oneof="wait_release_event::Event", tags="1, 2, 3")] - pub event: ::core::option::Option, -} -/// Nested message and enum types in `WaitReleaseEvent`. -pub mod wait_release_event { - #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] - pub enum Event { - #[prost(message, tag="1")] - StatusUpdate(super::ReleaseStatusUpdate), - #[prost(message, tag="2")] - LogLine(super::ReleaseLogLine), - #[prost(message, tag="3")] - StageUpdate(super::PipelineStageUpdate), - } -} -/// Streamed in WaitRelease for pipeline releases: reports stage status changes. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct PipelineStageUpdate { - #[prost(string, tag="1")] - pub stage_id: ::prost::alloc::string::String, - /// "deploy", "wait" - #[prost(string, tag="2")] - pub stage_type: ::prost::alloc::string::String, - /// PENDING, ACTIVE, SUCCEEDED, FAILED, CANCELLED - #[prost(string, tag="3")] - pub status: ::prost::alloc::string::String, - #[prost(string, optional, tag="4")] - pub queued_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="5")] - pub started_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="6")] - pub completed_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="7")] - pub wait_until: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] - pub error_message: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseStatusUpdate { - #[prost(string, tag="1")] - pub destination: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub status: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseLogLine { - #[prost(string, tag="1")] - pub destination: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub line: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub timestamp: ::prost::alloc::string::String, - #[prost(enumeration="LogChannel", tag="4")] - pub channel: i32, -} -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetOrganisationsRequest { -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetOrganisationsResponse { - #[prost(message, repeated, tag="1")] - pub organisations: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetProjectsRequest { - #[prost(oneof="get_projects_request::Query", tags="1")] - pub query: ::core::option::Option, -} -/// Nested message and enum types in `GetProjectsRequest`. -pub mod get_projects_request { - #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] - pub enum Query { - #[prost(message, tag="1")] - Organisation(super::OrganisationRef), - } -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetProjectsResponse { - #[prost(string, repeated, tag="1")] - pub projects: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct CreateProjectRequest { - #[prost(string, tag="1")] - pub organisation: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub project: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct CreateProjectResponse { - #[prost(message, optional, tag="1")] - pub project: ::core::option::Option, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetReleasesByActorRequest { - /// user_id or app_id - #[prost(string, tag="1")] - pub actor_id: ::prost::alloc::string::String, - /// "user" or "app" - #[prost(string, tag="2")] - pub actor_type: ::prost::alloc::string::String, - #[prost(int32, tag="3")] - pub page_size: i32, - #[prost(string, tag="4")] - pub page_token: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetReleasesByActorResponse { - #[prost(message, repeated, tag="1")] - pub releases: ::prost::alloc::vec::Vec, - #[prost(string, tag="2")] - pub next_page_token: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ReleaseIntentSummary { - #[prost(string, tag="1")] - pub release_intent_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(message, optional, tag="3")] - pub project: ::core::option::Option, - #[prost(message, repeated, tag="4")] - pub destinations: ::prost::alloc::vec::Vec, - #[prost(string, tag="5")] - pub created_at: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseDestinationStatus { - #[prost(string, tag="1")] - pub destination: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub environment: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub status: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetDestinationStatesRequest { - #[prost(string, tag="1")] - pub organisation: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] - pub project: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetDestinationStatesResponse { - #[prost(message, repeated, tag="1")] - pub destinations: ::prost::alloc::vec::Vec, - /// Active pipeline runs affecting these destinations (if any). - #[prost(message, repeated, tag="2")] - pub pipeline_runs: ::prost::alloc::vec::Vec, -} -// ── Release intent states (release-centric view) ───────────────────── - -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct GetReleaseIntentStatesRequest { - #[prost(string, tag="1")] - pub organisation: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] - pub project: ::core::option::Option<::prost::alloc::string::String>, - /// When true, also include recently completed release intents. - #[prost(bool, tag="3")] - pub include_completed: bool, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetReleaseIntentStatesResponse { - #[prost(message, repeated, tag="1")] - pub release_intents: ::prost::alloc::vec::Vec, -} -/// Full state of a release intent: pipeline stages + individual release steps. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ReleaseIntentState { - #[prost(string, tag="1")] - pub release_intent_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub project: ::prost::alloc::string::String, - #[prost(string, tag="4")] - pub created_at: ::prost::alloc::string::String, - /// Pipeline stages (empty for non-pipeline releases). - #[prost(message, repeated, tag="5")] - pub stages: ::prost::alloc::vec::Vec, - /// All release_states rows for this intent (deploy steps). - #[prost(message, repeated, tag="6")] - pub steps: ::prost::alloc::vec::Vec, -} -/// Status of a single pipeline stage (saga coordinator view). -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct PipelineStageState { - #[prost(string, tag="1")] - pub stage_id: ::prost::alloc::string::String, - #[prost(string, repeated, tag="2")] - pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(enumeration="PipelineRunStageType", tag="3")] - pub stage_type: i32, - #[prost(enumeration="PipelineRunStageStatus", tag="4")] - pub status: i32, - /// Consistent timestamps for all stage types. - #[prost(string, optional, tag="5")] - pub queued_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="6")] - pub started_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="7")] - pub completed_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] - pub error_message: ::core::option::Option<::prost::alloc::string::String>, - /// Type-specific context. - /// - /// deploy stages - #[prost(string, optional, tag="9")] - pub environment: ::core::option::Option<::prost::alloc::string::String>, - /// wait stages - #[prost(int64, optional, tag="10")] - pub duration_seconds: ::core::option::Option, - /// wait stages - #[prost(string, optional, tag="11")] - pub wait_until: ::core::option::Option<::prost::alloc::string::String>, - /// deploy stages: individual release IDs - #[prost(string, repeated, tag="12")] - pub release_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -/// Status of a single release step (release_states row). -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ReleaseStepState { - #[prost(string, tag="1")] - pub release_id: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] - pub stage_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="3")] - pub destination_name: ::prost::alloc::string::String, - #[prost(string, tag="4")] - pub environment: ::prost::alloc::string::String, - #[prost(string, tag="5")] - pub status: ::prost::alloc::string::String, - #[prost(string, optional, tag="6")] - pub queued_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="7")] - pub assigned_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] - pub started_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="9")] - pub completed_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="10")] - pub error_message: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct DestinationState { - #[prost(string, tag="1")] - pub destination_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub destination_name: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub environment: ::prost::alloc::string::String, - #[prost(string, optional, tag="4")] - pub release_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="5")] - pub artifact_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="6")] - pub status: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="7")] - pub error_message: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] - pub queued_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="9")] - pub completed_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(int32, optional, tag="10")] - pub queue_position: ::core::option::Option, - /// Pipeline context: set when this release was created by a pipeline stage. - #[prost(string, optional, tag="11")] - pub release_intent_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="12")] - pub stage_id: ::core::option::Option<::prost::alloc::string::String>, - /// When a runner was assigned to this release. - #[prost(string, optional, tag="13")] - pub assigned_at: ::core::option::Option<::prost::alloc::string::String>, - /// When the runner actually started executing. - #[prost(string, optional, tag="14")] - pub started_at: ::core::option::Option<::prost::alloc::string::String>, -} -// ── Pipeline run progress ──────────────────────────────────────────── - -/// Snapshot of an active (or recently completed) pipeline run. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PipelineRunState { - #[prost(string, tag="1")] - pub release_intent_id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub created_at: ::prost::alloc::string::String, - #[prost(message, repeated, tag="4")] - pub stages: ::prost::alloc::vec::Vec, -} -/// Status of a single stage within a pipeline run. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct PipelineRunStage { - #[prost(string, tag="1")] - pub stage_id: ::prost::alloc::string::String, - #[prost(string, repeated, tag="2")] - pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(enumeration="PipelineRunStageType", tag="3")] - pub stage_type: i32, - #[prost(enumeration="PipelineRunStageStatus", tag="4")] - pub status: i32, - /// Type-specific context - /// - /// deploy stages - #[prost(string, optional, tag="5")] - pub environment: ::core::option::Option<::prost::alloc::string::String>, - /// wait stages - #[prost(int64, optional, tag="6")] - pub duration_seconds: ::core::option::Option, - /// when dependencies were met - #[prost(string, optional, tag="7")] - pub queued_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] - pub started_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="9")] - pub completed_at: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="10")] - pub error_message: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="11")] - pub wait_until: ::core::option::Option<::prost::alloc::string::String>, - /// deploy stages: individual release IDs - #[prost(string, repeated, tag="12")] - pub release_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct Source { - #[prost(string, optional, tag="1")] - pub user: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="2")] - pub email: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="3")] - pub source_type: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] - pub run_url: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ArtifactContext { - #[prost(string, tag="1")] - pub title: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] - pub description: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="3")] - pub web: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] - pub pr: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Artifact { - #[prost(string, tag="1")] - pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub artifact_id: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub slug: ::prost::alloc::string::String, - #[prost(map="string, string", tag="4")] - pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, - #[prost(message, optional, tag="5")] - pub source: ::core::option::Option, - #[prost(message, optional, tag="6")] - pub context: ::core::option::Option, - #[prost(message, optional, tag="7")] - pub project: ::core::option::Option, - #[prost(message, repeated, tag="8")] - pub destinations: ::prost::alloc::vec::Vec, - #[prost(string, tag="9")] - pub created_at: ::prost::alloc::string::String, - #[prost(message, optional, tag="10")] - pub r#ref: ::core::option::Option, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ArtifactDestination { - #[prost(string, tag="1")] - pub name: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub environment: ::prost::alloc::string::String, - #[prost(string, tag="3")] - pub type_organisation: ::prost::alloc::string::String, - #[prost(string, tag="4")] - pub type_name: ::prost::alloc::string::String, - #[prost(uint64, tag="5")] - pub type_version: u64, - #[prost(string, tag="6")] - pub status: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct Project { - #[prost(string, tag="1")] - pub organisation: ::prost::alloc::string::String, - #[prost(string, tag="2")] - pub project: ::prost::alloc::string::String, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct Ref { - #[prost(string, tag="1")] - pub commit_sha: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] - pub branch: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="3")] - pub commit_message: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] - pub version: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="5")] - pub repo_url: ::core::option::Option<::prost::alloc::string::String>, -} -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct OrganisationRef { - #[prost(string, tag="1")] - pub organisation: ::prost::alloc::string::String, -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum LogChannel { - Unspecified = 0, - Stdout = 1, - Stderr = 2, -} -impl LogChannel { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "LOG_CHANNEL_UNSPECIFIED", - Self::Stdout => "LOG_CHANNEL_STDOUT", - Self::Stderr => "LOG_CHANNEL_STDERR", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "LOG_CHANNEL_UNSPECIFIED" => Some(Self::Unspecified), - "LOG_CHANNEL_STDOUT" => Some(Self::Stdout), - "LOG_CHANNEL_STDERR" => Some(Self::Stderr), - _ => None, - } - } -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum PipelineRunStageType { - Unspecified = 0, - Deploy = 1, - Wait = 2, -} -impl PipelineRunStageType { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "PIPELINE_RUN_STAGE_TYPE_UNSPECIFIED", - Self::Deploy => "PIPELINE_RUN_STAGE_TYPE_DEPLOY", - Self::Wait => "PIPELINE_RUN_STAGE_TYPE_WAIT", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "PIPELINE_RUN_STAGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified), - "PIPELINE_RUN_STAGE_TYPE_DEPLOY" => Some(Self::Deploy), - "PIPELINE_RUN_STAGE_TYPE_WAIT" => Some(Self::Wait), - _ => None, - } - } -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum PipelineRunStageStatus { - Unspecified = 0, - Pending = 1, - Active = 2, - Succeeded = 3, - Failed = 4, - Cancelled = 5, -} -impl PipelineRunStageStatus { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - Self::Unspecified => "PIPELINE_RUN_STAGE_STATUS_UNSPECIFIED", - Self::Pending => "PIPELINE_RUN_STAGE_STATUS_PENDING", - Self::Active => "PIPELINE_RUN_STAGE_STATUS_ACTIVE", - Self::Succeeded => "PIPELINE_RUN_STAGE_STATUS_SUCCEEDED", - Self::Failed => "PIPELINE_RUN_STAGE_STATUS_FAILED", - Self::Cancelled => "PIPELINE_RUN_STAGE_STATUS_CANCELLED", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "PIPELINE_RUN_STAGE_STATUS_UNSPECIFIED" => Some(Self::Unspecified), - "PIPELINE_RUN_STAGE_STATUS_PENDING" => Some(Self::Pending), - "PIPELINE_RUN_STAGE_STATUS_ACTIVE" => Some(Self::Active), - "PIPELINE_RUN_STAGE_STATUS_SUCCEEDED" => Some(Self::Succeeded), - "PIPELINE_RUN_STAGE_STATUS_FAILED" => Some(Self::Failed), - "PIPELINE_RUN_STAGE_STATUS_CANCELLED" => Some(Self::Cancelled), - _ => None, - } - } -} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SoakTimeConfig { /// Environment that must have a successful deploy before target is allowed @@ -1459,6 +2737,37 @@ pub struct BranchRestrictionConfig { #[prost(string, tag="2")] pub branch_pattern: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExternalApprovalConfig { + #[prost(string, tag="1")] + pub target_environment: ::prost::alloc::string::String, + #[prost(int32, tag="2")] + pub required_approvals: i32, +} +// ── External approval state ───────────────────────────────────────── + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExternalApprovalState { + #[prost(int32, tag="1")] + pub required_approvals: i32, + #[prost(int32, tag="2")] + pub current_approvals: i32, + #[prost(message, repeated, tag="3")] + pub decisions: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExternalApprovalDecisionEntry { + #[prost(string, tag="1")] + pub user_id: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub decision: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub decided_at: ::prost::alloc::string::String, + #[prost(string, optional, tag="5")] + pub comment: ::core::option::Option<::prost::alloc::string::String>, +} // ── Policy resource ───────────────────────────────────────────────── #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -1475,7 +2784,7 @@ pub struct Policy { pub created_at: ::prost::alloc::string::String, #[prost(string, tag="21")] pub updated_at: ::prost::alloc::string::String, - #[prost(oneof="policy::Config", tags="10, 11")] + #[prost(oneof="policy::Config", tags="10, 11, 12")] pub config: ::core::option::Option, } /// Nested message and enum types in `Policy`. @@ -1486,11 +2795,13 @@ pub mod policy { SoakTime(super::SoakTimeConfig), #[prost(message, tag="11")] BranchRestriction(super::BranchRestrictionConfig), + #[prost(message, tag="12")] + ExternalApproval(super::ExternalApprovalConfig), } } // ── Policy evaluation result ──────────────────────────────────────── -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PolicyEvaluation { #[prost(string, tag="1")] pub policy_name: ::prost::alloc::string::String, @@ -1501,6 +2812,8 @@ pub struct PolicyEvaluation { /// Human-readable explanation when blocked #[prost(string, tag="4")] pub reason: ::prost::alloc::string::String, + #[prost(message, optional, tag="10")] + pub approval_state: ::core::option::Option, } // ── CRUD messages ─────────────────────────────────────────────────── @@ -1512,7 +2825,7 @@ pub struct CreatePolicyRequest { pub name: ::prost::alloc::string::String, #[prost(enumeration="PolicyType", tag="3")] pub policy_type: i32, - #[prost(oneof="create_policy_request::Config", tags="10, 11")] + #[prost(oneof="create_policy_request::Config", tags="10, 11, 12")] pub config: ::core::option::Option, } /// Nested message and enum types in `CreatePolicyRequest`. @@ -1523,6 +2836,8 @@ pub mod create_policy_request { SoakTime(super::SoakTimeConfig), #[prost(message, tag="11")] BranchRestriction(super::BranchRestrictionConfig), + #[prost(message, tag="12")] + ExternalApproval(super::ExternalApprovalConfig), } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -1538,7 +2853,7 @@ pub struct UpdatePolicyRequest { pub name: ::prost::alloc::string::String, #[prost(bool, optional, tag="3")] pub enabled: ::core::option::Option, - #[prost(oneof="update_policy_request::Config", tags="10, 11")] + #[prost(oneof="update_policy_request::Config", tags="10, 11, 12")] pub config: ::core::option::Option, } /// Nested message and enum types in `UpdatePolicyRequest`. @@ -1549,6 +2864,8 @@ pub mod update_policy_request { SoakTime(super::SoakTimeConfig), #[prost(message, tag="11")] BranchRestriction(super::BranchRestrictionConfig), + #[prost(message, tag="12")] + ExternalApproval(super::ExternalApprovalConfig), } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -1585,6 +2902,8 @@ pub struct EvaluatePoliciesRequest { /// For branch restriction checks #[prost(string, optional, tag="3")] pub branch: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag="4")] + pub release_intent_id: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct EvaluatePoliciesResponse { @@ -1593,6 +2912,56 @@ pub struct EvaluatePoliciesResponse { #[prost(bool, tag="2")] pub all_passed: bool, } +// ── External approval RPC messages ────────────────────────────────── + +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExternalApproveReleaseRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub target_environment: ::prost::alloc::string::String, + #[prost(string, optional, tag="4")] + pub comment: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, tag="5")] + pub force_bypass: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExternalApproveReleaseResponse { + #[prost(message, optional, tag="1")] + pub state: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ExternalRejectReleaseRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub target_environment: ::prost::alloc::string::String, + #[prost(string, optional, tag="4")] + pub comment: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExternalRejectReleaseResponse { + #[prost(message, optional, tag="1")] + pub state: ::core::option::Option, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetExternalApprovalStateRequest { + #[prost(message, optional, tag="1")] + pub project: ::core::option::Option, + #[prost(string, tag="2")] + pub release_intent_id: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub target_environment: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetExternalApprovalStateResponse { + #[prost(message, optional, tag="1")] + pub state: ::core::option::Option, +} // ── Policy types ──────────────────────────────────────────────────── #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] @@ -1601,6 +2970,7 @@ pub enum PolicyType { Unspecified = 0, SoakTime = 1, BranchRestriction = 2, + ExternalApproval = 3, } impl PolicyType { /// String value of the enum field names used in the ProtoBuf definition. @@ -1612,6 +2982,7 @@ impl PolicyType { Self::Unspecified => "POLICY_TYPE_UNSPECIFIED", Self::SoakTime => "POLICY_TYPE_SOAK_TIME", Self::BranchRestriction => "POLICY_TYPE_BRANCH_RESTRICTION", + Self::ExternalApproval => "POLICY_TYPE_EXTERNAL_APPROVAL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1620,6 +2991,7 @@ impl PolicyType { "POLICY_TYPE_UNSPECIFIED" => Some(Self::Unspecified), "POLICY_TYPE_SOAK_TIME" => Some(Self::SoakTime), "POLICY_TYPE_BRANCH_RESTRICTION" => Some(Self::BranchRestriction), + "POLICY_TYPE_EXTERNAL_APPROVAL" => Some(Self::ExternalApproval), _ => None, } } @@ -1743,6 +3115,13 @@ pub struct WaitStageConfig { #[prost(int64, tag="1")] pub duration_seconds: i64, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PlanStageConfig { + #[prost(string, tag="1")] + pub environment: ::prost::alloc::string::String, + #[prost(bool, tag="2")] + pub auto_approve: bool, +} // ── A single pipeline stage ────────────────────────────────────────── #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -1751,7 +3130,7 @@ pub struct PipelineStage { pub id: ::prost::alloc::string::String, #[prost(string, repeated, tag="2")] pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(oneof="pipeline_stage::Config", tags="10, 11")] + #[prost(oneof="pipeline_stage::Config", tags="10, 11, 12")] pub config: ::core::option::Option, } /// Nested message and enum types in `PipelineStage`. @@ -1762,6 +3141,8 @@ pub mod pipeline_stage { Deploy(super::DeployStageConfig), #[prost(message, tag="11")] Wait(super::WaitStageConfig), + #[prost(message, tag="12")] + Plan(super::PlanStageConfig), } } // ── Pipeline resource ──────────────────────────────────────────────── @@ -1844,6 +3225,7 @@ pub enum StageType { Unspecified = 0, Deploy = 1, Wait = 2, + Plan = 3, } impl StageType { /// String value of the enum field names used in the ProtoBuf definition. @@ -1855,6 +3237,7 @@ impl StageType { Self::Unspecified => "STAGE_TYPE_UNSPECIFIED", Self::Deploy => "STAGE_TYPE_DEPLOY", Self::Wait => "STAGE_TYPE_WAIT", + Self::Plan => "STAGE_TYPE_PLAN", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1863,6 +3246,7 @@ impl StageType { "STAGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified), "STAGE_TYPE_DEPLOY" => Some(Self::Deploy), "STAGE_TYPE_WAIT" => Some(Self::Wait), + "STAGE_TYPE_PLAN" => Some(Self::Plan), _ => None, } } @@ -1878,6 +3262,7 @@ pub enum PipelineStageStatus { Succeeded = 3, Failed = 4, Cancelled = 5, + AwaitingApproval = 6, } impl PipelineStageStatus { /// String value of the enum field names used in the ProtoBuf definition. @@ -1892,6 +3277,7 @@ impl PipelineStageStatus { Self::Succeeded => "PIPELINE_STAGE_STATUS_SUCCEEDED", Self::Failed => "PIPELINE_STAGE_STATUS_FAILED", Self::Cancelled => "PIPELINE_STAGE_STATUS_CANCELLED", + Self::AwaitingApproval => "PIPELINE_STAGE_STATUS_AWAITING_APPROVAL", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -1903,6 +3289,7 @@ impl PipelineStageStatus { "PIPELINE_STAGE_STATUS_SUCCEEDED" => Some(Self::Succeeded), "PIPELINE_STAGE_STATUS_FAILED" => Some(Self::Failed), "PIPELINE_STAGE_STATUS_CANCELLED" => Some(Self::Cancelled), + "PIPELINE_STAGE_STATUS_AWAITING_APPROVAL" => Some(Self::AwaitingApproval), _ => None, } } diff --git a/crates/forage-grpc/src/grpc/forest/v1/forest.v1.tonic.rs b/crates/forage-grpc/src/grpc/forest/v1/forest.v1.tonic.rs index 112882d..85c4213 100644 --- a/crates/forage-grpc/src/grpc/forest/v1/forest.v1.tonic.rs +++ b/crates/forage-grpc/src/grpc/forest/v1/forest.v1.tonic.rs @@ -1042,9 +1042,7 @@ pub mod artifact_service_client { ); let mut req = request.into_request(); req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ArtifactService", "GetArtifactSpec"), - ); + .insert(GrpcMethod::new("forest.v1.ArtifactService", "GetArtifactSpec")); self.inner.unary(req, path, codec).await } } @@ -1086,6 +1084,22 @@ pub mod artifact_service_server { tonic::Response, tonic::Status, >; + /// + async fn get_artifact_files( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn get_artifact_spec( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// #[derive(Debug)] @@ -1307,6 +1321,98 @@ pub mod artifact_service_server { }; Box::pin(fut) } + "/forest.v1.ArtifactService/GetArtifactFiles" => { + #[allow(non_camel_case_types)] + struct GetArtifactFilesSvc(pub Arc); + impl< + T: ArtifactService, + > tonic::server::UnaryService + for GetArtifactFilesSvc { + type Response = super::GetArtifactFilesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_artifact_files(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetArtifactFilesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ArtifactService/GetArtifactSpec" => { + #[allow(non_camel_case_types)] + struct GetArtifactSpecSvc(pub Arc); + impl< + T: ArtifactService, + > tonic::server::UnaryService + for GetArtifactSpecSvc { + type Response = super::GetArtifactSpecResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_artifact_spec(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetArtifactSpecSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( @@ -1348,6 +1454,1921 @@ pub mod artifact_service_server { } } /// Generated client implementations. +pub mod release_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct ReleaseServiceClient { + inner: tonic::client::Grpc, + } + impl ReleaseServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl ReleaseServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> ReleaseServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + ReleaseServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn annotate_release( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/AnnotateRelease", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "AnnotateRelease")); + self.inner.unary(req, path, codec).await + } + pub async fn release( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/Release", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "Release")); + self.inner.unary(req, path, codec).await + } + pub async fn wait_release( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/WaitRelease", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "WaitRelease")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn get_artifact_by_slug( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetArtifactBySlug", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.ReleaseService", "GetArtifactBySlug"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_artifacts_by_project( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetArtifactsByProject", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.ReleaseService", "GetArtifactsByProject"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_releases_by_actor( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetReleasesByActor", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.ReleaseService", "GetReleasesByActor"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_organisations( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetOrganisations", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "GetOrganisations")); + self.inner.unary(req, path, codec).await + } + pub async fn get_projects( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetProjects", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "GetProjects")); + self.inner.unary(req, path, codec).await + } + pub async fn create_project( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/CreateProject", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "CreateProject")); + self.inner.unary(req, path, codec).await + } + pub async fn get_destination_states( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetDestinationStates", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.ReleaseService", "GetDestinationStates"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_release_intent_states( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetReleaseIntentStates", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.ReleaseService", "GetReleaseIntentStates"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn approve_plan_stage( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/ApprovePlanStage", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "ApprovePlanStage")); + self.inner.unary(req, path, codec).await + } + pub async fn reject_plan_stage( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/RejectPlanStage", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "RejectPlanStage")); + self.inner.unary(req, path, codec).await + } + pub async fn get_plan_output( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ReleaseService/GetPlanOutput", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ReleaseService", "GetPlanOutput")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod release_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with ReleaseServiceServer. + #[async_trait] + pub trait ReleaseService: std::marker::Send + std::marker::Sync + 'static { + async fn annotate_release( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn release( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Server streaming response type for the WaitRelease method. + type WaitReleaseStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn wait_release( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_artifact_by_slug( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_artifacts_by_project( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_releases_by_actor( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_organisations( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_projects( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn create_project( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_destination_states( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_release_intent_states( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn approve_plan_stage( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn reject_plan_stage( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_plan_output( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct ReleaseServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl ReleaseServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for ReleaseServiceServer + where + T: ReleaseService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/forest.v1.ReleaseService/AnnotateRelease" => { + #[allow(non_camel_case_types)] + struct AnnotateReleaseSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for AnnotateReleaseSvc { + type Response = super::AnnotateReleaseResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::annotate_release(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = AnnotateReleaseSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/Release" => { + #[allow(non_camel_case_types)] + struct ReleaseSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for ReleaseSvc { + type Response = super::ReleaseResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::release(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ReleaseSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/WaitRelease" => { + #[allow(non_camel_case_types)] + struct WaitReleaseSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::ServerStreamingService + for WaitReleaseSvc { + type Response = super::WaitReleaseEvent; + type ResponseStream = T::WaitReleaseStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::wait_release(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = WaitReleaseSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetArtifactBySlug" => { + #[allow(non_camel_case_types)] + struct GetArtifactBySlugSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetArtifactBySlugSvc { + type Response = super::GetArtifactBySlugResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_artifact_by_slug(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetArtifactBySlugSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetArtifactsByProject" => { + #[allow(non_camel_case_types)] + struct GetArtifactsByProjectSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetArtifactsByProjectSvc { + type Response = super::GetArtifactsByProjectResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_artifacts_by_project( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetArtifactsByProjectSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetReleasesByActor" => { + #[allow(non_camel_case_types)] + struct GetReleasesByActorSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetReleasesByActorSvc { + type Response = super::GetReleasesByActorResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_releases_by_actor( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetReleasesByActorSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetOrganisations" => { + #[allow(non_camel_case_types)] + struct GetOrganisationsSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetOrganisationsSvc { + type Response = super::GetOrganisationsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_organisations(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetOrganisationsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetProjects" => { + #[allow(non_camel_case_types)] + struct GetProjectsSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetProjectsSvc { + type Response = super::GetProjectsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_projects(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetProjectsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/CreateProject" => { + #[allow(non_camel_case_types)] + struct CreateProjectSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for CreateProjectSvc { + type Response = super::CreateProjectResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create_project(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateProjectSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetDestinationStates" => { + #[allow(non_camel_case_types)] + struct GetDestinationStatesSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetDestinationStatesSvc { + type Response = super::GetDestinationStatesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_destination_states( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetDestinationStatesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetReleaseIntentStates" => { + #[allow(non_camel_case_types)] + struct GetReleaseIntentStatesSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetReleaseIntentStatesSvc { + type Response = super::GetReleaseIntentStatesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_release_intent_states( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetReleaseIntentStatesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/ApprovePlanStage" => { + #[allow(non_camel_case_types)] + struct ApprovePlanStageSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for ApprovePlanStageSvc { + type Response = super::ApprovePlanStageResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::approve_plan_stage(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ApprovePlanStageSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/RejectPlanStage" => { + #[allow(non_camel_case_types)] + struct RejectPlanStageSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for RejectPlanStageSvc { + type Response = super::RejectPlanStageResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::reject_plan_stage(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RejectPlanStageSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ReleaseService/GetPlanOutput" => { + #[allow(non_camel_case_types)] + struct GetPlanOutputSvc(pub Arc); + impl< + T: ReleaseService, + > tonic::server::UnaryService + for GetPlanOutputSvc { + type Response = super::GetPlanOutputResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_plan_output(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetPlanOutputSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new( + tonic::body::Body::default(), + ); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for ReleaseServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "forest.v1.ReleaseService"; + impl tonic::server::NamedService for ReleaseServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +/// Generated client implementations. +pub mod auto_release_policy_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + /// + #[derive(Debug, Clone)] + pub struct AutoReleasePolicyServiceClient { + inner: tonic::client::Grpc, + } + impl AutoReleasePolicyServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl AutoReleasePolicyServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> AutoReleasePolicyServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + AutoReleasePolicyServiceClient::new( + InterceptedService::new(inner, interceptor), + ) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// + pub async fn create_auto_release_policy( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.AutoReleasePolicyService/CreateAutoReleasePolicy", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "forest.v1.AutoReleasePolicyService", + "CreateAutoReleasePolicy", + ), + ); + self.inner.unary(req, path, codec).await + } + /// + pub async fn update_auto_release_policy( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.AutoReleasePolicyService/UpdateAutoReleasePolicy", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "forest.v1.AutoReleasePolicyService", + "UpdateAutoReleasePolicy", + ), + ); + self.inner.unary(req, path, codec).await + } + /// + pub async fn delete_auto_release_policy( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.AutoReleasePolicyService/DeleteAutoReleasePolicy", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "forest.v1.AutoReleasePolicyService", + "DeleteAutoReleasePolicy", + ), + ); + self.inner.unary(req, path, codec).await + } + /// + pub async fn list_auto_release_policies( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.AutoReleasePolicyService/ListAutoReleasePolicies", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "forest.v1.AutoReleasePolicyService", + "ListAutoReleasePolicies", + ), + ); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod auto_release_policy_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with AutoReleasePolicyServiceServer. + #[async_trait] + pub trait AutoReleasePolicyService: std::marker::Send + std::marker::Sync + 'static { + /// + async fn create_auto_release_policy( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn update_auto_release_policy( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn delete_auto_release_policy( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn list_auto_release_policies( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + /// + #[derive(Debug)] + pub struct AutoReleasePolicyServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl AutoReleasePolicyServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> + for AutoReleasePolicyServiceServer + where + T: AutoReleasePolicyService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/forest.v1.AutoReleasePolicyService/CreateAutoReleasePolicy" => { + #[allow(non_camel_case_types)] + struct CreateAutoReleasePolicySvc( + pub Arc, + ); + impl< + T: AutoReleasePolicyService, + > tonic::server::UnaryService + for CreateAutoReleasePolicySvc { + type Response = super::CreateAutoReleasePolicyResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::CreateAutoReleasePolicyRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::create_auto_release_policy( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CreateAutoReleasePolicySvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.AutoReleasePolicyService/UpdateAutoReleasePolicy" => { + #[allow(non_camel_case_types)] + struct UpdateAutoReleasePolicySvc( + pub Arc, + ); + impl< + T: AutoReleasePolicyService, + > tonic::server::UnaryService + for UpdateAutoReleasePolicySvc { + type Response = super::UpdateAutoReleasePolicyResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::UpdateAutoReleasePolicyRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::update_auto_release_policy( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = UpdateAutoReleasePolicySvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.AutoReleasePolicyService/DeleteAutoReleasePolicy" => { + #[allow(non_camel_case_types)] + struct DeleteAutoReleasePolicySvc( + pub Arc, + ); + impl< + T: AutoReleasePolicyService, + > tonic::server::UnaryService + for DeleteAutoReleasePolicySvc { + type Response = super::DeleteAutoReleasePolicyResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::DeleteAutoReleasePolicyRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_auto_release_policy( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteAutoReleasePolicySvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.AutoReleasePolicyService/ListAutoReleasePolicies" => { + #[allow(non_camel_case_types)] + struct ListAutoReleasePoliciesSvc( + pub Arc, + ); + impl< + T: AutoReleasePolicyService, + > tonic::server::UnaryService + for ListAutoReleasePoliciesSvc { + type Response = super::ListAutoReleasePoliciesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::ListAutoReleasePoliciesRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_auto_release_policies( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListAutoReleasePoliciesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new( + tonic::body::Body::default(), + ); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for AutoReleasePolicyServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "forest.v1.AutoReleasePolicyService"; + impl tonic::server::NamedService for AutoReleasePolicyServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +/// Generated client implementations. pub mod destination_service_client { #![allow( unused_variables, @@ -3669,6 +5690,465 @@ pub mod event_subscription_service_server { } } /// Generated client implementations. +pub mod forage_service_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct ForageServiceClient { + inner: tonic::client::Grpc, + } + impl ForageServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl ForageServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> ForageServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + ForageServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn apply_resources( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ForageService/ApplyResources", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ForageService", "ApplyResources")); + self.inner.unary(req, path, codec).await + } + pub async fn watch_rollout( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ForageService/WatchRollout", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ForageService", "WatchRollout")); + self.inner.server_streaming(req, path, codec).await + } + pub async fn delete_resources( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.ForageService/DeleteResources", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("forest.v1.ForageService", "DeleteResources")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod forage_service_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with ForageServiceServer. + #[async_trait] + pub trait ForageService: std::marker::Send + std::marker::Sync + 'static { + async fn apply_resources( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the WatchRollout method. + type WatchRolloutStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + std::marker::Send + + 'static; + async fn watch_rollout( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn delete_resources( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct ForageServiceServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl ForageServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for ForageServiceServer + where + T: ForageService, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/forest.v1.ForageService/ApplyResources" => { + #[allow(non_camel_case_types)] + struct ApplyResourcesSvc(pub Arc); + impl< + T: ForageService, + > tonic::server::UnaryService + for ApplyResourcesSvc { + type Response = super::ApplyResourcesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::apply_resources(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ApplyResourcesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ForageService/WatchRollout" => { + #[allow(non_camel_case_types)] + struct WatchRolloutSvc(pub Arc); + impl< + T: ForageService, + > tonic::server::ServerStreamingService + for WatchRolloutSvc { + type Response = super::RolloutEvent; + type ResponseStream = T::WatchRolloutStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::watch_rollout(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = WatchRolloutSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.ForageService/DeleteResources" => { + #[allow(non_camel_case_types)] + struct DeleteResourcesSvc(pub Arc); + impl< + T: ForageService, + > tonic::server::UnaryService + for DeleteResourcesSvc { + type Response = super::DeleteResourcesResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::delete_resources(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DeleteResourcesSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new( + tonic::body::Body::default(), + ); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for ForageServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "forest.v1.ForageService"; + impl tonic::server::NamedService for ForageServiceServer { + const NAME: &'static str = SERVICE_NAME; + } +} +/// Generated client implementations. pub mod status_service_client { #![allow( unused_variables, @@ -5453,1098 +7933,6 @@ pub mod organisation_service_server { } } /// Generated client implementations. -pub mod release_service_client { - #![allow( - unused_variables, - dead_code, - missing_docs, - clippy::wildcard_imports, - clippy::let_unit_value, - )] - use tonic::codegen::*; - use tonic::codegen::http::Uri; - #[derive(Debug, Clone)] - pub struct ReleaseServiceClient { - inner: tonic::client::Grpc, - } - impl ReleaseServiceClient { - /// Attempt to create a new client by connecting to a given endpoint. - pub async fn connect(dst: D) -> Result - where - D: TryInto, - D::Error: Into, - { - let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; - Ok(Self::new(conn)) - } - } - impl ReleaseServiceClient - where - T: tonic::client::GrpcService, - T::Error: Into, - T::ResponseBody: Body + std::marker::Send + 'static, - ::Error: Into + std::marker::Send, - { - pub fn new(inner: T) -> Self { - let inner = tonic::client::Grpc::new(inner); - Self { inner } - } - pub fn with_origin(inner: T, origin: Uri) -> Self { - let inner = tonic::client::Grpc::with_origin(inner, origin); - Self { inner } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> ReleaseServiceClient> - where - F: tonic::service::Interceptor, - T::ResponseBody: Default, - T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, - >, - >, - , - >>::Error: Into + std::marker::Send + std::marker::Sync, - { - ReleaseServiceClient::new(InterceptedService::new(inner, interceptor)) - } - /// Compress requests with the given encoding. - /// - /// This requires the server to support it otherwise it might respond with an - /// error. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.send_compressed(encoding); - self - } - /// Enable decompressing responses. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.accept_compressed(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_decoding_message_size(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_encoding_message_size(limit); - self - } - pub async fn annotate_release( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/AnnotateRelease", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "AnnotateRelease")); - self.inner.unary(req, path, codec).await - } - pub async fn release( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/Release", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "Release")); - self.inner.unary(req, path, codec).await - } - pub async fn wait_release( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/WaitRelease", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "WaitRelease")); - self.inner.server_streaming(req, path, codec).await - } - pub async fn get_artifact_by_slug( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetArtifactBySlug", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ReleaseService", "GetArtifactBySlug"), - ); - self.inner.unary(req, path, codec).await - } - pub async fn get_artifacts_by_project( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetArtifactsByProject", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ReleaseService", "GetArtifactsByProject"), - ); - self.inner.unary(req, path, codec).await - } - pub async fn get_releases_by_actor( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetReleasesByActor", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ReleaseService", "GetReleasesByActor"), - ); - self.inner.unary(req, path, codec).await - } - pub async fn get_organisations( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetOrganisations", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "GetOrganisations")); - self.inner.unary(req, path, codec).await - } - pub async fn get_projects( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetProjects", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "GetProjects")); - self.inner.unary(req, path, codec).await - } - pub async fn create_project( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/CreateProject", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("forest.v1.ReleaseService", "CreateProject")); - self.inner.unary(req, path, codec).await - } - pub async fn get_destination_states( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetDestinationStates", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ReleaseService", "GetDestinationStates"), - ); - self.inner.unary(req, path, codec).await - } - pub async fn get_release_intent_states( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/forest.v1.ReleaseService/GetReleaseIntentStates", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("forest.v1.ReleaseService", "GetReleaseIntentStates"), - ); - self.inner.unary(req, path, codec).await - } - } -} -/// Generated server implementations. -pub mod release_service_server { - #![allow( - unused_variables, - dead_code, - missing_docs, - clippy::wildcard_imports, - clippy::let_unit_value, - )] - use tonic::codegen::*; - /// Generated trait containing gRPC methods that should be implemented for use with ReleaseServiceServer. - #[async_trait] - pub trait ReleaseService: std::marker::Send + std::marker::Sync + 'static { - async fn annotate_release( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn release( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status>; - /// Server streaming response type for the WaitRelease method. - type WaitReleaseStream: tonic::codegen::tokio_stream::Stream< - Item = std::result::Result, - > - + std::marker::Send - + 'static; - async fn wait_release( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_artifact_by_slug( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_artifacts_by_project( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_releases_by_actor( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_organisations( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_projects( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn create_project( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_destination_states( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - async fn get_release_intent_states( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - } - #[derive(Debug)] - pub struct ReleaseServiceServer { - inner: Arc, - accept_compression_encodings: EnabledCompressionEncodings, - send_compression_encodings: EnabledCompressionEncodings, - max_decoding_message_size: Option, - max_encoding_message_size: Option, - } - impl ReleaseServiceServer { - pub fn new(inner: T) -> Self { - Self::from_arc(Arc::new(inner)) - } - pub fn from_arc(inner: Arc) -> Self { - Self { - inner, - accept_compression_encodings: Default::default(), - send_compression_encodings: Default::default(), - max_decoding_message_size: None, - max_encoding_message_size: None, - } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService - where - F: tonic::service::Interceptor, - { - InterceptedService::new(Self::new(inner), interceptor) - } - /// Enable decompressing requests with the given encoding. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.accept_compression_encodings.enable(encoding); - self - } - /// Compress responses with the given encoding, if the client supports it. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.send_compression_encodings.enable(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.max_decoding_message_size = Some(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.max_encoding_message_size = Some(limit); - self - } - } - impl tonic::codegen::Service> for ReleaseServiceServer - where - T: ReleaseService, - B: Body + std::marker::Send + 'static, - B::Error: Into + std::marker::Send + 'static, - { - type Response = http::Response; - type Error = std::convert::Infallible; - type Future = BoxFuture; - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, req: http::Request) -> Self::Future { - match req.uri().path() { - "/forest.v1.ReleaseService/AnnotateRelease" => { - #[allow(non_camel_case_types)] - struct AnnotateReleaseSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for AnnotateReleaseSvc { - type Response = super::AnnotateReleaseResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::annotate_release(&inner, request) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = AnnotateReleaseSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/Release" => { - #[allow(non_camel_case_types)] - struct ReleaseSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for ReleaseSvc { - type Response = super::ReleaseResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::release(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = ReleaseSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/WaitRelease" => { - #[allow(non_camel_case_types)] - struct WaitReleaseSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::ServerStreamingService - for WaitReleaseSvc { - type Response = super::WaitReleaseEvent; - type ResponseStream = T::WaitReleaseStream; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::wait_release(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = WaitReleaseSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.server_streaming(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetArtifactBySlug" => { - #[allow(non_camel_case_types)] - struct GetArtifactBySlugSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetArtifactBySlugSvc { - type Response = super::GetArtifactBySlugResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_artifact_by_slug(&inner, request) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetArtifactBySlugSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetArtifactsByProject" => { - #[allow(non_camel_case_types)] - struct GetArtifactsByProjectSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetArtifactsByProjectSvc { - type Response = super::GetArtifactsByProjectResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_artifacts_by_project( - &inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetArtifactsByProjectSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetReleasesByActor" => { - #[allow(non_camel_case_types)] - struct GetReleasesByActorSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetReleasesByActorSvc { - type Response = super::GetReleasesByActorResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_releases_by_actor( - &inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetReleasesByActorSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetOrganisations" => { - #[allow(non_camel_case_types)] - struct GetOrganisationsSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetOrganisationsSvc { - type Response = super::GetOrganisationsResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_organisations(&inner, request) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetOrganisationsSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetProjects" => { - #[allow(non_camel_case_types)] - struct GetProjectsSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetProjectsSvc { - type Response = super::GetProjectsResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_projects(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetProjectsSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/CreateProject" => { - #[allow(non_camel_case_types)] - struct CreateProjectSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for CreateProjectSvc { - type Response = super::CreateProjectResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::create_project(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = CreateProjectSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetDestinationStates" => { - #[allow(non_camel_case_types)] - struct GetDestinationStatesSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetDestinationStatesSvc { - type Response = super::GetDestinationStatesResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_destination_states( - &inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetDestinationStatesSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/forest.v1.ReleaseService/GetReleaseIntentStates" => { - #[allow(non_camel_case_types)] - struct GetReleaseIntentStatesSvc(pub Arc); - impl< - T: ReleaseService, - > tonic::server::UnaryService - for GetReleaseIntentStatesSvc { - type Response = super::GetReleaseIntentStatesResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_release_intent_states( - &inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetReleaseIntentStatesSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - _ => { - Box::pin(async move { - let mut response = http::Response::new( - tonic::body::Body::default(), - ); - let headers = response.headers_mut(); - headers - .insert( - tonic::Status::GRPC_STATUS, - (tonic::Code::Unimplemented as i32).into(), - ); - headers - .insert( - http::header::CONTENT_TYPE, - tonic::metadata::GRPC_CONTENT_TYPE, - ); - Ok(response) - }) - } - } - } - } - impl Clone for ReleaseServiceServer { - fn clone(&self) -> Self { - let inner = self.inner.clone(); - Self { - inner, - accept_compression_encodings: self.accept_compression_encodings, - send_compression_encodings: self.send_compression_encodings, - max_decoding_message_size: self.max_decoding_message_size, - max_encoding_message_size: self.max_encoding_message_size, - } - } - } - /// Generated gRPC service name - pub const SERVICE_NAME: &str = "forest.v1.ReleaseService"; - impl tonic::server::NamedService for ReleaseServiceServer { - const NAME: &'static str = SERVICE_NAME; - } -} -/// Generated client implementations. pub mod policy_service_client { #![allow( unused_variables, @@ -6761,6 +8149,90 @@ pub mod policy_service_client { .insert(GrpcMethod::new("forest.v1.PolicyService", "EvaluatePolicies")); self.inner.unary(req, path, codec).await } + /// + pub async fn external_approve_release( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.PolicyService/ExternalApproveRelease", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.PolicyService", "ExternalApproveRelease"), + ); + self.inner.unary(req, path, codec).await + } + /// + pub async fn external_reject_release( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.PolicyService/ExternalRejectRelease", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("forest.v1.PolicyService", "ExternalRejectRelease"), + ); + self.inner.unary(req, path, codec).await + } + /// + pub async fn get_external_approval_state( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/forest.v1.PolicyService/GetExternalApprovalState", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "forest.v1.PolicyService", + "GetExternalApprovalState", + ), + ); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -6816,6 +8288,30 @@ pub mod policy_service_server { tonic::Response, tonic::Status, >; + /// + async fn external_approve_release( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn external_reject_release( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// + async fn get_external_approval_state( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// #[derive(Debug)] @@ -7120,6 +8616,155 @@ pub mod policy_service_server { }; Box::pin(fut) } + "/forest.v1.PolicyService/ExternalApproveRelease" => { + #[allow(non_camel_case_types)] + struct ExternalApproveReleaseSvc(pub Arc); + impl< + T: PolicyService, + > tonic::server::UnaryService + for ExternalApproveReleaseSvc { + type Response = super::ExternalApproveReleaseResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::external_approve_release( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ExternalApproveReleaseSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.PolicyService/ExternalRejectRelease" => { + #[allow(non_camel_case_types)] + struct ExternalRejectReleaseSvc(pub Arc); + impl< + T: PolicyService, + > tonic::server::UnaryService + for ExternalRejectReleaseSvc { + type Response = super::ExternalRejectReleaseResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::external_reject_release( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ExternalRejectReleaseSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/forest.v1.PolicyService/GetExternalApprovalState" => { + #[allow(non_camel_case_types)] + struct GetExternalApprovalStateSvc(pub Arc); + impl< + T: PolicyService, + > tonic::server::UnaryService + for GetExternalApprovalStateSvc { + type Response = super::GetExternalApprovalStateResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GetExternalApprovalStateRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_external_approval_state( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetExternalApprovalStateSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( diff --git a/crates/forage-server/src/auth.rs b/crates/forage-server/src/auth.rs index 008ad10..6de296e 100644 --- a/crates/forage-server/src/auth.rs +++ b/crates/forage-server/src/auth.rs @@ -108,14 +108,17 @@ impl FromRequestParts for Session { } } } else { - // Backfill: if we have a user but empty orgs, try to fetch them. - // This handles the case where list_my_organisations failed during login. - let needs_org_backfill = session_data + // Refresh orgs if they're empty OR if the session hasn't been seen + // for a while (e.g. after server restart, PG session loaded with stale orgs). + let now = chrono::Utc::now(); + let orgs_empty = session_data .user .as_ref() .is_some_and(|u| u.orgs.is_empty()); + let orgs_stale = now - session_data.last_seen_at > chrono::Duration::minutes(5); + let needs_org_refresh = orgs_empty || orgs_stale; - if needs_org_backfill { + if needs_org_refresh { if let Ok(orgs) = state .platform_client .list_my_organisations(&session_data.access_token) @@ -126,7 +129,8 @@ impl FromRequestParts for Session { tracing::info!( user_id = %user.user_id, org_count = orgs.len(), - "backfilled empty org list" + was_empty = orgs_empty, + "refreshed org list" ); user.orgs = orgs .into_iter() diff --git a/crates/forage-server/src/forest_client.rs b/crates/forage-server/src/forest_client.rs index 20e0aa3..74e43a1 100644 --- a/crates/forage-server/src/forest_client.rs +++ b/crates/forage-server/src/forest_client.rs @@ -3,11 +3,12 @@ use forage_core::auth::{ UserProfile, }; use forage_core::platform::{ - Artifact, ArtifactContext, ArtifactDestination, ArtifactRef, ArtifactSource, CreatePolicyInput, - CreateReleasePipelineInput, CreateTriggerInput, Destination, DestinationType, Environment, - ForestPlatform, NotificationPreference, Organisation, OrgMember, PipelineStage, - PipelineStageConfig, PlatformError, Policy, PolicyConfig, ReleasePipeline, Trigger, - UpdatePolicyInput, UpdateReleasePipelineInput, UpdateTriggerInput, + ApprovalDecisionEntry, ApprovalState, Artifact, ArtifactContext, ArtifactDestination, + ArtifactRef, ArtifactSource, CreatePolicyInput, CreateReleasePipelineInput, CreateTriggerInput, + Destination, DestinationType, Environment, ForestPlatform, NotificationPreference, Organisation, + OrgMember, PipelineStage, PipelineStageConfig, PlatformError, Policy, PolicyConfig, + PolicyEvaluation, ReleasePipeline, Trigger, UpdatePolicyInput, UpdateReleasePipelineInput, + UpdateTriggerInput, }; use forage_grpc::policy_service_client::PolicyServiceClient; use forage_grpc::release_pipeline_service_client::ReleasePipelineServiceClient; @@ -582,6 +583,9 @@ fn convert_pipeline_stage(s: forage_grpc::PipelineStage) -> PipelineStage { Some(forage_grpc::pipeline_stage::Config::Wait(w)) => { PipelineStageConfig::Wait { duration_seconds: w.duration_seconds } } + Some(forage_grpc::pipeline_stage::Config::Plan(_)) => { + PipelineStageConfig::Deploy { environment: String::new() } + } None => PipelineStageConfig::Deploy { environment: String::new() }, }; PipelineStage { @@ -698,6 +702,7 @@ fn convert_policy(p: forage_grpc::Policy) -> Policy { let policy_type_str = match forage_grpc::PolicyType::try_from(p.policy_type) { Ok(forage_grpc::PolicyType::SoakTime) => "soak_time", Ok(forage_grpc::PolicyType::BranchRestriction) => "branch_restriction", + Ok(forage_grpc::PolicyType::ExternalApproval) => "approval", _ => "unknown", }; let config = match p.config { @@ -712,6 +717,10 @@ fn convert_policy(p: forage_grpc::Policy) -> Policy { branch_pattern: c.branch_pattern, } } + Some(forage_grpc::policy::Config::ExternalApproval(c)) => PolicyConfig::Approval { + target_environment: c.target_environment, + required_approvals: c.required_approvals, + }, None => PolicyConfig::SoakTime { source_environment: String::new(), target_environment: String::new(), @@ -761,6 +770,20 @@ fn policy_config_to_grpc( ), ), ), + PolicyConfig::Approval { + target_environment, + required_approvals, + } => ( + forage_grpc::PolicyType::ExternalApproval as i32, + Some( + forage_grpc::create_policy_request::Config::ExternalApproval( + forage_grpc::ExternalApprovalConfig { + target_environment: target_environment.clone(), + required_approvals: *required_approvals, + }, + ), + ), + ), } } @@ -775,8 +798,9 @@ fn convert_member(m: forage_grpc::OrganisationMember) -> OrgMember { fn map_platform_status(status: tonic::Status) -> PlatformError { match status.code() { - tonic::Code::Unauthenticated | tonic::Code::PermissionDenied => { - PlatformError::NotAuthenticated + tonic::Code::Unauthenticated => PlatformError::NotAuthenticated, + tonic::Code::PermissionDenied => { + PlatformError::Other(status.message().into()) } tonic::Code::NotFound => PlatformError::NotFound(status.message().into()), tonic::Code::Unavailable => PlatformError::Unavailable(status.message().into()), @@ -1270,6 +1294,7 @@ impl ForestPlatform for GrpcForestClient { environments: environments.to_vec(), force: false, use_pipeline, + prepare_only: false, }, ) .map_err(|e| PlatformError::Other(e.to_string()))?; @@ -1481,6 +1506,9 @@ impl ForestPlatform for GrpcForestClient { Some(forage_grpc::create_policy_request::Config::BranchRestriction(b)) => { forage_grpc::update_policy_request::Config::BranchRestriction(b) } + Some(forage_grpc::create_policy_request::Config::ExternalApproval(a)) => { + forage_grpc::update_policy_request::Config::ExternalApproval(a) + } None => forage_grpc::update_policy_request::Config::SoakTime( forage_grpc::SoakTimeConfig::default(), ), @@ -1724,6 +1752,168 @@ impl ForestPlatform for GrpcForestClient { .map_err(map_platform_status)?; Ok(()) } + + async fn evaluate_policies( + &self, + access_token: &str, + organisation: &str, + project: &str, + target_environment: &str, + release_intent_id: Option<&str>, + ) -> Result, PlatformError> { + let req = platform_authed_request( + access_token, + forage_grpc::EvaluatePoliciesRequest { + project: Some(forage_grpc::Project { + organisation: organisation.into(), + project: project.into(), + }), + target_environment: target_environment.into(), + branch: None, + release_intent_id: release_intent_id.map(|s| s.to_string()), + }, + )?; + let resp = self + .policy_client() + .evaluate_policies(req) + .await + .map_err(map_platform_status)?; + Ok(resp + .into_inner() + .evaluations + .into_iter() + .map(convert_policy_evaluation) + .collect()) + } + + async fn approve_release( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + comment: Option<&str>, + force_bypass: bool, + ) -> Result { + let req = platform_authed_request( + access_token, + forage_grpc::ExternalApproveReleaseRequest { + project: Some(forage_grpc::Project { + organisation: organisation.into(), + project: project.into(), + }), + release_intent_id: release_intent_id.into(), + target_environment: target_environment.into(), + comment: comment.map(|s| s.to_string()), + force_bypass, + }, + )?; + let resp = self + .policy_client() + .external_approve_release(req) + .await + .map_err(map_platform_status)?; + Ok(convert_approval_state(resp.into_inner().state)) + } + + async fn reject_release( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + comment: Option<&str>, + ) -> Result { + let req = platform_authed_request( + access_token, + forage_grpc::ExternalRejectReleaseRequest { + project: Some(forage_grpc::Project { + organisation: organisation.into(), + project: project.into(), + }), + release_intent_id: release_intent_id.into(), + target_environment: target_environment.into(), + comment: comment.map(|s| s.to_string()), + }, + )?; + let resp = self + .policy_client() + .external_reject_release(req) + .await + .map_err(map_platform_status)?; + Ok(convert_approval_state(resp.into_inner().state)) + } + + async fn get_approval_state( + &self, + access_token: &str, + organisation: &str, + project: &str, + release_intent_id: &str, + target_environment: &str, + ) -> Result { + let req = platform_authed_request( + access_token, + forage_grpc::GetExternalApprovalStateRequest { + project: Some(forage_grpc::Project { + organisation: organisation.into(), + project: project.into(), + }), + release_intent_id: release_intent_id.into(), + target_environment: target_environment.into(), + }, + )?; + let resp = self + .policy_client() + .get_external_approval_state(req) + .await + .map_err(map_platform_status)?; + Ok(convert_approval_state(resp.into_inner().state)) + } +} + +fn convert_policy_evaluation(e: forage_grpc::PolicyEvaluation) -> PolicyEvaluation { + let policy_type = match e.policy_type { + 1 => "soak_time", + 2 => "branch_restriction", + 3 => "approval", + _ => "unknown", + }; + let approval_state = e.approval_state.map(|s| convert_approval_state(Some(s))); + PolicyEvaluation { + policy_name: e.policy_name, + policy_type: policy_type.into(), + passed: e.passed, + reason: e.reason, + approval_state, + } +} + +fn convert_approval_state(state: Option) -> ApprovalState { + match state { + Some(s) => ApprovalState { + required_approvals: s.required_approvals, + current_approvals: s.current_approvals, + decisions: s + .decisions + .into_iter() + .map(|d| ApprovalDecisionEntry { + user_id: d.user_id, + username: d.username, + decision: d.decision, + decided_at: d.decided_at, + comment: d.comment, + }) + .collect(), + }, + None => ApprovalState { + required_approvals: 0, + current_approvals: 0, + decisions: vec![], + }, + } } #[cfg(test)] diff --git a/crates/forage-server/src/routes/platform.rs b/crates/forage-server/src/routes/platform.rs index 9e285ab..b649739 100644 --- a/crates/forage-server/src/routes/platform.rs +++ b/crates/forage-server/src/routes/platform.rs @@ -100,6 +100,14 @@ pub fn router() -> Router { "/orgs/{org}/projects/{project}/policies/{name}/delete", post(delete_policy), ) + .route( + "/orgs/{org}/projects/{project}/releases/{slug}/approve", + post(approve_release_submit), + ) + .route( + "/orgs/{org}/projects/{project}/releases/{slug}/reject", + post(reject_release_submit), + ) .route( "/orgs/{org}/projects/{project}/pipelines", get(pipelines_page).post(create_pipeline_submit), @@ -682,7 +690,8 @@ async fn project_detail( Path((org, project)): Path<(String, String)>, ) -> Result { let orgs = &session.user.orgs; - require_org_membership(&state, orgs, &org)?; + let current_org = require_org_membership(&state, orgs, &org)?; + let current_role = current_org.role.clone(); if !validate_slug(&project) { return Err(error_page( @@ -767,6 +776,7 @@ async fn project_detail( org_name => &org, project_name => &project, projects => projects, + current_role => ¤t_role, active_tab => "project_overview", timeline => data.timeline, lanes => data.lanes, @@ -968,6 +978,74 @@ async fn artifact_detail( let has_pipeline = !pipeline_stages.is_empty() || project_has_pipeline; + // Fetch policy evaluations for active release intents. + let mut policy_evaluations: Vec = Vec::new(); + let mut release_intent_id_str = String::new(); + let is_release_author = false; + for ri in &release_intents { + if ri.artifact_id == artifact.artifact_id && !ri.stages.is_empty() { + release_intent_id_str = ri.release_intent_id.clone(); + // Collect unique environments from the pipeline stages. + let environments: Vec = ri + .stages + .iter() + .filter_map(|s| s.environment.clone()) + .collect::>() + .into_iter() + .collect(); + + for env in &environments { + if let Ok(evals) = state + .platform_client + .evaluate_policies( + &session.access_token, + &org, + &project, + env, + Some(&ri.release_intent_id), + ) + .await + { + for eval in evals { + let approval_state_ctx = eval.approval_state.as_ref().map(|s| { + let decisions: Vec = s + .decisions + .iter() + .map(|d| { + context! { + username => d.username, + decision => d.decision, + comment => d.comment, + decided_at => d.decided_at, + } + }) + .collect(); + context! { + required_approvals => s.required_approvals, + current_approvals => s.current_approvals, + decisions => decisions, + } + }); + policy_evaluations.push(context! { + policy_name => eval.policy_name, + policy_type => eval.policy_type, + passed => eval.passed, + reason => eval.reason, + target_environment => env, + approval_state => approval_state_ctx, + }); + } + } + } + break; // Only one active intent per artifact. + } + } + + let current_org_entry = orgs.iter().find(|o| o.name == org); + let is_admin = current_org_entry + .map(|o| o.role == "owner" || o.role == "admin") + .unwrap_or(false); + // Build env groups. let env_groups = build_env_groups(&matching_states); @@ -1045,6 +1123,10 @@ async fn artifact_detail( }).collect::>(), has_release_intents => release_intents.iter().any(|ri| ri.artifact_id == artifact.artifact_id), artifact_spec => if artifact_spec.is_empty() { None:: } else { Some(artifact_spec) }, + policy_evaluations => policy_evaluations, + release_intent_id => &release_intent_id_str, + is_release_author => is_release_author, + is_admin => is_admin, }, ) .map_err(|e| { @@ -1965,6 +2047,8 @@ pub enum ApiTimelineItem { pub struct ApiRelease { pub artifact_id: String, pub slug: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub release_intent_id: Option, pub title: String, pub description: Option, pub web: Option, @@ -2016,6 +2100,8 @@ pub struct ApiPipelineStage { pub completed_at: Option, pub error_message: Option, pub wait_until: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub blocked_by: Option, } #[derive(Debug, Serialize)] @@ -2033,6 +2119,7 @@ fn build_timeline_json( deployment_states: &forage_core::platform::DeploymentStates, release_intents: &[forage_core::platform::ReleaseIntentState], pipelines_by_project: &PipelinesByProject, + approval_envs: &[String], ) -> ApiTimelineResponse { // Index destination states by artifact_id. let mut states_by_artifact: std::collections::HashMap< @@ -2045,14 +2132,17 @@ fn build_timeline_json( } } - // Index pipeline run stages by artifact_id. + // Index pipeline run stages and intent IDs by artifact_id. let mut intent_stages_by_artifact: std::collections::HashMap< &str, &[forage_core::platform::PipelineRunStageState], > = std::collections::HashMap::new(); + let mut intent_id_by_artifact: std::collections::HashMap<&str, &str> = + std::collections::HashMap::new(); for ri in release_intents { if !ri.stages.is_empty() { intent_stages_by_artifact.insert(ri.artifact_id.as_str(), &ri.stages); + intent_id_by_artifact.insert(ri.artifact_id.as_str(), ri.release_intent_id.as_str()); } } @@ -2189,6 +2279,14 @@ fn build_timeline_json( } else { rs.status.clone() }; + let blocked_by = if display_status == "PENDING" + && rs.stage_type == "deploy" + && rs.environment.as_deref().map(|e| approval_envs.iter().any(|a| a == e)).unwrap_or(false) + { + Some("Awaiting approval".into()) + } else { + None + }; stages.push(ApiPipelineStage { id: rs.stage_id.clone(), stage_type: rs.stage_type.clone(), @@ -2199,6 +2297,7 @@ fn build_timeline_json( completed_at: rs.completed_at.clone(), error_message: rs.error_message.clone(), wait_until: rs.wait_until.clone(), + blocked_by, }); } } @@ -2238,6 +2337,9 @@ fn build_timeline_json( raw_releases.push(RawRelease { release: ApiRelease { + release_intent_id: intent_id_by_artifact + .get(artifact.artifact_id.as_str()) + .map(|s| s.to_string()), artifact_id: artifact.artifact_id, slug: artifact.slug, title: artifact.context.title, @@ -2309,7 +2411,8 @@ fn build_timeline_json( let mut seen_deployed = false; for raw in raw_releases { - if raw.has_dests { + let needs_action = raw.release.pipeline_stages.iter().any(|s| s.blocked_by.is_some()); + if raw.has_dests || needs_action { if !hidden_buf.is_empty() { let count = hidden_buf.len(); timeline.push(ApiTimelineItem::Hidden { @@ -2317,7 +2420,9 @@ fn build_timeline_json( releases: std::mem::take(&mut hidden_buf), }); } - seen_deployed = true; + if raw.has_dests { + seen_deployed = true; + } timeline.push(ApiTimelineItem::Release { release: Box::new(raw.release), }); @@ -2358,7 +2463,7 @@ async fn timeline_api( .into_response()); } - let (artifacts, environments, dest_states, release_intents, project_pipelines) = tokio::join!( + let (artifacts, environments, dest_states, release_intents, project_pipelines, policies) = tokio::join!( state .platform_client .list_artifacts(&session.access_token, &org, &project), @@ -2374,6 +2479,9 @@ async fn timeline_api( state .platform_client .list_release_pipelines(&session.access_token, &org, &project), + state + .platform_client + .list_policies(&session.access_token, &org, &project), ); let artifacts = artifacts.map_err(|e| { tracing::error!("timeline_api list_artifacts: {e:#}"); @@ -2401,7 +2509,18 @@ async fn timeline_api( pipelines_map.insert(project.clone(), project_pipelines); } - let data = build_timeline_json(items, &environments, &dest_states, &release_intents, &pipelines_map); + let policies = warn_default("list_policies", policies); + + let approval_envs: Vec = policies + .iter() + .filter(|p| p.enabled && p.policy_type == "approval") + .filter_map(|p| match &p.config { + PolicyConfig::Approval { target_environment, .. } => Some(target_environment.clone()), + _ => None, + }) + .collect(); + + let data = build_timeline_json(items, &environments, &dest_states, &release_intents, &pipelines_map, &approval_envs); Ok(Json(data).into_response()) } @@ -2463,6 +2582,7 @@ async fn org_timeline_api( &dest_states, &release_intents, &pipelines_by_project, + &[], // org timeline doesn't have per-project policy context ); Ok(Json(data).into_response()) @@ -3710,6 +3830,16 @@ async fn policies_page( branch_pattern => branch_pattern, }, ), + PolicyConfig::Approval { + target_environment, + required_approvals, + } => ( + "approval", + context! { + target_environment => target_environment, + required_approvals => required_approvals, + }, + ), }; context! { id => p.id, @@ -3810,6 +3940,9 @@ struct CreatePolicyForm { // BranchRestriction fields #[serde(default)] branch_pattern: String, + // Approval fields + #[serde(default)] + required_approvals: Option, } async fn create_policy_submit( @@ -3875,12 +4008,28 @@ async fn create_policy_submit( branch_pattern: pattern.to_string(), } } + "approval" => { + let target = form.target_environment.trim(); + let required = form.required_approvals.unwrap_or(1); + if target.is_empty() || required < 1 { + return Err(error_page( + &state, + StatusCode::BAD_REQUEST, + "Invalid request", + "Approval requires a target environment and at least 1 required approval.", + )); + } + PolicyConfig::Approval { + target_environment: target.to_string(), + required_approvals: required, + } + } _ => { return Err(error_page( &state, StatusCode::BAD_REQUEST, "Invalid request", - "Invalid policy type. Must be 'soak_time' or 'branch_restriction'.", + "Invalid policy type.", )); } }; @@ -4042,6 +4191,16 @@ async fn edit_policy_page( branch_pattern => branch_pattern, }, ), + PolicyConfig::Approval { + target_environment, + required_approvals, + } => ( + "approval", + context! { + target_environment => target_environment, + required_approvals => required_approvals, + }, + ), }; let policy_ctx = context! { @@ -4464,3 +4623,138 @@ fn non_empty(s: &str) -> Option { } } +// ── Approval routes ────────────────────────────────────────────────── + +#[derive(Deserialize)] +struct ApprovalForm { + csrf_token: String, + #[serde(default)] + release_intent_id: String, + #[serde(default)] + target_environment: String, + #[serde(default)] + comment: String, + #[serde(default)] + force_bypass: Option, +} + +fn approval_error( + state: &AppState, + headers: &axum::http::HeaderMap, + status: StatusCode, + message: &str, +) -> Response { + let wants_json = headers + .get(axum::http::header::ACCEPT) + .and_then(|v| v.to_str().ok()) + .is_some_and(|v| v.contains("application/json")); + + if wants_json { + (status, Json(serde_json::json!({ "error": message }))).into_response() + } else { + error_page(state, status, "Approval failed", message) + } +} + +async fn approve_release_submit( + State(state): State, + session: Session, + headers: axum::http::HeaderMap, + Path((org, project, slug)): Path<(String, String, String)>, + Form(form): Form, +) -> Result { + let orgs = &session.user.orgs; + require_org_membership(&state, orgs, &org)?; + + if form.csrf_token != session.csrf_token { + return Err(approval_error( + &state, + &headers, + StatusCode::FORBIDDEN, + "CSRF validation failed. Please try again.", + )); + } + + let force_bypass = form.force_bypass.as_deref() == Some("true"); + let comment = non_empty(&form.comment); + + state + .platform_client + .approve_release( + &session.access_token, + &org, + &project, + &form.release_intent_id, + &form.target_environment, + comment.as_deref(), + force_bypass, + ) + .await + .map_err(|e| match e { + forage_core::platform::PlatformError::NotAuthenticated => { + axum::response::Redirect::to("/login").into_response() + } + other => approval_error( + &state, + &headers, + StatusCode::INTERNAL_SERVER_ERROR, + &format!("{other}"), + ), + })?; + + Ok(Redirect::to(&format!( + "/orgs/{org}/projects/{project}/releases/{slug}" + )) + .into_response()) +} + +async fn reject_release_submit( + State(state): State, + session: Session, + headers: axum::http::HeaderMap, + Path((org, project, slug)): Path<(String, String, String)>, + Form(form): Form, +) -> Result { + let orgs = &session.user.orgs; + require_org_membership(&state, orgs, &org)?; + + if form.csrf_token != session.csrf_token { + return Err(approval_error( + &state, + &headers, + StatusCode::FORBIDDEN, + "CSRF validation failed. Please try again.", + )); + } + + let comment = non_empty(&form.comment); + + state + .platform_client + .reject_release( + &session.access_token, + &org, + &project, + &form.release_intent_id, + &form.target_environment, + comment.as_deref(), + ) + .await + .map_err(|e| match e { + forage_core::platform::PlatformError::NotAuthenticated => { + axum::response::Redirect::to("/login").into_response() + } + other => approval_error( + &state, + &headers, + StatusCode::INTERNAL_SERVER_ERROR, + &format!("{other}"), + ), + })?; + + Ok(Redirect::to(&format!( + "/orgs/{org}/projects/{project}/releases/{slug}" + )) + .into_response()) +} + diff --git a/crates/forage-server/src/test_support.rs b/crates/forage-server/src/test_support.rs index 7c32ede..075599a 100644 --- a/crates/forage-server/src/test_support.rs +++ b/crates/forage-server/src/test_support.rs @@ -714,6 +714,65 @@ impl ForestPlatform for MockPlatformClient { .clone() .unwrap_or(Ok(())) } + + async fn evaluate_policies( + &self, + _access_token: &str, + _organisation: &str, + _project: &str, + _target_environment: &str, + _release_intent_id: Option<&str>, + ) -> Result, PlatformError> { + Ok(vec![]) + } + + async fn approve_release( + &self, + _access_token: &str, + _organisation: &str, + _project: &str, + _release_intent_id: &str, + _target_environment: &str, + _comment: Option<&str>, + _force_bypass: bool, + ) -> Result { + Ok(forage_core::platform::ApprovalState { + required_approvals: 1, + current_approvals: 1, + decisions: vec![], + }) + } + + async fn reject_release( + &self, + _access_token: &str, + _organisation: &str, + _project: &str, + _release_intent_id: &str, + _target_environment: &str, + _comment: Option<&str>, + ) -> Result { + Ok(forage_core::platform::ApprovalState { + required_approvals: 1, + current_approvals: 0, + decisions: vec![], + }) + } + + async fn get_approval_state( + &self, + _access_token: &str, + _organisation: &str, + _project: &str, + _release_intent_id: &str, + _target_environment: &str, + ) -> Result { + Ok(forage_core::platform::ApprovalState { + required_approvals: 1, + current_approvals: 0, + decisions: vec![], + }) + } } pub(crate) fn make_templates() -> TemplateEngine { diff --git a/dashboard-after-fix.png b/dashboard-after-fix.png new file mode 100644 index 0000000..750eb2a Binary files /dev/null and b/dashboard-after-fix.png differ diff --git a/frontend/src/ReleaseTimeline.svelte b/frontend/src/ReleaseTimeline.svelte index 746c4b1..e4e2728 100644 --- a/frontend/src/ReleaseTimeline.svelte +++ b/frontend/src/ReleaseTimeline.svelte @@ -9,6 +9,9 @@ // Props from attributes export let org = ""; export let project = ""; + export let csrf = ""; + export let username = ""; + export let role = ""; // Reactive state let timeline = []; @@ -29,6 +32,70 @@ const IN_FLIGHT = new Set(["QUEUED", "RUNNING", "ASSIGNED"]); const DEPLOYED = new Set(["SUCCEEDED"]); + // ── Approval action ────────────────────────────────────────────── + + let approving = new Set(); + let approvalError = null; + + function isAdmin() { + return role === "owner" || role === "admin"; + } + + function isAuthor(release) { + return username && release.source_user === username; + } + + async function approveRelease(release, stage, bypass = false) { + const key = `${release.release_intent_id}:${stage.environment}`; + if (approving.has(key)) return; + approving.add(key); + approving = approving; // trigger reactivity + approvalError = null; + + try { + const formData = new URLSearchParams(); + formData.set("csrf_token", csrf); + formData.set("release_intent_id", release.release_intent_id); + formData.set("target_environment", stage.environment); + if (bypass) formData.set("force_bypass", "true"); + + const res = await fetch( + `/orgs/${org}/projects/${release.project_name}/releases/${release.slug}/approve`, + { + method: "POST", + body: formData, + credentials: "same-origin", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + }, + redirect: "manual", + } + ); + // 303/302 redirect = success (form handler redirects after approval) + if (res.ok || res.status === 303 || res.status === 302 || res.status === 0) { + await refreshData(); + } else { + // Try JSON error first, then extract from HTML + const text = await res.text().catch(() => ""); + let msg; + try { msg = JSON.parse(text).error; } catch {} + if (!msg) { + const match = text.match(/]*>\s*(.*?)\s*<\/p>/); + msg = match?.[1]; + } + approvalError = msg || `Approval failed (${res.status})`; + setTimeout(() => { approvalError = null; }, 8000); + } + } catch (err) { + approvalError = err.message || "Approval request failed"; + setTimeout(() => { approvalError = null; }, 8000); + } finally { + approving.delete(key); + approving = approving; + } + } + // ── Data fetching ──────────────────────────────────────────────── // Debounce re-fetches: multiple SSE events within 300ms only trigger one fetch @@ -367,6 +434,16 @@ +{#if approvalError} +
+ + {approvalError} + +
+{/if} + {#if initialLoading}
@@ -466,6 +543,8 @@ {:else if summary.icon === "clock"} + {:else if summary.icon === "shield"} + {:else} {/if} @@ -480,6 +559,21 @@ {/if} + {#if stage.blocked_by && release.release_intent_id && csrf} + {#if isAuthor(release) && isAdmin()} + + {:else if !isAuthor(release)} + + {/if} + {/if} {/each} {summary.done}/{summary.total} diff --git a/frontend/src/lib/status.js b/frontend/src/lib/status.js index b32298f..fdedf85 100644 --- a/frontend/src/lib/status.js +++ b/frontend/src/lib/status.js @@ -24,8 +24,11 @@ export function pipelineSummary(stages) { if (s.stage_type === "wait" && s.status === "RUNNING") anyWaiting = true; } + let anyApprovalBlocked = stages.some(s => s.blocked_by); + if (allDone) return { label: "Pipeline complete", color: "text-gray-600", icon: "check-circle", iconColor: "text-green-500", done, total }; if (anyFailed) return { label: "Pipeline failed", color: "text-red-600", icon: "x-circle", iconColor: "text-red-500", done, total }; + if (anyApprovalBlocked) return { label: "Awaiting approval", color: "text-emerald-700", icon: "shield", iconColor: "text-emerald-500", done, total }; if (anyWaiting) return { label: "Waiting for time window", color: "text-yellow-700", icon: "clock", iconColor: "text-yellow-500", done, total }; if (anyRunning) return { label: "Deploying to", color: "text-yellow-700", icon: "pulse", iconColor: "text-yellow-500", done, total }; if (anyQueued) return { label: "Queued", color: "text-blue-600", icon: "clock", iconColor: "text-blue-400", done, total }; diff --git a/interface/proto/forest/v1/apps.proto b/interface/proto/forest/v1/apps.proto new file mode 100644 index 0000000..131a133 --- /dev/null +++ b/interface/proto/forest/v1/apps.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; + +package forest.v1; + +import "google/protobuf/timestamp.proto"; + +service AppService { + // App lifecycle + rpc CreateApp(CreateAppRequest) returns (CreateAppResponse); + rpc GetApp(GetAppRequest) returns (GetAppResponse); + rpc ListApps(ListAppsRequest) returns (ListAppsResponse); + rpc DeleteApp(DeleteAppRequest) returns (DeleteAppResponse); + rpc SuspendApp(SuspendAppRequest) returns (SuspendAppResponse); + + // App tokens + rpc CreateAppToken(CreateAppTokenRequest) returns (CreateAppTokenResponse); + rpc ListAppTokens(ListAppTokensRequest) returns (ListAppTokensResponse); + rpc RevokeAppToken(RevokeAppTokenRequest) returns (RevokeAppTokenResponse); +} + +// ─── Core types ────────────────────────────────────────────────────── + +message App { + string app_id = 1; + string organisation_id = 2; + string name = 3; + string description = 4; + repeated string permissions = 5; + bool suspended = 6; + google.protobuf.Timestamp created_at = 7; +} + +message AppToken { + string token_id = 1; + string name = 2; + google.protobuf.Timestamp expires_at = 3; + google.protobuf.Timestamp last_used = 4; + bool revoked = 5; + google.protobuf.Timestamp created_at = 6; +} + +// ─── App lifecycle ─────────────────────────────────────────────────── + +message CreateAppRequest { + string organisation_id = 1; + string name = 2; + string description = 3; + repeated string permissions = 4; +} + +message CreateAppResponse { + App app = 1; +} + +message GetAppRequest { + string app_id = 1; +} + +message GetAppResponse { + App app = 1; +} + +message ListAppsRequest { + string organisation_id = 1; +} + +message ListAppsResponse { + repeated App apps = 1; +} + +message DeleteAppRequest { + string app_id = 1; +} + +message DeleteAppResponse {} + +message SuspendAppRequest { + string app_id = 1; + bool suspended = 2; +} + +message SuspendAppResponse {} + +// ─── App tokens ────────────────────────────────────────────────────── + +message CreateAppTokenRequest { + string app_id = 1; + string name = 2; + int64 expires_in_seconds = 3; // 0 = no expiry +} + +message CreateAppTokenResponse { + AppToken token = 1; + string raw_token = 2; // only returned on creation +} + +message ListAppTokensRequest { + string app_id = 1; +} + +message ListAppTokensResponse { + repeated AppToken tokens = 1; +} + +message RevokeAppTokenRequest { + string token_id = 1; +} + +message RevokeAppTokenResponse {} diff --git a/interface/proto/forest/v1/artifacts.proto b/interface/proto/forest/v1/artifacts.proto new file mode 100644 index 0000000..c2001d6 --- /dev/null +++ b/interface/proto/forest/v1/artifacts.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +package forest.v1; + +message BeginUploadArtifactRequest {} +message BeginUploadArtifactResponse { + string upload_id = 1; +} + +message UploadArtifactRequest { + string upload_id = 1; + + string env = 2; + string destination = 3; + + string file_name = 4; + string file_content = 5; + + // Category of the file: "deployment" (default), "spec", or "attachment" + string category = 6; +} +message UploadArtifactResponse {} + +message CommitArtifactRequest{ + string upload_id = 1; +} +message CommitArtifactResponse { + string artifact_id = 1; +} + +message GetArtifactFilesRequest { + // The artifact_id (UUID from annotations/artifacts table) + string artifact_id = 1; + // Optional filter: "deployment", "spec", "attachment". Empty = all categories. + optional string category = 2; +} +message GetArtifactFilesResponse { + repeated ArtifactFile files = 1; +} +message ArtifactFile { + string file_name = 1; + string category = 2; + string env = 3; + string destination = 4; + string content = 5; +} + +message GetArtifactSpecRequest { + string artifact_id = 1; +} +message GetArtifactSpecResponse { + // The spec file content (forest.cue), empty string if no spec was uploaded. + string content = 1; +} + +service ArtifactService { + rpc BeginUploadArtifact(BeginUploadArtifactRequest) returns (BeginUploadArtifactResponse); + rpc UploadArtifact(stream UploadArtifactRequest) returns (UploadArtifactResponse); + rpc CommitArtifact(CommitArtifactRequest) returns (CommitArtifactResponse); + rpc GetArtifactFiles(GetArtifactFilesRequest) returns (GetArtifactFilesResponse); + rpc GetArtifactSpec(GetArtifactSpecRequest) returns (GetArtifactSpecResponse); +} diff --git a/interface/proto/forest/v1/events.proto b/interface/proto/forest/v1/events.proto new file mode 100644 index 0000000..6655465 --- /dev/null +++ b/interface/proto/forest/v1/events.proto @@ -0,0 +1,119 @@ +syntax = "proto3"; + +package forest.v1; + +// ── Event streaming ─────────────────────────────────────────────── + +service EventService { + // Ephemeral server-streaming subscription. Client manages its own cursor. + rpc Subscribe(SubscribeEventsRequest) returns (stream OrgEvent); + + // Durable subscription: resumes from the subscription's persisted cursor. + // Events are streamed, and the cursor is advanced as events are sent. + // Client should call AcknowledgeEvents to confirm processing. + rpc SubscribeDurable(SubscribeDurableRequest) returns (stream OrgEvent); + + // Acknowledge that events up to (and including) the given sequence have + // been processed. Advances the subscription's cursor. Idempotent. + rpc AcknowledgeEvents(AcknowledgeEventsRequest) returns (AcknowledgeEventsResponse); +} + +message SubscribeEventsRequest { + string organisation = 1; + string project = 2; // optional — empty means all projects in org + repeated string resource_types = 3; // optional filter: "release", "destination", etc. + repeated string actions = 4; // optional filter: "created", "updated", etc. + int64 since_sequence = 5; // 0 = latest only, >0 = replay from that sequence +} + +message SubscribeDurableRequest { + string organisation = 1; + string subscription_name = 2; // the registered subscription name +} + +message AcknowledgeEventsRequest { + string organisation = 1; + string subscription_name = 2; + int64 sequence = 3; // advance cursor to this sequence +} + +message AcknowledgeEventsResponse { + int64 cursor = 1; // the new cursor value +} + +message OrgEvent { + int64 sequence = 1; // monotonic cursor — client stores this for reconnect + string event_id = 2; // UUID, dedup key + string timestamp = 3; // RFC 3339 + string organisation = 4; + string project = 5; // empty for org-level events + string resource_type = 6; // "release", "destination", "environment", "pipeline", "artifact", "policy", "app", "organisation" + string action = 7; // "created", "updated", "deleted", "status_changed" + string resource_id = 8; // ID of the changed resource + map metadata = 9; // lightweight context (e.g. "status" → "SUCCEEDED") +} + +// ── Subscription management ─────────────────────────────────────── + +service EventSubscriptionService { + rpc CreateEventSubscription(CreateEventSubscriptionRequest) returns (CreateEventSubscriptionResponse); + rpc UpdateEventSubscription(UpdateEventSubscriptionRequest) returns (UpdateEventSubscriptionResponse); + rpc DeleteEventSubscription(DeleteEventSubscriptionRequest) returns (DeleteEventSubscriptionResponse); + rpc ListEventSubscriptions(ListEventSubscriptionsRequest) returns (ListEventSubscriptionsResponse); +} + +message EventSubscription { + string id = 1; + string organisation = 2; + string name = 3; + repeated string resource_types = 4; + repeated string actions = 5; + repeated string projects = 6; + string status = 7; // "active", "paused" + int64 cursor = 8; // last acknowledged sequence + string created_at = 9; + string updated_at = 10; +} + +message CreateEventSubscriptionRequest { + string organisation = 1; + string name = 2; + repeated string resource_types = 3; // empty = all + repeated string actions = 4; // empty = all + repeated string projects = 5; // empty = all projects in org +} + +message CreateEventSubscriptionResponse { + EventSubscription subscription = 1; +} + +message UpdateEventSubscriptionRequest { + string organisation = 1; + string name = 2; + optional string status = 3; // "active" or "paused" + // To update filters, set update_filters = true and provide new values. + // Empty arrays mean "all" (no filter). + bool update_filters = 4; + repeated string resource_types = 5; + repeated string actions = 6; + repeated string projects = 7; +} + +message UpdateEventSubscriptionResponse { + EventSubscription subscription = 1; +} + +message DeleteEventSubscriptionRequest { + string organisation = 1; + string name = 2; +} + +message DeleteEventSubscriptionResponse {} + +message ListEventSubscriptionsRequest { + string organisation = 1; +} + +message ListEventSubscriptionsResponse { + repeated EventSubscription subscriptions = 1; +} diff --git a/interface/proto/forest/v1/forage.proto b/interface/proto/forest/v1/forage.proto new file mode 100644 index 0000000..9f9ea03 --- /dev/null +++ b/interface/proto/forest/v1/forage.proto @@ -0,0 +1,864 @@ +syntax = "proto3"; + +package forest.v1; + +import "google/protobuf/duration.proto"; + +// --------------------------------------------------------------------------- +// ForageService — the control plane RPC surface that forest-server uses to +// drive deployments against a forage cluster. The scheduler calls +// ApplyResources with the full desired-state bundle; forage reconciles. +// --------------------------------------------------------------------------- +service ForageService { + // Apply a batch of resources (create / update / delete). + // This is the main entry-point used by the forage/containers@1 destination. + rpc ApplyResources(ApplyResourcesRequest) returns (ApplyResourcesResponse); + + // Poll / stream the rollout status of a previous apply. + rpc WatchRollout(WatchRolloutRequest) returns (stream RolloutEvent); + + // Tear down all resources associated with a release / project. + rpc DeleteResources(DeleteResourcesRequest) returns (DeleteResourcesResponse); +} + +// --------------------------------------------------------------------------- +// Apply +// --------------------------------------------------------------------------- +message ApplyResourcesRequest { + // Caller-chosen idempotency key (release_state id works well). + string apply_id = 1; + + // Namespace / tenant isolation — maps to the forest organisation. + string namespace = 2; + + // The ordered list of resources to reconcile. Forage processes them in + // order so that dependencies (e.g. Service before HTTPRoute) are met. + repeated ForageResource resources = 3; + + // Labels propagated to every resource for bookkeeping. + map labels = 4; +} + +message ApplyResourcesResponse { + // Server-generated rollout id for status tracking. + string rollout_id = 1; +} + +message WatchRolloutRequest { + string rollout_id = 1; +} + +message RolloutEvent { + string resource_name = 1; + string resource_kind = 2; + RolloutStatus status = 3; + string message = 4; +} + +enum RolloutStatus { + ROLLOUT_STATUS_UNSPECIFIED = 0; + ROLLOUT_STATUS_PENDING = 1; + ROLLOUT_STATUS_IN_PROGRESS = 2; + ROLLOUT_STATUS_SUCCEEDED = 3; + ROLLOUT_STATUS_FAILED = 4; + ROLLOUT_STATUS_ROLLED_BACK = 5; +} + +message DeleteResourcesRequest { + string namespace = 1; + // Selector labels — all resources matching these labels are removed. + map labels = 2; +} + +message DeleteResourcesResponse {} + +// =========================================================================== +// Resource envelope — every item in the apply list is one of these. +// =========================================================================== +message ForageResource { + // Unique name within the namespace (e.g. "my-api", "my-api-worker"). + string name = 1; + + oneof spec { + ContainerServiceSpec container_service = 10; + ServiceSpec service = 11; + RouteSpec route = 12; + CronJobSpec cron_job = 13; + JobSpec job = 14; + } +} + +// =========================================================================== +// ContainerServiceSpec — the primary workload. +// Combines the concerns of Deployment + Pod in a single cohesive spec. +// =========================================================================== +message ContainerServiceSpec { + // ---- Scheduling & scaling ------------------------------------------------ + ScalingPolicy scaling = 1; + + // ---- Pod-level settings -------------------------------------------------- + // Main application container (exactly one required). + Container container = 2; + + // Optional sidecar containers that share the pod network. + repeated Container sidecars = 3; + + // Init containers run sequentially before the main container starts. + repeated Container init_containers = 4; + + // ---- Volumes available to all containers in the pod ---------------------- + repeated Volume volumes = 5; + + // ---- Update strategy ----------------------------------------------------- + UpdateStrategy update_strategy = 6; + + // ---- Pod-level configuration --------------------------------------------- + PodConfig pod_config = 7; +} + +// --------------------------------------------------------------------------- +// Container — describes a single OCI container. +// --------------------------------------------------------------------------- +message Container { + // Human-readable name (must be unique within the pod). + string name = 1; + + // OCI image reference, e.g. "registry.forage.sh/org/app:v1.2.3". + string image = 2; + + // Override the image entrypoint. + repeated string command = 3; + + // Arguments passed to the entrypoint. + repeated string args = 4; + + // Working directory inside the container. + string working_dir = 5; + + // Environment variables — static values and references. + repeated EnvVar env = 6; + + // Ports the container listens on. + repeated ContainerPort ports = 7; + + // Resource requests and limits. + ResourceRequirements resources = 8; + + // Volume mounts into this container's filesystem. + repeated VolumeMount volume_mounts = 9; + + // Health probes. + Probe liveness_probe = 10; + Probe readiness_probe = 11; + Probe startup_probe = 12; + + // Lifecycle hooks. + Lifecycle lifecycle = 13; + + // Security context for this container. + ContainerSecurityContext security_context = 14; + + // Image pull policy: "Always", "IfNotPresent", "Never". + string image_pull_policy = 15; + + // Whether stdin / tty are allocated (usually false for services). + bool stdin = 16; + bool tty = 17; +} + +// --------------------------------------------------------------------------- +// Environment variables +// --------------------------------------------------------------------------- +message EnvVar { + string name = 1; + + oneof value_source { + // Literal value. + string value = 2; + + // Reference to a secret key. + SecretKeyRef secret_ref = 3; + + // Reference to a config-map key. + ConfigKeyRef config_ref = 4; + + // Downward-API field (e.g. "metadata.name", "status.podIP"). + string field_ref = 5; + + // Resource field (e.g. "limits.cpu"). + string resource_field_ref = 6; + } +} + +message SecretKeyRef { + string secret_name = 1; + string key = 2; +} + +message ConfigKeyRef { + string config_name = 1; + string key = 2; +} + +// --------------------------------------------------------------------------- +// Ports +// --------------------------------------------------------------------------- +message ContainerPort { + // Friendly name (e.g. "http", "grpc", "metrics"). + string name = 1; + + // The port number inside the container. + uint32 container_port = 2; + + // Protocol: TCP (default), UDP, SCTP. + string protocol = 3; +} + +// --------------------------------------------------------------------------- +// Resources +// --------------------------------------------------------------------------- +message ResourceRequirements { + ResourceList requests = 1; + ResourceList limits = 2; +} + +message ResourceList { + // CPU in Kubernetes quantity format: "100m", "0.5", "2". + string cpu = 1; + + // Memory in Kubernetes quantity format: "128Mi", "1Gi". + string memory = 2; + + // Ephemeral storage: "1Gi". + string ephemeral_storage = 3; + + // GPU / accelerator requests (e.g. "nvidia.com/gpu": "1"). + map extended = 4; +} + +// --------------------------------------------------------------------------- +// Volumes & mounts +// --------------------------------------------------------------------------- +message Volume { + // Volume name referenced by VolumeMount.name. + string name = 1; + + oneof source { + EmptyDirVolume empty_dir = 10; + SecretVolume secret = 11; + ConfigMapVolume config_map = 12; + PVCVolume pvc = 13; + HostPathVolume host_path = 14; + NfsVolume nfs = 15; + } +} + +message EmptyDirVolume { + // "Memory" for tmpfs, empty for node disk. + string medium = 1; + + // Size limit (e.g. "256Mi"). Empty means node default. + string size_limit = 2; +} + +message SecretVolume { + string secret_name = 1; + // Optional: mount only specific keys. + repeated KeyToPath items = 2; + // Octal file mode (e.g. 0644). Default 0644. + uint32 default_mode = 3; + bool optional = 4; +} + +message ConfigMapVolume { + string config_map_name = 1; + repeated KeyToPath items = 2; + uint32 default_mode = 3; + bool optional = 4; +} + +message KeyToPath { + string key = 1; + string path = 2; + uint32 mode = 3; +} + +message PVCVolume { + string claim_name = 1; + bool read_only = 2; +} + +message HostPathVolume { + string path = 1; + // "Directory", "File", "DirectoryOrCreate", "FileOrCreate", etc. + string type = 2; +} + +message NfsVolume { + string server = 1; + string path = 2; + bool read_only = 3; +} + +message VolumeMount { + // Must match a Volume.name. + string name = 1; + + // Absolute path inside the container. + string mount_path = 2; + + // Optional sub-path within the volume. + string sub_path = 3; + + bool read_only = 4; +} + +// --------------------------------------------------------------------------- +// Probes +// --------------------------------------------------------------------------- +message Probe { + oneof handler { + HttpGetProbe http_get = 1; + TcpSocketProbe tcp_socket = 2; + ExecProbe exec = 3; + GrpcProbe grpc = 4; + } + + uint32 initial_delay_seconds = 10; + uint32 period_seconds = 11; + uint32 timeout_seconds = 12; + uint32 success_threshold = 13; + uint32 failure_threshold = 14; +} + +message HttpGetProbe { + string path = 1; + uint32 port = 2; + string scheme = 3; // "HTTP" or "HTTPS" + repeated HttpHeader http_headers = 4; +} + +message HttpHeader { + string name = 1; + string value = 2; +} + +message TcpSocketProbe { + uint32 port = 1; +} + +message ExecProbe { + repeated string command = 1; +} + +message GrpcProbe { + uint32 port = 1; + string service = 2; +} + +// --------------------------------------------------------------------------- +// Lifecycle hooks +// --------------------------------------------------------------------------- +message Lifecycle { + LifecycleHandler post_start = 1; + LifecycleHandler pre_stop = 2; +} + +message LifecycleHandler { + oneof action { + ExecProbe exec = 1; + HttpGetProbe http_get = 2; + TcpSocketProbe tcp_socket = 3; + } +} + +// --------------------------------------------------------------------------- +// Security +// --------------------------------------------------------------------------- +message ContainerSecurityContext { + bool run_as_non_root = 1; + int64 run_as_user = 2; + int64 run_as_group = 3; + bool read_only_root_filesystem = 4; + bool privileged = 5; + bool allow_privilege_escalation = 6; + + Capabilities capabilities = 7; + + // SELinux options (optional). + string se_linux_type = 8; + + // Seccomp profile: "RuntimeDefault", "Unconfined", or a localhost path. + string seccomp_profile = 9; +} + +message Capabilities { + repeated string add = 1; + repeated string drop = 2; +} + +message PodSecurityContext { + int64 run_as_user = 1; + int64 run_as_group = 2; + bool run_as_non_root = 3; + int64 fs_group = 4; + + // Supplemental groups for all containers. + repeated int64 supplemental_groups = 5; + + // "OnRootMismatch" or "Always". + string fs_group_change_policy = 6; + + string seccomp_profile = 7; +} + +// --------------------------------------------------------------------------- +// Scaling +// --------------------------------------------------------------------------- +message ScalingPolicy { + // Fixed replica count (used when autoscaling is not configured). + uint32 replicas = 1; + + // Optional horizontal autoscaler. + AutoscalingPolicy autoscaling = 2; +} + +message AutoscalingPolicy { + uint32 min_replicas = 1; + uint32 max_replicas = 2; + + // Target average CPU utilisation percentage (e.g. 70). + uint32 target_cpu_utilization_percent = 3; + + // Target average memory utilisation percentage. + uint32 target_memory_utilization_percent = 4; + + // Custom metrics (e.g. queue depth, RPS). + repeated CustomMetric custom_metrics = 5; + + // Scale-down stabilisation window. + google.protobuf.Duration scale_down_stabilization = 6; +} + +message CustomMetric { + // Metric name as exposed by the metrics adapter. + string name = 1; + + // One of "Value", "AverageValue", "Utilization". + string target_type = 2; + + // Target threshold (interpretation depends on target_type). + string target_value = 3; +} + +// --------------------------------------------------------------------------- +// Update strategy +// --------------------------------------------------------------------------- +message UpdateStrategy { + // "RollingUpdate" (default) or "Recreate". + string type = 1; + + RollingUpdateConfig rolling_update = 2; +} + +message RollingUpdateConfig { + // Absolute number or percentage (e.g. "1", "25%"). + string max_unavailable = 1; + string max_surge = 2; +} + +// --------------------------------------------------------------------------- +// Pod-level configuration +// --------------------------------------------------------------------------- +message PodConfig { + // Service account name for RBAC / workload identity. + string service_account_name = 1; + + // Restart policy: "Always" (default for services), "OnFailure", "Never". + string restart_policy = 2; + + // Graceful shutdown window. + uint32 termination_grace_period_seconds = 3; + + // DNS policy: "ClusterFirst" (default), "Default", "None". + string dns_policy = 4; + PodDnsConfig dns_config = 5; + + // Host networking (rare, but needed for some infra workloads). + bool host_network = 6; + + // Node scheduling. + map node_selector = 7; + repeated Toleration tolerations = 8; + Affinity affinity = 9; + + // Topology spread constraints for HA. + repeated TopologySpreadConstraint topology_spread_constraints = 10; + + // Image pull secrets. + repeated string image_pull_secrets = 11; + + // Pod-level security context. + PodSecurityContext security_context = 12; + + // Priority class name for preemption. + string priority_class_name = 13; + + // Runtime class (e.g. "gvisor", "kata"). + string runtime_class_name = 14; + + // Annotations passed to the pod template (not the workload resource). + map annotations = 15; + + // Labels passed to the pod template. + map labels = 16; +} + +message PodDnsConfig { + repeated string nameservers = 1; + repeated string searches = 2; + repeated DnsOption options = 3; +} + +message DnsOption { + string name = 1; + string value = 2; +} + +message Toleration { + string key = 1; + // "Equal" or "Exists". + string operator = 2; + string value = 3; + // "NoSchedule", "PreferNoSchedule", "NoExecute". + string effect = 4; + // Toleration seconds for NoExecute. + int64 toleration_seconds = 5; +} + +message Affinity { + NodeAffinity node_affinity = 1; + PodAffinity pod_affinity = 2; + PodAntiAffinity pod_anti_affinity = 3; +} + +message NodeAffinity { + repeated PreferredSchedulingTerm preferred = 1; + NodeSelector required = 2; +} + +message PreferredSchedulingTerm { + int32 weight = 1; + NodeSelectorTerm preference = 2; +} + +message NodeSelector { + repeated NodeSelectorTerm terms = 1; +} + +message NodeSelectorTerm { + repeated NodeSelectorRequirement match_expressions = 1; + repeated NodeSelectorRequirement match_fields = 2; +} + +message NodeSelectorRequirement { + string key = 1; + // "In", "NotIn", "Exists", "DoesNotExist", "Gt", "Lt". + string operator = 2; + repeated string values = 3; +} + +message PodAffinity { + repeated WeightedPodAffinityTerm preferred = 1; + repeated PodAffinityTerm required = 2; +} + +message PodAntiAffinity { + repeated WeightedPodAffinityTerm preferred = 1; + repeated PodAffinityTerm required = 2; +} + +message WeightedPodAffinityTerm { + int32 weight = 1; + PodAffinityTerm term = 2; +} + +message PodAffinityTerm { + LabelSelector label_selector = 1; + string topology_key = 2; + repeated string namespaces = 3; +} + +message LabelSelector { + map match_labels = 1; + repeated LabelSelectorRequirement match_expressions = 2; +} + +message LabelSelectorRequirement { + string key = 1; + // "In", "NotIn", "Exists", "DoesNotExist". + string operator = 2; + repeated string values = 3; +} + +message TopologySpreadConstraint { + // Max difference in spread (e.g. 1 for even distribution). + int32 max_skew = 1; + + // "zone", "hostname", or any node label. + string topology_key = 2; + + // "DoNotSchedule" or "ScheduleAnyway". + string when_unsatisfiable = 3; + + LabelSelector label_selector = 4; +} + +// =========================================================================== +// ServiceSpec — L4 load balancing & service discovery. +// Combines Service + optional gateway route into one resource when desired. +// =========================================================================== +message ServiceSpec { + // The ContainerServiceSpec name this service fronts. + string target = 1; + + // Service type: "ClusterIP" (default), "NodePort", "LoadBalancer", "Headless". + string type = 2; + + repeated ServicePort ports = 3; + + // Session affinity: "None" (default), "ClientIP". + string session_affinity = 4; + + // Optional: expose this service externally via the gateway. + // Setting this is equivalent to creating a separate RouteSpec. + // Allows combining Service + Route into one resource for simpler configs. + InlineRoute inline_route = 5; + + // Extra annotations on the Service object (e.g. cloud LB configs). + map annotations = 6; +} + +message ServicePort { + string name = 1; + uint32 port = 2; + uint32 target_port = 3; + string protocol = 4; // TCP, UDP, SCTP + // Only for NodePort type. + uint32 node_port = 5; +} + +message InlineRoute { + // Hostname(s) to match (e.g. "api.example.com"). + repeated string hostnames = 1; + + // Path matching rules. If empty, matches all paths to the first port. + repeated RouteRule rules = 2; + + // TLS configuration. + RouteTls tls = 3; +} + +// =========================================================================== +// RouteSpec — Gateway API HTTPRoute (standalone). +// Use this when you need routing rules separate from the service definition. +// =========================================================================== +message RouteSpec { + // The ServiceSpec name this route targets. + string target_service = 1; + + // Hostname(s) this route matches. + repeated string hostnames = 2; + + // Matching & routing rules. + repeated RouteRule rules = 3; + + // TLS termination config. + RouteTls tls = 4; + + // Which gateway to attach to (empty = cluster default). + string gateway_ref = 5; + + // Route priority / ordering. + int32 priority = 6; +} + +message RouteRule { + // Path matching. + repeated RouteMatch matches = 1; + + // Backend(s) traffic is sent to. + repeated RouteBackend backends = 2; + + // Request / response filters applied to this rule. + repeated RouteFilter filters = 3; + + // Timeout for the entire request. + google.protobuf.Duration timeout = 4; +} + +message RouteMatch { + // Path match. + PathMatch path = 1; + + // Header conditions. + repeated HeaderMatch headers = 2; + + // Query parameter conditions. + repeated QueryParamMatch query_params = 3; + + // HTTP method constraint. + string method = 4; +} + +message PathMatch { + // "Exact", "PathPrefix" (default), "RegularExpression". + string type = 1; + string value = 2; +} + +message HeaderMatch { + // "Exact" (default), "RegularExpression". + string type = 1; + string name = 2; + string value = 3; +} + +message QueryParamMatch { + string type = 1; + string name = 2; + string value = 3; +} + +message RouteBackend { + // Service name. + string service = 1; + // Port on the backend service. + uint32 port = 2; + // Traffic weight for canary / blue-green (1-100). + uint32 weight = 3; +} + +message RouteFilter { + oneof filter { + RequestHeaderModifier request_header_modifier = 1; + ResponseHeaderModifier response_header_modifier = 2; + RequestRedirect request_redirect = 3; + UrlRewrite url_rewrite = 4; + RequestMirror request_mirror = 5; + } +} + +message RequestHeaderModifier { + map set = 1; + map add = 2; + repeated string remove = 3; +} + +message ResponseHeaderModifier { + map set = 1; + map add = 2; + repeated string remove = 3; +} + +message RequestRedirect { + string scheme = 1; + string hostname = 2; + uint32 port = 3; + string path = 4; + uint32 status_code = 5; // 301, 302, etc. +} + +message UrlRewrite { + string hostname = 1; + PathMatch path = 2; +} + +message RequestMirror { + string service = 1; + uint32 port = 2; +} + +message RouteTls { + // "Terminate" (default) or "Passthrough". + string mode = 1; + + // Secret name containing the TLS certificate. + string certificate_ref = 2; +} + +// =========================================================================== +// CronJobSpec — scheduled workload. +// =========================================================================== +message CronJobSpec { + // Cron schedule (e.g. "*/5 * * * *"). + string schedule = 1; + + // Timezone (e.g. "Europe/Copenhagen"). Empty = UTC. + string timezone = 2; + + // Container that runs the job. + Container container = 3; + + // Volumes for the job pod. + repeated Volume volumes = 4; + + // Job-level config. + JobConfig job_config = 5; + + // Pod-level config (node selector, tolerations, etc.). + PodConfig pod_config = 6; + + // "Allow", "Forbid", "Replace". + string concurrency_policy = 7; + + // Number of successful/failed jobs to retain. + uint32 successful_jobs_history_limit = 8; + uint32 failed_jobs_history_limit = 9; + + // Suspend the cron schedule. + bool suspend = 10; + + // Deadline in seconds for starting the job if it missed its schedule. + int64 starting_deadline_seconds = 11; +} + +// =========================================================================== +// JobSpec — one-shot workload. +// =========================================================================== +message JobSpec { + // Container that runs the job. + Container container = 1; + + // Volumes for the job pod. + repeated Volume volumes = 2; + + // Job-level config. + JobConfig job_config = 3; + + // Pod-level config. + PodConfig pod_config = 4; +} + +message JobConfig { + // Number of times the job should complete successfully. + uint32 completions = 1; + + // Max parallel pods. + uint32 parallelism = 2; + + // "NonIndexed" (default) or "Indexed". + string completion_mode = 3; + + // Number of retries before marking failed. + uint32 backoff_limit = 4; + + // Active deadline (seconds) — job killed if it runs longer. + int64 active_deadline_seconds = 5; + + // TTL after finished (seconds) — auto-cleanup. + int64 ttl_seconds_after_finished = 6; + + // Restart policy: "OnFailure" (default) or "Never". + string restart_policy = 7; +} diff --git a/interface/proto/forest/v1/health.proto b/interface/proto/forest/v1/health.proto new file mode 100644 index 0000000..58101e1 --- /dev/null +++ b/interface/proto/forest/v1/health.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package forest.v1; + +service StatusService { + rpc Status(GetStatusRequest) returns (GetStatusResponse) {} +} + +message GetStatusRequest {} +message GetStatusResponse {} diff --git a/interface/proto/forest/v1/notifications.proto b/interface/proto/forest/v1/notifications.proto new file mode 100644 index 0000000..aa51551 --- /dev/null +++ b/interface/proto/forest/v1/notifications.proto @@ -0,0 +1,98 @@ +syntax = "proto3"; + +package forest.v1; + +enum NotificationType { + NOTIFICATION_TYPE_UNSPECIFIED = 0; + NOTIFICATION_TYPE_RELEASE_ANNOTATED = 1; + NOTIFICATION_TYPE_RELEASE_STARTED = 2; + NOTIFICATION_TYPE_RELEASE_SUCCEEDED = 3; + NOTIFICATION_TYPE_RELEASE_FAILED = 4; +} + +enum NotificationChannel { + NOTIFICATION_CHANNEL_UNSPECIFIED = 0; + NOTIFICATION_CHANNEL_CLI = 1; + NOTIFICATION_CHANNEL_SLACK = 2; +} + +// Rich context about the release that triggered the notification. +// Integrations decide which fields to use. +message ReleaseContext { + string slug = 1; + string organisation = 2; + string project = 3; + string artifact_id = 4; + string release_intent_id = 5; + string destination = 6; + string environment = 7; + // Source info + string source_username = 8; + string source_email = 9; + string source_user_id = 17; + // Git ref + string commit_sha = 10; + string commit_branch = 11; + // Artifact context + string context_title = 12; + string context_description = 13; + string context_web = 14; + // Error info (populated on failure) + string error_message = 15; + // Number of destinations involved + int32 destination_count = 16; +} + +message Notification { + string id = 1; + NotificationType notification_type = 2; + string title = 3; + string body = 4; + string organisation = 5; + string project = 6; + ReleaseContext release_context = 7; + string created_at = 8; +} + +message NotificationPreference { + NotificationType notification_type = 1; + NotificationChannel channel = 2; + bool enabled = 3; +} + +message GetNotificationPreferencesRequest {} +message GetNotificationPreferencesResponse { + repeated NotificationPreference preferences = 1; +} + +message SetNotificationPreferenceRequest { + NotificationType notification_type = 1; + NotificationChannel channel = 2; + bool enabled = 3; +} +message SetNotificationPreferenceResponse { + NotificationPreference preference = 1; +} + +message ListenNotificationsRequest { + optional string organisation = 1; + optional string project = 2; +} + +message ListNotificationsRequest { + int32 page_size = 1; + string page_token = 2; + optional string organisation = 3; + optional string project = 4; +} +message ListNotificationsResponse { + repeated Notification notifications = 1; + string next_page_token = 2; +} + +service NotificationService { + rpc GetNotificationPreferences(GetNotificationPreferencesRequest) returns (GetNotificationPreferencesResponse); + rpc SetNotificationPreference(SetNotificationPreferenceRequest) returns (SetNotificationPreferenceResponse); + rpc ListenNotifications(ListenNotificationsRequest) returns (stream Notification); + rpc ListNotifications(ListNotificationsRequest) returns (ListNotificationsResponse); +} diff --git a/interface/proto/forest/v1/policies.proto b/interface/proto/forest/v1/policies.proto new file mode 100644 index 0000000..509ad5e --- /dev/null +++ b/interface/proto/forest/v1/policies.proto @@ -0,0 +1,178 @@ +syntax = "proto3"; + +package forest.v1; + +import "forest/v1/releases.proto"; + +// ── Policy types ──────────────────────────────────────────────────── + +enum PolicyType { + POLICY_TYPE_UNSPECIFIED = 0; + POLICY_TYPE_SOAK_TIME = 1; + POLICY_TYPE_BRANCH_RESTRICTION = 2; + POLICY_TYPE_EXTERNAL_APPROVAL = 3; +} + +message SoakTimeConfig { + // Environment that must have a successful deploy before target is allowed + string source_environment = 1; + // Environment that is gated by this policy + string target_environment = 2; + // Seconds to wait after source environment succeeds + int64 duration_seconds = 3; +} + +message BranchRestrictionConfig { + // Environment that is restricted + string target_environment = 1; + // Regex that source branch must match + string branch_pattern = 2; +} + +message ExternalApprovalConfig { + string target_environment = 1; + int32 required_approvals = 2; +} + +// ── External approval state ───────────────────────────────────────── + +message ExternalApprovalState { + int32 required_approvals = 1; + int32 current_approvals = 2; + repeated ExternalApprovalDecisionEntry decisions = 3; +} + +message ExternalApprovalDecisionEntry { + string user_id = 1; + string username = 2; + string decision = 3; + string decided_at = 4; + optional string comment = 5; +} + +// ── Policy resource ───────────────────────────────────────────────── + +message Policy { + string id = 1; + string name = 2; + bool enabled = 3; + PolicyType policy_type = 4; + + oneof config { + SoakTimeConfig soak_time = 10; + BranchRestrictionConfig branch_restriction = 11; + ExternalApprovalConfig external_approval = 12; + } + + string created_at = 20; + string updated_at = 21; +} + +// ── Policy evaluation result ──────────────────────────────────────── + +message PolicyEvaluation { + string policy_name = 1; + PolicyType policy_type = 2; + bool passed = 3; + // Human-readable explanation when blocked + string reason = 4; + optional ExternalApprovalState approval_state = 10; +} + +// ── CRUD messages ─────────────────────────────────────────────────── + +message CreatePolicyRequest { + Project project = 1; + string name = 2; + PolicyType policy_type = 3; + oneof config { + SoakTimeConfig soak_time = 10; + BranchRestrictionConfig branch_restriction = 11; + ExternalApprovalConfig external_approval = 12; + } +} +message CreatePolicyResponse { + Policy policy = 1; +} + +message UpdatePolicyRequest { + Project project = 1; + string name = 2; + optional bool enabled = 3; + oneof config { + SoakTimeConfig soak_time = 10; + BranchRestrictionConfig branch_restriction = 11; + ExternalApprovalConfig external_approval = 12; + } +} +message UpdatePolicyResponse { + Policy policy = 1; +} + +message DeletePolicyRequest { + Project project = 1; + string name = 2; +} +message DeletePolicyResponse {} + +message ListPoliciesRequest { + Project project = 1; +} +message ListPoliciesResponse { + repeated Policy policies = 1; +} + +message EvaluatePoliciesRequest { + Project project = 1; + string target_environment = 2; + // For branch restriction checks + optional string branch = 3; + optional string release_intent_id = 4; +} +message EvaluatePoliciesResponse { + repeated PolicyEvaluation evaluations = 1; + bool all_passed = 2; +} + +// ── External approval RPC messages ────────────────────────────────── + +message ExternalApproveReleaseRequest { + Project project = 1; + string release_intent_id = 2; + string target_environment = 3; + optional string comment = 4; + bool force_bypass = 5; +} +message ExternalApproveReleaseResponse { + ExternalApprovalState state = 1; +} + +message ExternalRejectReleaseRequest { + Project project = 1; + string release_intent_id = 2; + string target_environment = 3; + optional string comment = 4; +} +message ExternalRejectReleaseResponse { + ExternalApprovalState state = 1; +} + +message GetExternalApprovalStateRequest { + Project project = 1; + string release_intent_id = 2; + string target_environment = 3; +} +message GetExternalApprovalStateResponse { + ExternalApprovalState state = 1; +} + +service PolicyService { + rpc CreatePolicy(CreatePolicyRequest) returns (CreatePolicyResponse); + rpc UpdatePolicy(UpdatePolicyRequest) returns (UpdatePolicyResponse); + rpc DeletePolicy(DeletePolicyRequest) returns (DeletePolicyResponse); + rpc ListPolicies(ListPoliciesRequest) returns (ListPoliciesResponse); + rpc EvaluatePolicies(EvaluatePoliciesRequest) returns (EvaluatePoliciesResponse); + rpc ExternalApproveRelease(ExternalApproveReleaseRequest) returns (ExternalApproveReleaseResponse); + rpc ExternalRejectRelease(ExternalRejectReleaseRequest) returns (ExternalRejectReleaseResponse); + rpc GetExternalApprovalState(GetExternalApprovalStateRequest) returns (GetExternalApprovalStateResponse); +} diff --git a/interface/proto/forest/v1/registry.proto b/interface/proto/forest/v1/registry.proto new file mode 100644 index 0000000..d9e3451 --- /dev/null +++ b/interface/proto/forest/v1/registry.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package forest.v1; + +service RegistryService { + rpc GetComponents(GetComponentsRequest) returns (GetComponentsResponse) {} + rpc GetComponent(GetComponentRequest) returns (GetComponentResponse) {} + rpc GetComponentVersion(GetComponentVersionRequest) returns (GetComponentVersionResponse) {} + rpc BeginUpload(BeginUploadRequest) returns (BeginUploadResponse) {} + rpc UploadFile(UploadFileRequest) returns (UploadFileResponse) {} + rpc CommitUpload(CommitUploadRequest) returns (CommitUploadResponse) {} + rpc GetComponentFiles(GetComponentFilesRequest) returns (stream GetComponentFilesResponse) {} +} + +message GetComponentsRequest {} +message GetComponentsResponse {} + +message GetComponentRequest { + string name = 1; + string organisation = 2; +} +message GetComponentResponse { + optional Component component = 1; +} + +message Component { + string id = 1; + string version = 2; +} + +// ComponentVersion +message GetComponentVersionRequest { + string name = 1; + string organisation = 2; + string version = 3; +} +message GetComponentVersionResponse { + optional Component component = 1; +} + +// BeginUpload + +message BeginUploadRequest { + string name = 1; + string organisation = 2; + string version = 3; +} +message BeginUploadResponse { + string upload_context = 1; +} + +message UploadFileRequest { + string upload_context = 1; + string file_path = 2; + bytes file_content = 3; +} +message UploadFileResponse {} + +message CommitUploadRequest { + string upload_context = 1; +} +message CommitUploadResponse {} + +// Get component files +message GetComponentFilesRequest { + string component_id = 1; +} +message GetComponentFilesResponse { + oneof msg { + Done done = 1; + ComponentFile component_file = 2; + } +} + +message ComponentFile { + string file_path = 1; + bytes file_content = 2; +} + +message Done {} diff --git a/interface/proto/forest/v1/release_pipelines.proto b/interface/proto/forest/v1/release_pipelines.proto index edacc03..df62af7 100644 --- a/interface/proto/forest/v1/release_pipelines.proto +++ b/interface/proto/forest/v1/release_pipelines.proto @@ -10,6 +10,7 @@ enum StageType { STAGE_TYPE_UNSPECIFIED = 0; STAGE_TYPE_DEPLOY = 1; STAGE_TYPE_WAIT = 2; + STAGE_TYPE_PLAN = 3; } // ── Per-type config messages ───────────────────────────────────────── @@ -22,6 +23,11 @@ message WaitStageConfig { int64 duration_seconds = 1; } +message PlanStageConfig { + string environment = 1; + bool auto_approve = 2; +} + // ── A single pipeline stage ────────────────────────────────────────── message PipelineStage { @@ -31,6 +37,7 @@ message PipelineStage { oneof config { DeployStageConfig deploy = 10; WaitStageConfig wait = 11; + PlanStageConfig plan = 12; } } @@ -43,6 +50,7 @@ enum PipelineStageStatus { PIPELINE_STAGE_STATUS_SUCCEEDED = 3; PIPELINE_STAGE_STATUS_FAILED = 4; PIPELINE_STAGE_STATUS_CANCELLED = 5; + PIPELINE_STAGE_STATUS_AWAITING_APPROVAL = 6; } // ── Pipeline resource ──────────────────────────────────────────────── diff --git a/interface/proto/forest/v1/releases.proto b/interface/proto/forest/v1/releases.proto index 31c80c6..9f372b1 100644 --- a/interface/proto/forest/v1/releases.proto +++ b/interface/proto/forest/v1/releases.proto @@ -35,6 +35,8 @@ message ReleaseRequest { // When true, use the project's release pipeline (DAG) instead of // deploying directly to the specified destinations/environments. bool use_pipeline = 5; + // When true, create a plan-only pipeline (single Plan stage, no deploy). + bool prepare_only = 6; } message ReleaseResponse { // List of release intents created (one per destination) @@ -55,9 +57,23 @@ message WaitReleaseEvent { oneof event { ReleaseStatusUpdate status_update = 1; ReleaseLogLine log_line = 2; + PipelineStageUpdate stage_update = 3; } } +// Streamed in WaitRelease for pipeline releases: reports stage status changes. +message PipelineStageUpdate { + string stage_id = 1; + string stage_type = 2; // "deploy", "wait" + string status = 3; // PENDING, ACTIVE, SUCCEEDED, FAILED, CANCELLED + optional string queued_at = 4; + optional string started_at = 5; + optional string completed_at = 6; + optional string wait_until = 7; + optional string error_message = 8; + optional string approval_status = 9; +} + message ReleaseStatusUpdate { string destination = 1; string status = 2; @@ -90,6 +106,13 @@ message GetProjectsResponse { repeated string projects = 1; } +message CreateProjectRequest { + string organisation = 1; + string project = 2; +} +message CreateProjectResponse { + Project project = 1; +} message GetReleasesByActorRequest { @@ -125,6 +148,67 @@ message GetDestinationStatesRequest { message GetDestinationStatesResponse { repeated DestinationState destinations = 1; + // Active pipeline runs affecting these destinations (if any). + repeated PipelineRunState pipeline_runs = 2; +} + +// ── Release intent states (release-centric view) ───────────────────── + +message GetReleaseIntentStatesRequest { + string organisation = 1; + optional string project = 2; + // When true, also include recently completed release intents. + bool include_completed = 3; +} + +message GetReleaseIntentStatesResponse { + repeated ReleaseIntentState release_intents = 1; +} + +// Full state of a release intent: pipeline stages + individual release steps. +message ReleaseIntentState { + string release_intent_id = 1; + string artifact_id = 2; + string project = 3; + string created_at = 4; + // Pipeline stages (empty for non-pipeline releases). + repeated PipelineStageState stages = 5; + // All release_states rows for this intent (deploy steps). + repeated ReleaseStepState steps = 6; +} + +// Status of a single pipeline stage (saga coordinator view). +message PipelineStageState { + string stage_id = 1; + repeated string depends_on = 2; + PipelineRunStageType stage_type = 3; + PipelineRunStageStatus status = 4; + // Consistent timestamps for all stage types. + optional string queued_at = 5; + optional string started_at = 6; + optional string completed_at = 7; + optional string error_message = 8; + // Type-specific context. + optional string environment = 9; // deploy/plan stages + optional int64 duration_seconds = 10; // wait stages + optional string wait_until = 11; // wait stages + repeated string release_ids = 12; // deploy/plan stages: individual release IDs + optional string approval_status = 13; // plan stages: AWAITING_APPROVAL, APPROVED, REJECTED + optional bool auto_approve = 14; // plan stages +} + +// Status of a single release step (release_states row). +message ReleaseStepState { + string release_id = 1; + optional string stage_id = 2; + string destination_name = 3; + string environment = 4; + string status = 5; + optional string queued_at = 6; + optional string assigned_at = 7; + optional string started_at = 8; + optional string completed_at = 9; + optional string error_message = 10; } message DestinationState { @@ -138,6 +222,83 @@ message DestinationState { optional string queued_at = 8; optional string completed_at = 9; optional int32 queue_position = 10; + // Pipeline context: set when this release was created by a pipeline stage. + optional string release_intent_id = 11; + optional string stage_id = 12; + // When a runner was assigned to this release. + optional string assigned_at = 13; + // When the runner actually started executing. + optional string started_at = 14; +} + +// ── Pipeline run progress ──────────────────────────────────────────── + +// Snapshot of an active (or recently completed) pipeline run. +message PipelineRunState { + string release_intent_id = 1; + string artifact_id = 2; + string created_at = 3; + repeated PipelineRunStage stages = 4; +} + +// Status of a single stage within a pipeline run. +message PipelineRunStage { + string stage_id = 1; + repeated string depends_on = 2; + PipelineRunStageType stage_type = 3; + PipelineRunStageStatus status = 4; + // Type-specific context + optional string environment = 5; // deploy stages + optional int64 duration_seconds = 6; // wait stages + optional string queued_at = 7; // when dependencies were met + optional string started_at = 8; + optional string completed_at = 9; + optional string error_message = 10; + optional string wait_until = 11; + repeated string release_ids = 12; // deploy stages: individual release IDs + optional string approval_status = 13; // plan stages: AWAITING_APPROVAL, APPROVED, REJECTED + optional bool auto_approve = 14; // plan stages +} + +enum PipelineRunStageType { + PIPELINE_RUN_STAGE_TYPE_UNSPECIFIED = 0; + PIPELINE_RUN_STAGE_TYPE_DEPLOY = 1; + PIPELINE_RUN_STAGE_TYPE_WAIT = 2; + PIPELINE_RUN_STAGE_TYPE_PLAN = 3; +} + +enum PipelineRunStageStatus { + PIPELINE_RUN_STAGE_STATUS_UNSPECIFIED = 0; + PIPELINE_RUN_STAGE_STATUS_PENDING = 1; + PIPELINE_RUN_STAGE_STATUS_ACTIVE = 2; + PIPELINE_RUN_STAGE_STATUS_SUCCEEDED = 3; + PIPELINE_RUN_STAGE_STATUS_FAILED = 4; + PIPELINE_RUN_STAGE_STATUS_CANCELLED = 5; + PIPELINE_RUN_STAGE_STATUS_AWAITING_APPROVAL = 6; +} + +// ── Plan stage approval ────────────────────────────────────────────── + +message ApprovePlanStageRequest { + string release_intent_id = 1; + string stage_id = 2; +} +message ApprovePlanStageResponse {} + +message RejectPlanStageRequest { + string release_intent_id = 1; + string stage_id = 2; + optional string reason = 3; +} +message RejectPlanStageResponse {} + +message GetPlanOutputRequest { + string release_intent_id = 1; + string stage_id = 2; +} +message GetPlanOutputResponse { + string plan_output = 1; + string status = 2; // RUNNING, AWAITING_APPROVAL, APPROVED, REJECTED } service ReleaseService { @@ -150,7 +311,13 @@ service ReleaseService { rpc GetReleasesByActor(GetReleasesByActorRequest) returns (GetReleasesByActorResponse); rpc GetOrganisations(GetOrganisationsRequest) returns (GetOrganisationsResponse); rpc GetProjects(GetProjectsRequest) returns (GetProjectsResponse); + rpc CreateProject(CreateProjectRequest) returns (CreateProjectResponse); rpc GetDestinationStates(GetDestinationStatesRequest) returns (GetDestinationStatesResponse); + rpc GetReleaseIntentStates(GetReleaseIntentStatesRequest) returns (GetReleaseIntentStatesResponse); + + rpc ApprovePlanStage(ApprovePlanStageRequest) returns (ApprovePlanStageResponse); + rpc RejectPlanStage(RejectPlanStageRequest) returns (RejectPlanStageResponse); + rpc GetPlanOutput(GetPlanOutputRequest) returns (GetPlanOutputResponse); } message Source { @@ -158,6 +325,8 @@ message Source { optional string email = 2; optional string source_type = 3; optional string run_url = 4; + // The actor ID (user, app, or service account UUID) that created this annotation. + optional string user_id = 5; } message ArtifactContext { @@ -177,6 +346,7 @@ message Artifact { Project project = 7; repeated ArtifactDestination destinations = 8; string created_at = 9; + Ref ref = 10; } message ArtifactDestination { diff --git a/interface/proto/forest/v1/runner.proto b/interface/proto/forest/v1/runner.proto new file mode 100644 index 0000000..ac9a275 --- /dev/null +++ b/interface/proto/forest/v1/runner.proto @@ -0,0 +1,212 @@ +syntax = "proto3"; + +package forest.v1; + +// RunnerService is exposed by the forest-server. Runners (workers) call these +// RPCs to register for work, fetch release artifacts, stream logs, and report +// completion. Authentication for all post-assignment RPCs uses a release-scoped +// opaque token rather than the regular JWT flow. +service RunnerService { + // Bidirectional stream used for runner registration and work assignment. + // The runner sends a RunnerRegister as its first message, then periodic + // RunnerHeartbeat messages. The server responds with a RegisterAck followed + // by WorkAssignment messages when releases matching the runner's capabilities + // become available. + rpc RegisterRunner(stream RunnerMessage) returns (stream ServerMessage); + + // Fetch the artifact files for a release assigned to this runner. + // Scoped by the release_token received in the WorkAssignment. + rpc GetReleaseFiles(GetReleaseFilesRequest) returns (stream ReleaseFile); + + // Stream log lines back to the server for real-time display. + // Each message must include the release_token for authentication. + rpc PushLogs(stream PushLogRequest) returns (PushLogResponse); + + // Fetch the original spec files for a release. + // Scoped by the release_token received in the WorkAssignment. + rpc GetSpecFiles(GetSpecFilesRequest) returns (stream ReleaseFile); + + // Fetch the annotation (metadata context) for a release. + rpc GetReleaseAnnotation(GetReleaseAnnotationRequest) returns (ReleaseAnnotationResponse); + + // Fetch project info (organisation + project name) for a release. + rpc GetProjectInfo(GetProjectInfoRequest) returns (ProjectInfoResponse); + + // Report the final outcome of a release (success or failure). + // This commits the release status and revokes the token. + rpc CompleteRelease(CompleteReleaseRequest) returns (CompleteReleaseResponse); +} + +// ============================================================================ +// Connect stream: Runner → Server +// ============================================================================ + +message RunnerMessage { + oneof message { + RunnerRegister register = 1; + RunnerHeartbeat heartbeat = 2; + WorkAck work_ack = 3; + } +} + +// First message a runner sends on the Connect stream. +message RunnerRegister { + // Runner-chosen unique identifier. If empty, the server assigns one. + string runner_id = 1; + // Destination types this runner can handle. + repeated DestinationCapability capabilities = 2; + // Maximum number of simultaneous releases this runner can process. + int32 max_concurrent = 3; +} + +// Describes a destination type the runner supports. +message DestinationCapability { + string organisation = 1; + string name = 2; + uint64 version = 3; +} + +// Periodic keepalive sent by the runner (recommended every 10s). +message RunnerHeartbeat { + // Current number of in-progress releases on this runner. + int32 active_releases = 1; +} + +// Runner's response to a WorkAssignment. +message WorkAck { + string release_token = 1; + // false = runner rejects the work (e.g., overloaded). The server will + // reassign or fall back to in-process execution. + bool accepted = 2; +} + +// ============================================================================ +// Connect stream: Server → Runner +// ============================================================================ + +message ServerMessage { + oneof message { + RegisterAck register_ack = 1; + WorkAssignment work_assignment = 2; + } +} + +// Server response to RunnerRegister. +message RegisterAck { + // Server-confirmed (or server-assigned) runner ID. + string runner_id = 1; + bool accepted = 2; + string reason = 3; +} + +// Work assignment pushed to a runner when a matching release is available. +message WorkAssignment { + // Scoped opaque auth token. Use this for GetReleaseFiles, PushLogs, + // and CompleteRelease. The token restricts access to only the data + // associated with this specific release. + string release_token = 1; + string release_id = 2; + string release_intent_id = 3; + string artifact_id = 4; + string destination_id = 5; + // Full destination configuration including metadata. + DestinationInfo destination = 6; +} + +// Destination configuration sent with the work assignment. +message DestinationInfo { + string name = 1; + string environment = 2; + map metadata = 3; + DestinationCapability type = 4; + string organisation = 5; +} + +// ============================================================================ +// GetReleaseFiles +// ============================================================================ + +message GetReleaseFilesRequest { + string release_token = 1; +} + +message ReleaseFile { + string file_name = 1; + string file_content = 2; +} + +// ============================================================================ +// GetSpecFiles +// ============================================================================ + +message GetSpecFilesRequest { + string release_token = 1; +} + +// ============================================================================ +// GetReleaseAnnotation +// ============================================================================ + +message GetReleaseAnnotationRequest { + string release_token = 1; +} + +message ReleaseAnnotationResponse { + string slug = 1; + string source_username = 2; + string source_email = 3; + string context_title = 4; + string context_description = 5; + string context_web = 6; + string reference_version = 7; + string reference_commit_sha = 8; + string reference_commit_branch = 9; + string reference_commit_message = 10; + string created_at = 11; +} + +// ============================================================================ +// GetProjectInfo +// ============================================================================ + +message GetProjectInfoRequest { + string release_token = 1; +} + +message ProjectInfoResponse { + string organisation = 1; + string project = 2; +} + +// ============================================================================ +// PushLogs +// ============================================================================ + +message PushLogRequest { + string release_token = 1; + // "stdout" or "stderr" + string channel = 2; + string line = 3; + uint64 timestamp = 4; +} + +message PushLogResponse {} + +// ============================================================================ +// CompleteRelease +// ============================================================================ + +message CompleteReleaseRequest { + string release_token = 1; + ReleaseOutcome outcome = 2; + // Error description when outcome is FAILURE. + string error_message = 3; +} + +enum ReleaseOutcome { + RELEASE_OUTCOME_UNSPECIFIED = 0; + RELEASE_OUTCOME_SUCCESS = 1; + RELEASE_OUTCOME_FAILURE = 2; +} + +message CompleteReleaseResponse {} diff --git a/interface/proto/forest/v1/triggers.proto b/interface/proto/forest/v1/triggers.proto new file mode 100644 index 0000000..3316c13 --- /dev/null +++ b/interface/proto/forest/v1/triggers.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +package forest.v1; + +import "forest/v1/releases.proto"; + +message Trigger { + string id = 1; + string name = 2; + bool enabled = 3; + optional string branch_pattern = 4; + optional string title_pattern = 5; + optional string author_pattern = 6; + optional string commit_message_pattern = 7; + optional string source_type_pattern = 8; + repeated string target_environments = 9; + repeated string target_destinations = 10; + bool force_release = 11; + string created_at = 12; + string updated_at = 13; + // When true, trigger the project's release pipeline instead of + // deploying directly to target destinations/environments. + bool use_pipeline = 14; +} + +message CreateTriggerRequest { + Project project = 1; + string name = 2; + optional string branch_pattern = 3; + optional string title_pattern = 4; + optional string author_pattern = 5; + optional string commit_message_pattern = 6; + optional string source_type_pattern = 7; + repeated string target_environments = 8; + repeated string target_destinations = 9; + bool force_release = 10; + bool use_pipeline = 11; +} +message CreateTriggerResponse { + Trigger trigger = 1; +} + +message UpdateTriggerRequest { + Project project = 1; + string name = 2; + optional bool enabled = 3; + optional string branch_pattern = 4; + optional string title_pattern = 5; + optional string author_pattern = 6; + optional string commit_message_pattern = 7; + optional string source_type_pattern = 8; + repeated string target_environments = 9; + repeated string target_destinations = 10; + optional bool force_release = 11; + optional bool use_pipeline = 12; +} +message UpdateTriggerResponse { + Trigger trigger = 1; +} + +message DeleteTriggerRequest { + Project project = 1; + string name = 2; +} +message DeleteTriggerResponse {} + +message ListTriggersRequest { + Project project = 1; +} +message ListTriggersResponse { + repeated Trigger triggers = 1; +} + +service TriggerService { + rpc CreateTrigger(CreateTriggerRequest) returns (CreateTriggerResponse); + rpc UpdateTrigger(UpdateTriggerRequest) returns (UpdateTriggerResponse); + rpc DeleteTrigger(DeleteTriggerRequest) returns (DeleteTriggerResponse); + rpc ListTriggers(ListTriggersRequest) returns (ListTriggersResponse); +} diff --git a/policies-with-approval.png b/policies-with-approval.png new file mode 100644 index 0000000..8946458 Binary files /dev/null and b/policies-with-approval.png differ diff --git a/project-overview-hidden.png b/project-overview-hidden.png new file mode 100644 index 0000000..af9ed80 Binary files /dev/null and b/project-overview-hidden.png differ diff --git a/project-overview.png b/project-overview.png new file mode 100644 index 0000000..cc4fd5b Binary files /dev/null and b/project-overview.png differ diff --git a/scripts/sync-protos.sh b/scripts/sync-protos.sh new file mode 100755 index 0000000..fb85575 --- /dev/null +++ b/scripts/sync-protos.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +FOREST_PROTO="/home/kjuulh/git/src.rawpotion.io/rawpotion/forest/interface/proto/forest/v1" +FORAGE_PROTO="/home/kjuulh/git/git.kjuulh.io/forage/client/interface/proto/forest/v1" + +echo "Syncing protos from forest -> forage..." + +for proto in "$FOREST_PROTO"/*.proto; do + name=$(basename "$proto") + cp "$proto" "$FORAGE_PROTO/$name" + echo " copied $name" +done + +echo "Running buf generate..." +cd /home/kjuulh/git/git.kjuulh.io/forage/client +buf generate + +echo "Done." diff --git a/static/css/style.css b/static/css/style.css index 8ce622f..f756af8 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-700:oklch(45.7% .24 277.023);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-800:oklch(45.9% .187 3.815);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.end\!{inset-inline-end:var(--spacing)!important}.-top-3{top:calc(var(--spacing) * -3)}.top-0\.5{top:calc(var(--spacing) * .5)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-\[3px\]{top:3px}.right-1\.5{right:calc(var(--spacing) * 1.5)}.left-0{left:calc(var(--spacing) * 0)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-4{left:calc(var(--spacing) * 4)}.left-\[3px\]{left:3px}.left-\[calc\(100\%-1\.125rem\)\]{left:calc(100% - 1.125rem)}.z-20{z-index:20}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-8{margin-block:calc(var(--spacing) * 8)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-auto{margin-top:auto}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-auto{margin-left:auto}.scrollbar-none{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-none::-webkit-scrollbar{display:none}.mobile-only{display:none}@media (max-width:39.999rem){.mobile-only{display:block}}.block{display:block}@media (max-width:39.999rem){.desktop-only{display:none}}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-48{width:calc(var(--spacing) * 48)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[250px\]{max-width:250px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[140px\]{min-width:140px}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-y{resize:vertical}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-blue-200{border-color:var(--color-blue-200)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-purple-200{border-color:var(--color-purple-200)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-transparent{border-color:#0000}.border-t-gray-600{border-top-color:var(--color-gray-600)}.bg-\[\#4A154B\]{background-color:#4a154b}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-50\/50{background-color:#f9fafb80}@supports (color:color-mix(in lab, red, red)){.bg-gray-50\/50{background-color:color-mix(in oklab, var(--color-gray-50) 50%, transparent)}}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-500{background-color:var(--color-red-500)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.p-1{padding:calc(var(--spacing) * 1)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-12{padding:calc(var(--spacing) * 12)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pt-24{padding-top:calc(var(--spacing) * 24)}.pr-1{padding-right:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-indigo-700{color:var(--color-indigo-700)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-pink-800{color:var(--color-pink-800)}.text-purple-400{color:var(--color-purple-400)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-white{color:var(--color-white)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.group-open\:hidden:is(:where(.group):is([open],:popover-open,:open) *){display:none}.group-open\:inline:is(:where(.group):is([open],:popover-open,:open) *){display:inline}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}@media (hover:hover){.group-hover\:border-gray-300:is(:where(.group):hover *){border-color:var(--color-gray-300)}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}.group-hover\:text-violet-700:is(:where(.group):hover *){color:var(--color-violet-700)}}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}@media (hover:hover){.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-sm:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.focus\:border-transparent:focus{border-color:#0000}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.has-\[\:checked\]\:border-blue-500:has(:checked){border-color:var(--color-blue-500)}.has-\[\:checked\]\:bg-blue-50:has(:checked){background-color:var(--color-blue-50)}@media (min-width:40rem){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}@media (prefers-color-scheme:dark){:root,:host{--color-white:oklch(14.5% .015 260);--color-black:oklch(98% .002 248);--color-gray-50:oklch(17.5% .02 260);--color-gray-100:oklch(21% .024 265);--color-gray-200:oklch(27.8% .025 257);--color-gray-300:oklch(37.3% .025 260);--color-gray-400:oklch(55.1% .02 264);--color-gray-500:oklch(60% .02 264);--color-gray-600:oklch(70.7% .017 261);--color-gray-700:oklch(80% .012 258);--color-gray-800:oklch(87.2% .008 258);--color-gray-900:oklch(93% .005 265);--color-gray-950:oklch(96.7% .003 265);--color-green-50:oklch(20% .04 155);--color-green-100:oklch(25% .06 155);--color-green-200:oklch(30% .08 155);--color-green-300:oklch(42% .12 154);--color-green-700:oklch(75% .15 150);--color-green-800:oklch(80% .12 150);--color-red-50:oklch(22% .04 17);--color-red-200:oklch(32% .06 18);--color-red-600:oklch(65% .2 27);--color-red-700:oklch(72% .18 27);--color-red-800:oklch(77% .15 27);--color-blue-100:oklch(22% .04 255);--color-blue-600:oklch(62% .2 263);--color-blue-700:oklch(72% .17 264);--color-blue-800:oklch(77% .15 265);--color-orange-100:oklch(25% .05 75);--color-orange-800:oklch(78% .13 37);--color-yellow-100:oklch(25% .06 103);--color-yellow-700:oklch(72% .12 66);--color-yellow-800:oklch(77% .1 62);--color-violet-100:oklch(22% .04 295);--color-violet-200:oklch(28% .06 294);--color-violet-400:oklch(45% .14 293);--color-violet-600:oklch(60% .2 293);--color-violet-800:oklch(75% .18 293);--color-purple-100:oklch(22% .04 307);--color-purple-800:oklch(75% .17 304);--color-pink-100:oklch(22% .04 342);--color-pink-800:oklch(75% .15 4);--color-amber-400:oklch(80% .17 84)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-700:oklch(50.8% .118 165.612);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-700:oklch(45.7% .24 277.023);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-800:oklch(45.9% .187 3.815);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-tight:1.25;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.end\!{inset-inline-end:var(--spacing)!important}.-top-3{top:calc(var(--spacing) * -3)}.top-0\.5{top:calc(var(--spacing) * .5)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-\[3px\]{top:3px}.right-1\.5{right:calc(var(--spacing) * 1.5)}.left-0{left:calc(var(--spacing) * 0)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-4{left:calc(var(--spacing) * 4)}.left-\[3px\]{left:3px}.left-\[calc\(100\%-1\.125rem\)\]{left:calc(100% - 1.125rem)}.z-20{z-index:20}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-8{margin-block:calc(var(--spacing) * 8)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-auto{margin-top:auto}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-7{margin-left:calc(var(--spacing) * 7)}.ml-auto{margin-left:auto}.scrollbar-none{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-none::-webkit-scrollbar{display:none}.mobile-only{display:none}@media (max-width:39.999rem){.mobile-only{display:block}}.block{display:block}@media (max-width:39.999rem){.desktop-only{display:none}}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-48{width:calc(var(--spacing) * 48)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[250px\]{max-width:250px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[140px\]{min-width:140px}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-y{resize:vertical}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-blue-200{border-color:var(--color-blue-200)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-purple-200{border-color:var(--color-purple-200)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-transparent{border-color:#0000}.border-t-gray-600{border-top-color:var(--color-gray-600)}.bg-\[\#4A154B\]{background-color:#4a154b}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-50\/50{background-color:#f9fafb80}@supports (color:color-mix(in lab, red, red)){.bg-gray-50\/50{background-color:color-mix(in oklab, var(--color-gray-50) 50%, transparent)}}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.p-1{padding:calc(var(--spacing) * 1)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-12{padding:calc(var(--spacing) * 12)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pt-24{padding-top:calc(var(--spacing) * 24)}.pr-1{padding-right:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-blue-400{color:var(--color-blue-400)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-700{color:var(--color-emerald-700)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-indigo-700{color:var(--color-indigo-700)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-pink-800{color:var(--color-pink-800)}.text-purple-400{color:var(--color-purple-400)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-white{color:var(--color-white)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.group-open\:hidden:is(:where(.group):is([open],:popover-open,:open) *){display:none}.group-open\:inline:is(:where(.group):is([open],:popover-open,:open) *){display:inline}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}@media (hover:hover){.group-hover\:border-gray-300:is(:where(.group):hover *){border-color:var(--color-gray-300)}.group-hover\:text-gray-500:is(:where(.group):hover *){color:var(--color-gray-500)}.group-hover\:text-violet-700:is(:where(.group):hover *){color:var(--color-violet-700)}}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}@media (hover:hover){.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-sm:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.focus\:border-transparent:focus{border-color:#0000}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}.has-\[\:checked\]\:border-blue-500:has(:checked){border-color:var(--color-blue-500)}.has-\[\:checked\]\:bg-blue-50:has(:checked){background-color:var(--color-blue-50)}@media (min-width:40rem){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}@media (prefers-color-scheme:dark){:root,:host{--color-white:oklch(14.5% .015 260);--color-black:oklch(98% .002 248);--color-gray-50:oklch(17.5% .02 260);--color-gray-100:oklch(21% .024 265);--color-gray-200:oklch(27.8% .025 257);--color-gray-300:oklch(37.3% .025 260);--color-gray-400:oklch(55.1% .02 264);--color-gray-500:oklch(60% .02 264);--color-gray-600:oklch(70.7% .017 261);--color-gray-700:oklch(80% .012 258);--color-gray-800:oklch(87.2% .008 258);--color-gray-900:oklch(93% .005 265);--color-gray-950:oklch(96.7% .003 265);--color-green-50:oklch(20% .04 155);--color-green-100:oklch(25% .06 155);--color-green-200:oklch(30% .08 155);--color-green-300:oklch(42% .12 154);--color-green-700:oklch(75% .15 150);--color-green-800:oklch(80% .12 150);--color-red-50:oklch(22% .04 17);--color-red-200:oklch(32% .06 18);--color-red-600:oklch(65% .2 27);--color-red-700:oklch(72% .18 27);--color-red-800:oklch(77% .15 27);--color-blue-100:oklch(22% .04 255);--color-blue-600:oklch(62% .2 263);--color-blue-700:oklch(72% .17 264);--color-blue-800:oklch(77% .15 265);--color-orange-100:oklch(25% .05 75);--color-orange-800:oklch(78% .13 37);--color-yellow-100:oklch(25% .06 103);--color-yellow-700:oklch(72% .12 66);--color-yellow-800:oklch(77% .1 62);--color-violet-100:oklch(22% .04 295);--color-violet-200:oklch(28% .06 294);--color-violet-400:oklch(45% .14 293);--color-violet-600:oklch(60% .2 293);--color-violet-800:oklch(75% .18 293);--color-purple-100:oklch(22% .04 307);--color-purple-800:oklch(75% .17 304);--color-pink-100:oklch(22% .04 342);--color-pink-800:oklch(75% .15 4);--color-amber-400:oklch(80% .17 84)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file diff --git a/static/js/components/forage-components.js b/static/js/components/forage-components.js index 71a9a7d..4dcf256 100644 --- a/static/js/components/forage-components.js +++ b/static/js/components/forage-components.js @@ -1,11 +1,11 @@ -var Jf=Object.defineProperty;var Ao=_e=>{throw TypeError(_e)};var Xf=(_e,fe,Me)=>fe in _e?Jf(_e,fe,{enumerable:!0,configurable:!0,writable:!0,value:Me}):_e[fe]=Me;var ye=(_e,fe,Me)=>Xf(_e,typeof fe!="symbol"?fe+"":fe,Me),Zs=(_e,fe,Me)=>fe.has(_e)||Ao("Cannot "+Me);var u=(_e,fe,Me)=>(Zs(_e,fe,"read from private field"),Me?Me.call(_e):fe.get(_e)),G=(_e,fe,Me)=>fe.has(_e)?Ao("Cannot add the same private member more than once"):fe instanceof WeakSet?fe.add(_e):fe.set(_e,Me),F=(_e,fe,Me,Fn)=>(Zs(_e,fe,"write to private field"),Fn?Fn.call(_e,Me):fe.set(_e,Me),Me),Ee=(_e,fe,Me)=>(Zs(_e,fe,"access private method"),Me);(function(){"use strict";var Eo,$o,hr,an,Fr,fn,cn,un,_r,Ht,dn,rt,ei,ti,ri,ni,ft,In,Gt,Br,nt,Vt,ct,qt,er,Pr,gr,vn,pn,hn,tr,rs,we,Mo,Ro,Io,si,as,fs,ii,Co,Ot,Wt,ut,zr,Ln,qn,ns,rr,yt;typeof window<"u"&&((Eo=window.__svelte??(window.__svelte={})).v??(Eo.v=new Set)).add("5");let fe=!1,Me=!1;function Fn(){fe=!0}Fn();const Lo=1,qo=2,oi=4,Oo=8,Uo=16,jo=1,Fo=2,Bo=4,Po=8,zo=16,li=1,Ho=2,ai="[",cs="[!",fi="[?",us="]",wr={},Ue=Symbol(),ci="http://www.w3.org/1999/xhtml",Go="http://www.w3.org/2000/svg",Vo="http://www.w3.org/1998/Math/MathML",ds=!1;var ui=Array.isArray,Wo=Array.prototype.indexOf,Qr=Array.prototype.includes,Bn=Array.from,Pn=Object.keys,zn=Object.defineProperty,kr=Object.getOwnPropertyDescriptor,di=Object.getOwnPropertyDescriptors,Yo=Object.prototype,Qo=Array.prototype,vs=Object.getPrototypeOf,vi=Object.isExtensible;const Ko=()=>{};function Jo(e){return e()}function ps(e){for(var t=0;t{e=s,t=i});return{promise:r,resolve:e,reject:t}}const je=2,Kr=4,yr=8,hs=1<<24,ir=16,Tt=32,or=64,_s=128,ht=512,Ie=1024,Fe=2048,_t=4096,We=8192,Ft=16384,Er=32768,Jr=65536,hi=1<<17,Xo=1<<18,$r=1<<19,_i=1<<20,Bt=1<<25,Cr=65536,gs=1<<21,ms=1<<22,lr=1<<23,Sr=Symbol("$state"),gi=Symbol("legacy props"),Zo=Symbol(""),Dr=new class extends Error{constructor(){super(...arguments);ye(this,"name","StaleReactionError");ye(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}},el=!!(($o=globalThis.document)!=null&&$o.contentType)&&globalThis.document.contentType.includes("xml"),bn=3,Xr=8;function mi(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function tl(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function rl(e,t,r){throw new Error("https://svelte.dev/e/each_key_duplicate")}function nl(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function sl(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function il(e){throw new Error("https://svelte.dev/e/effect_orphan")}function ol(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function ll(){throw new Error("https://svelte.dev/e/hydration_failed")}function al(e){throw new Error("https://svelte.dev/e/props_invalid_value")}function fl(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function cl(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function ul(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function dl(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}function wn(e){console.warn("https://svelte.dev/e/hydration_mismatch")}function vl(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}let Q=!1;function Jt(e){Q=e}let j;function He(e){if(e===null)throw wn(),wr;return j=e}function Zr(){return He(Rt(j))}function y(e){if(Q){if(Rt(j)!==null)throw wn(),wr;j=e}}function ar(e=1){if(Q){for(var t=e,r=j;t--;)r=Rt(r);j=r}}function Hn(e=!0){for(var t=0,r=j;;){if(r.nodeType===Xr){var s=r.data;if(s===us){if(t===0)return r;t-=1}else(s===ai||s===cs||s[0]==="["&&!isNaN(Number(s.slice(1))))&&(t+=1)}var i=Rt(r);e&&r.remove(),r=i}}function xi(e){if(!e||e.nodeType!==Xr)throw wn(),wr;return e.data}function bi(e){return e===this.v}function pl(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function wi(e){return!pl(e,this.v)}let ge=null;function en(e){ge=e}function Gn(e,t=!1,r){ge={p:ge,i:!1,c:null,e:null,s:e,x:null,l:fe&&!t?{s:null,u:null,$:[]}:null}}function Vn(e){var t=ge,r=t.e;if(r!==null){t.e=null;for(var s of r)Pi(s)}return e!==void 0&&(t.x=e),t.i=!0,ge=t.p,e??{}}function kn(){return!fe||ge!==null&&ge.l===null}let Nr=[];function ki(){var e=Nr;Nr=[],ps(e)}function Xt(e){if(Nr.length===0&&!yn){var t=Nr;queueMicrotask(()=>{t===Nr&&ki()})}Nr.push(e)}function hl(){for(;Nr.length>0;)ki()}function yi(e){var t=K;if(t===null)return W.f|=lr,e;if((t.f&Er)===0&&(t.f&Kr)===0)throw e;fr(e,t)}function fr(e,t){for(;t!==null;){if((t.f&_s)!==0){if((t.f&Er)===0)throw e;try{t.b.error(e);return}catch(r){e=r}}t=t.parent}throw e}const _l=-7169;function be(e,t){e.f=e.f&_l|t}function xs(e){(e.f&ht)!==0||e.deps===null?be(e,Ie):be(e,_t)}function Ei(e){if(e!==null)for(const t of e)(t.f&je)===0||(t.f&Cr)===0||(t.f^=Cr,Ei(t.deps))}function $i(e,t,r){(e.f&Fe)!==0?t.add(e):(e.f&_t)!==0&&r.add(e),Ei(e.deps),be(e,Ie)}const Wn=new Set;let ee=null,Be=null,Ze=[],Yn=null,yn=!1,tn=null,gl=1;const Ps=class Ps{constructor(){G(this,rt);ye(this,"id",gl++);ye(this,"current",new Map);ye(this,"previous",new Map);G(this,hr,new Set);G(this,an,new Set);G(this,Fr,0);G(this,fn,0);G(this,cn,null);G(this,un,new Set);G(this,_r,new Set);G(this,Ht,new Map);ye(this,"is_fork",!1);G(this,dn,!1)}skip_effect(t){u(this,Ht).has(t)||u(this,Ht).set(t,{d:[],m:[]})}unskip_effect(t){var r=u(this,Ht).get(t);if(r){u(this,Ht).delete(t);for(var s of r.d)be(s,Fe),Pt(s);for(s of r.m)be(s,_t),Pt(s)}}process(t){var i;Ze=[],this.apply();var r=tn=[],s=[];for(const o of t)Ee(this,rt,ti).call(this,o,r,s);if(tn=null,Ee(this,rt,ei).call(this)){Ee(this,rt,ri).call(this,s),Ee(this,rt,ri).call(this,r);for(const[o,l]of u(this,Ht))Ti(o,l)}else{ee=null;for(const o of u(this,hr))o(this);u(this,hr).clear(),u(this,Fr)===0&&Ee(this,rt,ni).call(this),Si(s),Si(r),u(this,un).clear(),u(this,_r).clear(),(i=u(this,cn))==null||i.resolve()}Be=null}capture(t,r){r!==Ue&&!this.previous.has(t)&&this.previous.set(t,r),(t.f&lr)===0&&(this.current.set(t,t.v),Be==null||Be.set(t,t.v))}activate(){ee=this,this.apply()}deactivate(){ee===this&&(ee=null,Be=null)}flush(){var t;if(Ze.length>0)ee=this,Ci();else if(u(this,Fr)===0&&!this.is_fork){for(const r of u(this,hr))r(this);u(this,hr).clear(),Ee(this,rt,ni).call(this),(t=u(this,cn))==null||t.resolve()}this.deactivate()}discard(){for(const t of u(this,an))t(this);u(this,an).clear()}increment(t){F(this,Fr,u(this,Fr)+1),t&&F(this,fn,u(this,fn)+1)}decrement(t){F(this,Fr,u(this,Fr)-1),t&&F(this,fn,u(this,fn)-1),!u(this,dn)&&(F(this,dn,!0),Xt(()=>{F(this,dn,!1),Ee(this,rt,ei).call(this)?Ze.length>0&&this.flush():this.revive()}))}revive(){for(const t of u(this,un))u(this,_r).delete(t),be(t,Fe),Pt(t);for(const t of u(this,_r))be(t,_t),Pt(t);this.flush()}oncommit(t){u(this,hr).add(t)}ondiscard(t){u(this,an).add(t)}settled(){return(u(this,cn)??F(this,cn,pi())).promise}static ensure(){if(ee===null){const t=ee=new Ps;Wn.add(ee),yn||Xt(()=>{ee===t&&t.flush()})}return ee}apply(){}};hr=new WeakMap,an=new WeakMap,Fr=new WeakMap,fn=new WeakMap,cn=new WeakMap,un=new WeakMap,_r=new WeakMap,Ht=new WeakMap,dn=new WeakMap,rt=new WeakSet,ei=function(){return this.is_fork||u(this,fn)>0},ti=function(t,r,s){t.f^=Ie;for(var i=t.first;i!==null;){var o=i.f,l=(o&(Tt|or))!==0,f=l&&(o&Ie)!==0,a=(o&We)!==0,c=f||u(this,Ht).has(i);if(!c&&i.fn!==null){l?a||(i.f^=Ie):(o&Kr)!==0?r.push(i):(o&(yr|hs))!==0&&a?s.push(i):nn(i)&&(qr(i),(o&ir)!==0&&(u(this,_r).add(i),a&&be(i,Fe)));var v=i.first;if(v!==null){i=v;continue}}for(;i!==null;){var m=i.next;if(m!==null){i=m;break}i=i.parent}}},ri=function(t){for(var r=0;r1){this.previous.clear();var t=ee,r=Be,s=!0;for(const l of Wn){if(l===this){s=!1;continue}const f=[];for(const[c,v]of this.current){if(l.current.has(c))if(s&&v!==l.current.get(c))l.current.set(c,v);else continue;f.push(c)}if(f.length===0)continue;const a=[...l.current.keys()].filter(c=>!this.current.has(c));if(a.length>0){var i=Ze;Ze=[];const c=new Set,v=new Map;for(const m of f)Di(m,a,c,v);if(Ze.length>0){ee=l,l.apply();for(const m of Ze)Ee(o=l,rt,ti).call(o,m,[],[]);l.deactivate()}Ze=i}}ee=t,Be=r}u(this,Ht).clear(),Wn.delete(this)};let Zt=Ps;function Tr(e){var t=yn;yn=!0;try{for(var r;;){if(hl(),Ze.length===0&&(ee==null||ee.flush(),Ze.length===0))return Yn=null,r;Ci()}}finally{yn=t}}function Ci(){var e=null;try{for(var t=0;Ze.length>0;){var r=Zt.ensure();if(t++>1e3){var s,i;ml()}r.process(Ze),ur.clear()}}finally{Ze=[],Yn=null,tn=null}}function ml(){try{ol()}catch(e){fr(e,Yn)}}let At=null;function Si(e){var t=e.length;if(t!==0){for(var r=0;r0)){ur.clear();for(const i of At){if((i.f&(Ft|We))!==0)continue;const o=[i];let l=i.parent;for(;l!==null;)At.has(l)&&(At.delete(l),o.push(l)),l=l.parent;for(let f=o.length-1;f>=0;f--){const a=o[f];(a.f&(Ft|We))===0&&qr(a)}}At.clear()}}At=null}}function Di(e,t,r,s){if(!r.has(e)&&(r.add(e),e.reactions!==null))for(const i of e.reactions){const o=i.f;(o&je)!==0?Di(i,t,r,s):(o&(ms|ir))!==0&&(o&Fe)===0&&Ni(i,t,s)&&(be(i,Fe),Pt(i))}}function Ni(e,t,r){const s=r.get(e);if(s!==void 0)return s;if(e.deps!==null)for(const i of e.deps){if(Qr.call(t,i))return!0;if((i.f&je)!==0&&Ni(i,t,r))return r.set(i,!0),!0}return r.set(e,!1),!1}function Pt(e){var t=Yn=e,r=t.b;if(r!=null&&r.is_pending&&(e.f&(Kr|yr|hs))!==0&&(e.f&Er)===0){r.defer_effect(e);return}for(;t.parent!==null;){t=t.parent;var s=t.f;if(tn!==null&&t===K&&(e.f&yr)===0)return;if((s&(or|Tt))!==0){if((s&Ie)===0)return;t.f^=Ie}}Ze.push(t)}function Ti(e,t){if(!((e.f&Tt)!==0&&(e.f&Ie)!==0)){(e.f&Fe)!==0?t.d.push(e):(e.f&_t)!==0&&t.m.push(e),be(e,Ie);for(var r=e.first;r!==null;)Ti(r,t),r=r.next}}function xl(e){let t=0,r=Ar(0),s;return()=>{Cs()&&(n(r),Sn(()=>(t===0&&(s=d(()=>e(()=>$n(r)))),t+=1,()=>{Xt(()=>{t-=1,t===0&&(s==null||s(),s=void 0,$n(r))})})))}}var bl=Jr|$r;function wl(e,t,r,s){new kl(e,t,r,s)}class kl{constructor(t,r,s,i){G(this,we);ye(this,"parent");ye(this,"is_pending",!1);ye(this,"transform_error");G(this,ft);G(this,In,Q?j:null);G(this,Gt);G(this,Br);G(this,nt);G(this,Vt,null);G(this,ct,null);G(this,qt,null);G(this,er,null);G(this,Pr,0);G(this,gr,0);G(this,vn,!1);G(this,pn,new Set);G(this,hn,new Set);G(this,tr,null);G(this,rs,xl(()=>(F(this,tr,Ar(u(this,Pr))),()=>{F(this,tr,null)})));var o;F(this,ft,t),F(this,Gt,r),F(this,Br,l=>{var f=K;f.b=this,f.f|=_s,s(l)}),this.parent=K.b,this.transform_error=i??((o=this.parent)==null?void 0:o.transform_error)??(l=>l),F(this,nt,Ds(()=>{if(Q){const l=u(this,In);Zr();const f=l.data===cs;if(l.data.startsWith(fi)){const c=JSON.parse(l.data.slice(fi.length));Ee(this,we,Ro).call(this,c)}else f?Ee(this,we,Io).call(this):Ee(this,we,Mo).call(this)}else Ee(this,we,si).call(this)},bl)),Q&&F(this,ft,j)}defer_effect(t){$i(t,u(this,pn),u(this,hn))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!u(this,Gt).pending}update_pending_count(t){Ee(this,we,ii).call(this,t),F(this,Pr,u(this,Pr)+t),!(!u(this,tr)||u(this,vn))&&(F(this,vn,!0),Xt(()=>{F(this,vn,!1),u(this,tr)&&rn(u(this,tr),u(this,Pr))}))}get_effect_pending(){return u(this,rs).call(this),n(u(this,tr))}error(t){var r=u(this,Gt).onerror;let s=u(this,Gt).failed;if(!r&&!s)throw t;u(this,Vt)&&(Ke(u(this,Vt)),F(this,Vt,null)),u(this,ct)&&(Ke(u(this,ct)),F(this,ct,null)),u(this,qt)&&(Ke(u(this,qt)),F(this,qt,null)),Q&&(He(u(this,In)),ar(),He(Hn()));var i=!1,o=!1;const l=()=>{if(i){vl();return}i=!0,o&&dl(),u(this,qt)!==null&&Rr(u(this,qt),()=>{F(this,qt,null)}),Ee(this,we,fs).call(this,()=>{Zt.ensure(),Ee(this,we,si).call(this)})},f=a=>{try{o=!0,r==null||r(a,l),o=!1}catch(c){fr(c,u(this,nt)&&u(this,nt).parent)}s&&F(this,qt,Ee(this,we,fs).call(this,()=>{Zt.ensure();try{return mt(()=>{var c=K;c.b=this,c.f|=_s,s(u(this,ft),()=>a,()=>l)})}catch(c){return fr(c,u(this,nt).parent),null}}))};Xt(()=>{var a;try{a=this.transform_error(t)}catch(c){fr(c,u(this,nt)&&u(this,nt).parent);return}a!==null&&typeof a=="object"&&typeof a.then=="function"?a.then(f,c=>fr(c,u(this,nt)&&u(this,nt).parent)):f(a)})}}ft=new WeakMap,In=new WeakMap,Gt=new WeakMap,Br=new WeakMap,nt=new WeakMap,Vt=new WeakMap,ct=new WeakMap,qt=new WeakMap,er=new WeakMap,Pr=new WeakMap,gr=new WeakMap,vn=new WeakMap,pn=new WeakMap,hn=new WeakMap,tr=new WeakMap,rs=new WeakMap,we=new WeakSet,Mo=function(){try{F(this,Vt,mt(()=>u(this,Br).call(this,u(this,ft))))}catch(t){this.error(t)}},Ro=function(t){const r=u(this,Gt).failed;r&&F(this,qt,mt(()=>{r(u(this,ft),()=>t,()=>()=>{})}))},Io=function(){const t=u(this,Gt).pending;t&&(this.is_pending=!0,F(this,ct,mt(()=>t(u(this,ft)))),Xt(()=>{var r=F(this,er,document.createDocumentFragment()),s=et();r.append(s),F(this,Vt,Ee(this,we,fs).call(this,()=>(Zt.ensure(),mt(()=>u(this,Br).call(this,s))))),u(this,gr)===0&&(u(this,ft).before(r),F(this,er,null),Rr(u(this,ct),()=>{F(this,ct,null)}),Ee(this,we,as).call(this))}))},si=function(){try{if(this.is_pending=this.has_pending_snippet(),F(this,gr,0),F(this,Pr,0),F(this,Vt,mt(()=>{u(this,Br).call(this,u(this,ft))})),u(this,gr)>0){var t=F(this,er,document.createDocumentFragment());As(u(this,Vt),t);const r=u(this,Gt).pending;F(this,ct,mt(()=>r(u(this,ft))))}else Ee(this,we,as).call(this)}catch(r){this.error(r)}},as=function(){this.is_pending=!1;for(const t of u(this,pn))be(t,Fe),Pt(t);for(const t of u(this,hn))be(t,_t),Pt(t);u(this,pn).clear(),u(this,hn).clear()},fs=function(t){var r=K,s=W,i=ge;zt(u(this,nt)),xt(u(this,nt)),en(u(this,nt).ctx);try{return t()}catch(o){return yi(o),null}finally{zt(r),xt(s),en(i)}},ii=function(t){var r;if(!this.has_pending_snippet()){this.parent&&Ee(r=this.parent,we,ii).call(r,t);return}F(this,gr,u(this,gr)+t),u(this,gr)===0&&(Ee(this,we,as).call(this),u(this,ct)&&Rr(u(this,ct),()=>{F(this,ct,null)}),u(this,er)&&(u(this,ft).before(u(this,er)),F(this,er,null)))};function yl(e,t,r,s){const i=kn()?En:Ye;var o=e.filter(m=>!m.settled);if(r.length===0&&o.length===0){s(t.map(i));return}var l=K,f=El(),a=o.length===1?o[0].promise:o.length>1?Promise.all(o.map(m=>m.promise)):null;function c(m){f();try{s(m)}catch(b){(l.f&Ft)===0&&fr(b,l)}bs()}if(r.length===0){a.then(()=>c(t.map(i)));return}function v(){f(),Promise.all(r.map(m=>Cl(m))).then(m=>c([...t.map(i),...m])).catch(m=>fr(m,l))}a?a.then(v):v()}function El(){var e=K,t=W,r=ge,s=ee;return function(o=!0){zt(e),xt(t),en(r),o&&(s==null||s.activate())}}function bs(e=!0){zt(null),xt(null),en(null),e&&(ee==null||ee.deactivate())}function $l(){var e=K.b,t=ee,r=e.is_rendered();return e.update_pending_count(1),t.increment(r),()=>{e.update_pending_count(-1),t.decrement(r)}}function En(e){var t=je|Fe,r=W!==null&&(W.f&je)!==0?W:null;return K!==null&&(K.f|=$r),{ctx:ge,deps:null,effects:null,equals:bi,f:t,fn:e,reactions:null,rv:0,v:Ue,wv:0,parent:r??K,ac:null}}function Cl(e,t,r){K===null&&tl();var i=void 0,o=Ar(Ue),l=!W,f=new Map;return ql(()=>{var b;var a=pi();i=a.promise;try{Promise.resolve(e()).then(a.resolve,a.reject).finally(bs)}catch(C){a.reject(C),bs()}var c=ee;if(l){var v=$l();(b=f.get(c))==null||b.reject(Dr),f.delete(c),f.set(c,a)}const m=(C,E=void 0)=>{if(c.activate(),E)E!==Dr&&(o.f|=lr,rn(o,E));else{(o.f&lr)!==0&&(o.f^=lr),rn(o,C);for(const[q,x]of f){if(f.delete(q),q===c)break;x.reject(Dr)}}v&&v()};a.promise.then(m,C=>m(null,C||"unknown"))}),Bi(()=>{for(const a of f.values())a.reject(Dr)}),new Promise(a=>{function c(v){function m(){v===i?a(o):c(i)}v.then(m,m)}c(i)})}function cr(e){const t=En(e);return Ki(t),t}function Ye(e){const t=En(e);return t.equals=wi,t}function Sl(e){var t=e.effects;if(t!==null){e.effects=null;for(var r=0;r0&&!Ri&&Tl()}return t}function Tl(){Ri=!1;for(const e of ks)(e.f&Ie)!==0&&be(e,_t),nn(e)&&qr(e);ks.clear()}function $n(e){V(e,e.v+1)}function Ii(e,t){var r=e.reactions;if(r!==null)for(var s=kn(),i=r.length,o=0;o{if(Lr===o)return f();var a=W,c=Lr;xt(null),Xi(o);var v=f();return xt(a),Xi(c),v};return s&&r.set("length",Le(e.length)),new Proxy(e,{defineProperty(f,a,c){(!("value"in c)||c.configurable===!1||c.enumerable===!1||c.writable===!1)&&fl();var v=r.get(a);return v===void 0?l(()=>{var m=Le(c.value);return r.set(a,m),m}):V(v,c.value,!0),!0},deleteProperty(f,a){var c=r.get(a);if(c===void 0){if(a in f){const v=l(()=>Le(Ue));r.set(a,v),$n(i)}}else V(c,Ue),$n(i);return!0},get(f,a,c){var C;if(a===Sr)return e;var v=r.get(a),m=a in f;if(v===void 0&&(!m||(C=kr(f,a))!=null&&C.writable)&&(v=l(()=>{var E=Mr(m?f[a]:Ue),q=Le(E);return q}),r.set(a,v)),v!==void 0){var b=n(v);return b===Ue?void 0:b}return Reflect.get(f,a,c)},getOwnPropertyDescriptor(f,a){var c=Reflect.getOwnPropertyDescriptor(f,a);if(c&&"value"in c){var v=r.get(a);v&&(c.value=n(v))}else if(c===void 0){var m=r.get(a),b=m==null?void 0:m.v;if(m!==void 0&&b!==Ue)return{enumerable:!0,configurable:!0,value:b,writable:!0}}return c},has(f,a){var b;if(a===Sr)return!0;var c=r.get(a),v=c!==void 0&&c.v!==Ue||Reflect.has(f,a);if(c!==void 0||K!==null&&(!v||(b=kr(f,a))!=null&&b.writable)){c===void 0&&(c=l(()=>{var C=v?Mr(f[a]):Ue,E=Le(C);return E}),r.set(a,c));var m=n(c);if(m===Ue)return!1}return v},set(f,a,c,v){var J;var m=r.get(a),b=a in f;if(s&&a==="length")for(var C=c;CLe(Ue)),r.set(C+"",E))}if(m===void 0)(!b||(J=kr(f,a))!=null&&J.writable)&&(m=l(()=>Le(void 0)),V(m,Mr(c)),r.set(a,m));else{b=m.v!==Ue;var q=l(()=>Mr(c));V(m,q)}var x=Reflect.getOwnPropertyDescriptor(f,a);if(x!=null&&x.set&&x.set.call(v,c),!b){if(s&&typeof a=="string"){var A=r.get("length"),ce=Number(a);Number.isInteger(ce)&&ce>=A.v&&V(A,ce+1)}$n(i)}return!0},ownKeys(f){n(i);var a=Reflect.ownKeys(f).filter(m=>{var b=r.get(m);return b===void 0||b.v!==Ue});for(var[c,v]of r)v.v!==Ue&&!(c in f)&&a.push(c);return a},setPrototypeOf(){cl()}})}var ys,Li,qi,Oi;function Es(){if(ys===void 0){ys=window,Li=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,r=Text.prototype;qi=kr(t,"firstChild").get,Oi=kr(t,"nextSibling").get,vi(e)&&(e.__click=void 0,e.__className=void 0,e.__attributes=null,e.__style=void 0,e.__e=void 0),vi(r)&&(r.__t=void 0)}}function et(e=""){return document.createTextNode(e)}function Qe(e){return qi.call(e)}function Rt(e){return Oi.call(e)}function $(e,t){if(!Q)return Qe(e);var r=Qe(j);if(r===null)r=j.appendChild(et());else if(t&&r.nodeType!==bn){var s=et();return r==null||r.before(s),He(s),s}return t&&Kn(r),He(r),r}function gt(e,t=!1){if(!Q){var r=Qe(e);return r instanceof Comment&&r.data===""?Rt(r):r}if(t){if((j==null?void 0:j.nodeType)!==bn){var s=et();return j==null||j.before(s),He(s),s}Kn(j)}return j}function T(e,t=1,r=!1){let s=Q?j:e;for(var i;t--;)i=s,s=Rt(s);if(!Q)return s;if(r){if((s==null?void 0:s.nodeType)!==bn){var o=et();return s===null?i==null||i.after(o):s.before(o),He(o),o}Kn(s)}return He(s),s}function Ui(e){e.textContent=""}function ji(){return!1}function Qn(e,t,r){return document.createElementNS(t??ci,e,void 0)}function Kn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===bn;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function $s(e){var t=W,r=K;xt(null),zt(null);try{return e()}finally{xt(t),zt(r)}}function Fi(e){K===null&&(W===null&&il(),sl()),dr&&nl()}function Al(e,t){var r=t.last;r===null?t.last=t.first=e:(r.next=e,e.prev=r,t.last=e)}function It(e,t){var r=K;r!==null&&(r.f&We)!==0&&(e|=We);var s={ctx:ge,deps:null,nodes:null,f:e|Fe|ht,first:null,fn:t,last:null,next:null,parent:r,b:r&&r.b,prev:null,teardown:null,wv:0,ac:null},i=s;if((e&Kr)!==0)tn!==null?tn.push(s):Pt(s);else if(t!==null){try{qr(s)}catch(l){throw Ke(s),l}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&(i.f&$r)===0&&(i=i.first,(e&ir)!==0&&(e&Jr)!==0&&i!==null&&(i.f|=Jr))}if(i!==null&&(i.parent=r,r!==null&&Al(i,r),W!==null&&(W.f&je)!==0&&(e&or)===0)){var o=W;(o.effects??(o.effects=[])).push(i)}return s}function Cs(){return W!==null&&!Lt}function Bi(e){const t=It(yr,null);return be(t,Ie),t.teardown=e,t}function Cn(e){Fi();var t=K.f,r=!W&&(t&Tt)!==0&&(t&Er)===0;if(r){var s=ge;(s.e??(s.e=[])).push(e)}else return Pi(e)}function Pi(e){return It(Kr|_i,e)}function Ml(e){return Fi(),It(yr|_i,e)}function Rl(e){Zt.ensure();const t=It(or|$r,e);return()=>{Ke(t)}}function Il(e){Zt.ensure();const t=It(or|$r,e);return(r={})=>new Promise(s=>{r.outro?Rr(t,()=>{Ke(t),s(void 0)}):(Ke(t),s(void 0))})}function zi(e){return It(Kr,e)}function Ss(e,t){var r=ge,s={effect:null,ran:!1,deps:e};r.l.$.push(s),s.effect=Sn(()=>{e(),!s.ran&&(s.ran=!0,d(t))})}function Ll(){var e=ge;Sn(()=>{for(var t of e.l.$){t.deps();var r=t.effect;(r.f&Ie)!==0&&r.deps!==null&&be(r,_t),nn(r)&&qr(r),t.ran=!1}})}function ql(e){return It(ms|$r,e)}function Sn(e,t=0){return It(yr|t,e)}function B(e,t=[],r=[],s=[]){yl(s,t,r,i=>{It(yr,()=>e(...i.map(n)))})}function Ds(e,t=0){var r=It(ir|t,e);return r}function mt(e){return It(Tt|$r,e)}function Hi(e){var t=e.teardown;if(t!==null){const r=dr,s=W;Qi(!0),xt(null);try{t.call(null)}finally{Qi(r),xt(s)}}}function Ns(e,t=!1){var r=e.first;for(e.first=e.last=null;r!==null;){const i=r.ac;i!==null&&$s(()=>{i.abort(Dr)});var s=r.next;(r.f&or)!==0?r.parent=null:Ke(r,t),r=s}}function Ol(e){for(var t=e.first;t!==null;){var r=t.next;(t.f&Tt)===0&&Ke(t),t=r}}function Ke(e,t=!0){var r=!1;(t||(e.f&Xo)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(Gi(e.nodes.start,e.nodes.end),r=!0),Ns(e,t&&!r),Dn(e,0),be(e,Ft);var s=e.nodes&&e.nodes.t;if(s!==null)for(const o of s)o.stop();Hi(e);var i=e.parent;i!==null&&i.first!==null&&Vi(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=null}function Gi(e,t){for(;e!==null;){var r=e===t?null:Rt(e);e.remove(),e=r}}function Vi(e){var t=e.parent,r=e.prev,s=e.next;r!==null&&(r.next=s),s!==null&&(s.prev=r),t!==null&&(t.first===e&&(t.first=s),t.last===e&&(t.last=r))}function Rr(e,t,r=!0){var s=[];Wi(e,s,!0);var i=()=>{r&&Ke(e),t&&t()},o=s.length;if(o>0){var l=()=>--o||i();for(var f of s)f.out(l)}else i()}function Wi(e,t,r){if((e.f&We)===0){e.f^=We;var s=e.nodes&&e.nodes.t;if(s!==null)for(const f of s)(f.is_global||r)&&t.push(f);for(var i=e.first;i!==null;){var o=i.next,l=(i.f&Jr)!==0||(i.f&Tt)!==0&&(e.f&ir)!==0;Wi(i,t,l?r:!1),i=o}}}function Ts(e){Yi(e,!0)}function Yi(e,t){if((e.f&We)!==0){e.f^=We;for(var r=e.first;r!==null;){var s=r.next,i=(r.f&Jr)!==0||(r.f&Tt)!==0;Yi(r,i?t:!1),r=s}var o=e.nodes&&e.nodes.t;if(o!==null)for(const l of o)(l.is_global||t)&&l.in()}}function As(e,t){if(e.nodes)for(var r=e.nodes.start,s=e.nodes.end;r!==null;){var i=r===s?null:Rt(r);t.append(r),r=i}}let Jn=!1,dr=!1;function Qi(e){dr=e}let W=null,Lt=!1;function xt(e){W=e}let K=null;function zt(e){K=e}let bt=null;function Ki(e){W!==null&&(bt===null?bt=[e]:bt.push(e))}let tt=null,lt=0,wt=null;function Ul(e){wt=e}let Ji=1,Ir=0,Lr=Ir;function Xi(e){Lr=e}function Zi(){return++Ji}function nn(e){var t=e.f;if((t&Fe)!==0)return!0;if(t&je&&(e.f&=~Cr),(t&_t)!==0){for(var r=e.deps,s=r.length,i=0;ie.wv)return!0}(t&ht)!==0&&Be===null&&be(e,Ie)}return!1}function eo(e,t,r=!0){var s=e.reactions;if(s!==null&&!(bt!==null&&Qr.call(bt,e)))for(var i=0;i{e.ac.abort(Dr)}),e.ac=null);try{e.f|=gs;var v=e.fn,m=v();e.f|=Er;var b=e.deps,C=ee==null?void 0:ee.is_fork;if(tt!==null){var E;if(C||Dn(e,lt),b!==null&<>0)for(b.length=lt+tt.length,E=0;Er==null?void 0:r.call(this,o))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?Xt(()=>{t.addEventListener(e,i,s)}):t.addEventListener(e,i,s),i}function Nn(e,t,r,s,i){var o={capture:s,passive:i},l=Bl(e,t,r,o);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&Bi(()=>{t.removeEventListener(e,l,o)})}function Tn(e,t,r){(t[Or]??(t[Or]={}))[e]=r}function io(e){for(var t=0;t{throw ce});throw b}}finally{e[Or]=t,delete e.currentTarget,xt(v),zt(m)}}}const Ls=((Co=globalThis==null?void 0:globalThis.window)==null?void 0:Co.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function Pl(e){return(Ls==null?void 0:Ls.createHTML(e))??e}function lo(e){var t=Qn("template");return t.innerHTML=Pl(e.replaceAll("","")),t.content}function at(e,t){var r=K;r.nodes===null&&(r.nodes={start:e,end:t,a:null,t:null})}function N(e,t){var r=(t&li)!==0,s=(t&Ho)!==0,i,o=!e.startsWith("");return()=>{if(Q)return at(j,null),j;i===void 0&&(i=lo(o?e:""+e),r||(i=Qe(i)));var l=s||Li?document.importNode(i,!0):i.cloneNode(!0);if(r){var f=Qe(l),a=l.lastChild;at(f,a)}else at(l,l);return l}}function zl(e,t,r="svg"){var s=!e.startsWith(""),i=(t&li)!==0,o=`<${r}>${s?e:""+e}`,l;return()=>{if(Q)return at(j,null),j;if(!l){var f=lo(o),a=Qe(f);if(i)for(l=document.createDocumentFragment();Qe(a);)l.appendChild(Qe(a));else l=Qe(a)}var c=l.cloneNode(!0);if(i){var v=Qe(c),m=c.lastChild;at(v,m)}else at(c,c);return c}}function Pe(e,t){return zl(e,t,"svg")}function Hl(e=""){if(!Q){var t=et(e+"");return at(t,t),t}var r=j;return r.nodeType!==bn?(r.before(r=et()),He(r)):Kn(r),at(r,r),r}function sn(){if(Q)return at(j,null),j;var e=document.createDocumentFragment(),t=document.createComment(""),r=et();return e.append(t,r),at(t,r),e}function k(e,t){if(Q){var r=K;((r.f&Er)===0||r.nodes.end===null)&&(r.nodes.end=j),Zr();return}e!==null&&e.before(t)}const Gl=["touchstart","touchmove"];function Vl(e){return Gl.includes(e)}function Y(e,t){var r=t==null?"":typeof t=="object"?`${t}`:t;r!==(e.__t??(e.__t=e.nodeValue))&&(e.__t=r,e.nodeValue=`${r}`)}function ao(e,t){return fo(e,t)}function Wl(e,t){Es(),t.intro=t.intro??!1;const r=t.target,s=Q,i=j;try{for(var o=Qe(r);o&&(o.nodeType!==Xr||o.data!==ai);)o=Rt(o);if(!o)throw wr;Jt(!0),He(o);const l=fo(e,{...t,anchor:o});return Jt(!1),l}catch(l){if(l instanceof Error&&l.message.split(` -`).some(f=>f.startsWith("https://svelte.dev/e/")))throw l;return l!==wr&&console.warn("Failed to hydrate: ",l),t.recover===!1&&ll(),Es(),Ui(r),Jt(!1),ao(e,t)}finally{Jt(s),He(i)}}const Xn=new Map;function fo(e,{target:t,anchor:r,props:s={},events:i,context:o,intro:l=!0,transformError:f}){Es();var a=void 0,c=Il(()=>{var v=r??t.appendChild(et());wl(v,{pending:()=>{}},C=>{Gn({});var E=ge;if(o&&(E.c=o),i&&(s.$$events=i),Q&&at(C,null),a=e(C,s)||{},Q&&(K.nodes.end=j,j===null||j.nodeType!==Xr||j.data!==us))throw wn(),wr;Vn()},f);var m=new Set,b=C=>{for(var E=0;E{var x;for(var C of m)for(const A of[t,document]){var E=Xn.get(A),q=E.get(C);--q==0?(A.removeEventListener(C,Is),E.delete(C),E.size===0&&Xn.delete(A)):E.set(C,q)}Rs.delete(b),v!==r&&((x=v.parentNode)==null||x.removeChild(v))}});return qs.set(a,c),a}let qs=new WeakMap;function Yl(e,t){const r=qs.get(e);return r?(qs.delete(e),r(t)):Promise.resolve()}class Ql{constructor(t,r=!0){ye(this,"anchor");G(this,Ot,new Map);G(this,Wt,new Map);G(this,ut,new Map);G(this,zr,new Set);G(this,Ln,!0);G(this,qn,t=>{if(u(this,Ot).has(t)){var r=u(this,Ot).get(t),s=u(this,Wt).get(r);if(s)Ts(s),u(this,zr).delete(r);else{var i=u(this,ut).get(r);i&&(i.effect.f&We)===0&&(u(this,Wt).set(r,i.effect),u(this,ut).delete(r),i.fragment.lastChild.remove(),this.anchor.before(i.fragment),s=i.effect)}for(const[o,l]of u(this,Ot)){if(u(this,Ot).delete(o),o===t)break;const f=u(this,ut).get(l);f&&(Ke(f.effect),u(this,ut).delete(l))}for(const[o,l]of u(this,Wt)){if(o===r||u(this,zr).has(o)||(l.f&We)!==0)continue;const f=()=>{if(Array.from(u(this,Ot).values()).includes(o)){var c=document.createDocumentFragment();As(l,c),c.append(et()),u(this,ut).set(o,{effect:l,fragment:c})}else Ke(l);u(this,zr).delete(o),u(this,Wt).delete(o)};u(this,Ln)||!s?(u(this,zr).add(o),Rr(l,f,!1)):f()}}});G(this,ns,t=>{u(this,Ot).delete(t);const r=Array.from(u(this,Ot).values());for(const[s,i]of u(this,ut))r.includes(s)||(Ke(i.effect),u(this,ut).delete(s))});this.anchor=t,F(this,Ln,r)}ensure(t,r){var s=ee,i=ji();if(r&&!u(this,Wt).has(t)&&!u(this,ut).has(t))if(i){var o=document.createDocumentFragment(),l=et();o.append(l),u(this,ut).set(t,{effect:mt(()=>r(l)),fragment:o})}else u(this,Wt).set(t,mt(()=>r(this.anchor)));if(u(this,Ot).set(s,t),i){for(const[f,a]of u(this,Wt))f===t?s.unskip_effect(a):s.skip_effect(a);for(const[f,a]of u(this,ut))f===t?s.unskip_effect(a.effect):s.skip_effect(a.effect);s.oncommit(u(this,qn)),s.ondiscard(u(this,ns))}else Q&&(this.anchor=j),u(this,qn).call(this,s)}}Ot=new WeakMap,Wt=new WeakMap,ut=new WeakMap,zr=new WeakMap,Ln=new WeakMap,qn=new WeakMap,ns=new WeakMap;function co(e){ge===null&&mi(),fe&&ge.l!==null?Jl(ge).m.push(e):Cn(()=>{const t=d(e);if(typeof t=="function")return t})}function Kl(e){ge===null&&mi(),co(()=>()=>d(e))}function Jl(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}function te(e,t,r=!1){var s;Q&&(s=j,Zr());var i=new Ql(e),o=r?Jr:0;function l(f,a){if(Q){var c=xi(s);if(f!==parseInt(c.substring(1))){var v=Hn();He(v),i.anchor=v,Jt(!1),i.ensure(f,a),Jt(!0);return}}i.ensure(f,a)}Ds(()=>{var f=!1;t((a,c=0)=>{f=!0,l(c,a)}),f||l(-1,null)},o)}function Zn(e,t){return t}function Xl(e,t,r){for(var s=[],i=t.length,o,l=t.length,f=0;f{if(o){if(o.pending.delete(m),o.done.add(m),o.pending.size===0){var b=e.outrogroups;Os(e,Bn(o.done)),b.delete(o),b.size===0&&(e.outrogroups=null)}}else l-=1},!1)}if(l===0){var a=s.length===0&&r!==null;if(a){var c=r,v=c.parentNode;Ui(v),v.append(c),e.items.clear()}Os(e,t,!a)}else o={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(o)}function Os(e,t,r=!0){var s;if(e.pending.size>0){s=new Set;for(const l of e.pending.values())for(const f of l)s.add(e.items.get(f).e)}for(var i=0;i{var J=r();return ui(J)?J:J==null?[]:Bn(J)}),b,C=new Map,E=!0;function q(J){(ce.effect.f&Ft)===0&&(ce.pending.delete(J),ce.fallback=v,Zl(ce,b,l,t,s),v!==null&&(b.length===0?(v.f&Bt)===0?Ts(v):(v.f^=Bt,Mn(v,null,l)):Rr(v,()=>{v=null})))}function x(J){ce.pending.delete(J)}var A=Ds(()=>{b=n(m);var J=b.length;let z=!1;if(Q){var qe=xi(l)===cs;qe!==(J===0)&&(l=Hn(),He(l),Jt(!1),z=!0)}for(var ue=new Set,pe=ee,st=ji(),Je=0;Jeo(l)):(v=mt(()=>o(uo??(uo=et()))),v.f|=Bt)),J>ue.size&&rl(),Q&&J>0&&He(Hn()),!E)if(C.set(pe,ue),st){for(const[ie,X]of f)ue.has(ie)||pe.skip_effect(X.e);pe.oncommit(q),pe.ondiscard(x)}else q(pe);z&&Jt(!0),n(m)}),ce={effect:A,items:f,pending:C,outrogroups:null,fallback:v};E=!1,Q&&(l=j)}function An(e){for(;e!==null&&(e.f&Tt)===0;)e=e.next;return e}function Zl(e,t,r,s,i){var Et,Ut,M,ie,X,oe,Ne,me,it;var o=(s&Oo)!==0,l=t.length,f=e.items,a=An(e.effect.first),c,v=null,m,b=[],C=[],E,q,x,A;if(o)for(A=0;A0){var Je=(s&oi)!==0&&l===0?r:null;if(o){for(A=0;A{var Ge,mr;if(m!==void 0)for(x of m)(mr=(Ge=x.nodes)==null?void 0:Ge.a)==null||mr.apply()})}function ea(e,t,r,s,i,o,l,f){var a=(l&Lo)!==0?(l&Uo)===0?Mt(r,!1,!1):Ar(r):null,c=(l&qo)!==0?Ar(i):null;return{v:a,i:c,e:mt(()=>(o(t,a??r,c??i,f),()=>{e.delete(s)}))}}function Mn(e,t,r){if(e.nodes)for(var s=e.nodes.start,i=e.nodes.end,o=t&&(t.f&Bt)===0?t.nodes.start:r;s!==null;){var l=Rt(s);if(o.before(s),s===i)return;s=l}}function vr(e,t,r){t===null?e.effect.first=r:t.next=r,r===null?e.effect.last=t:r.prev=t}function ta(e,t,r=!1,s=!1,i=!1){var o=e,l="";B(()=>{var f=K;if(l===(l=t()??"")){Q&&Zr();return}if(f.nodes!==null&&(Gi(f.nodes.start,f.nodes.end),f.nodes=null),l!==""){if(Q){j.data;for(var a=Zr(),c=a;a!==null&&(a.nodeType!==Xr||a.data!=="");)c=a,a=Rt(a);if(a===null)throw wn(),wr;at(j,c),o=He(a);return}var v=r?Go:s?Vo:void 0,m=Qn(r?"svg":s?"math":"template",v);m.innerHTML=l;var b=r||s?m:m.content;if(at(Qe(b),b.lastChild),r||s)for(;Qe(b);)o.before(Qe(b));else o.before(b)}})}function Us(e,t){zi(()=>{var r=e.getRootNode(),s=r.host?r:r.head??r.ownerDocument.head;if(!s.querySelector("#"+t.hash)){const i=Qn("style");i.id=t.hash,i.textContent=t.code,s.appendChild(i)}})}const vo=[...` -\r\f \v\uFEFF`];function ra(e,t,r){var s=e==null?"":""+e;if(t&&(s=s?s+" "+t:t),r){for(var i of Object.keys(r))if(r[i])s=s?s+" "+i:i;else if(s.length)for(var o=i.length,l=0;(l=s.indexOf(i,l))>=0;){var f=l+o;(l===0||vo.includes(s[l-1]))&&(f===s.length||vo.includes(s[f]))?s=(l===0?"":s.substring(0,l))+s.substring(f+1):l=f}}return s===""?null:s}function na(e,t){return e==null?null:String(e)}function he(e,t,r,s,i,o){var l=e.__className;if(Q||l!==r||l===void 0){var f=ra(r,s,o);(!Q||f!==e.getAttribute("class"))&&(f==null?e.removeAttribute("class"):t?e.className=f:e.setAttribute("class",f)),e.__className=r}else if(o&&i!==o)for(var a in o){var c=!!o[a];(i==null||c!==!!i[a])&&e.classList.toggle(a,c)}return o}function Ur(e,t,r,s){var i=e.__style;if(Q||i!==t){var o=na(t);(!Q||o!==e.getAttribute("style"))&&(o==null?e.removeAttribute("style"):e.style.cssText=o),e.__style=t}return s}const sa=Symbol("is custom element"),ia=Symbol("is html"),oa=el?"link":"LINK";function jr(e,t,r,s){var i=la(e);Q&&(i[t]=e.getAttribute(t),t==="src"||t==="srcset"||t==="href"&&e.nodeName===oa)||i[t]!==(i[t]=r)&&(t==="loading"&&(e[Zo]=r),r==null?e.removeAttribute(t):typeof r!="string"&&aa(e).includes(t)?e[t]=r:e.setAttribute(t,r))}function la(e){return e.__attributes??(e.__attributes={[sa]:e.nodeName.includes("-"),[ia]:e.namespaceURI===ci})}var po=new Map;function aa(e){var t=e.getAttribute("is")||e.nodeName,r=po.get(t);if(r)return r;po.set(t,r=[]);for(var s,i=e,o=Element.prototype;o!==i;){s=di(i);for(var l in s)s[l].set&&r.push(l);i=vs(i)}return r}function ho(e,t){return e===t||(e==null?void 0:e[Sr])===t}function _o(e={},t,r,s){return zi(()=>{var i,o;return Sn(()=>{i=o,o=[],d(()=>{e!==r(...o)&&(t(e,...o),i&&ho(r(...i),e)&&t(null,...i))})}),()=>{Xt(()=>{o&&ho(r(...o),e)&&t(null,...o)})}}),e}function fa(e=!1){const t=ge,r=t.l.u;if(!r)return;let s=()=>h(t.s);if(e){let i=0,o={};const l=En(()=>{let f=!1;const a=t.s;for(const c in a)a[c]!==o[c]&&(o[c]=a[c],f=!0);return f&&i++,i});s=()=>n(l)}r.b.length&&Ml(()=>{go(t,s),ps(r.b)}),Cn(()=>{const i=d(()=>r.m.map(Jo));return()=>{for(const o of i)typeof o=="function"&&o()}}),r.a.length&&Cn(()=>{go(t,s),ps(r.a)})}function go(e,t){if(e.l.s)for(const r of e.l.s)n(r);t()}let es=!1;function ca(e){var t=es;try{return es=!1,[e(),es]}finally{es=t}}function Rn(e,t,r,s){var J;var i=!fe||(r&Fo)!==0,o=(r&Po)!==0,l=(r&zo)!==0,f=s,a=!0,c=()=>(a&&(a=!1,f=l?d(s):s),f),v;if(o){var m=Sr in e||gi in e;v=((J=kr(e,t))==null?void 0:J.set)??(m&&t in e?z=>e[t]=z:void 0)}var b,C=!1;o?[b,C]=ca(()=>e[t]):b=e[t],b===void 0&&s!==void 0&&(b=c(),v&&(i&&al(),v(b)));var E;if(i?E=()=>{var z=e[t];return z===void 0?c():(a=!0,z)}:E=()=>{var z=e[t];return z!==void 0&&(f=void 0),z===void 0?f:z},i&&(r&Bo)===0)return E;if(v){var q=e.$$legacy;return(function(z,qe){return arguments.length>0?((!i||!qe||q||C)&&v(qe?E():z),z):E()})}var x=!1,A=((r&jo)!==0?En:Ye)(()=>(x=!1,E()));o&&n(A);var ce=K;return(function(z,qe){if(arguments.length>0){const ue=qe?n(A):i&&o?Mr(z):z;return V(A,ue),x=!0,f!==void 0&&(f=ue),z}return dr&&x||(ce.f&Ft)!==0?A.v:n(A)})}function ua(e){return new da(e)}class da{constructor(t){G(this,rr);G(this,yt);var o;var r=new Map,s=(l,f)=>{var a=Mt(f,!1,!1);return r.set(l,a),a};const i=new Proxy({...t.props||{},$$events:{}},{get(l,f){return n(r.get(f)??s(f,Reflect.get(l,f)))},has(l,f){return f===gi?!0:(n(r.get(f)??s(f,Reflect.get(l,f))),Reflect.has(l,f))},set(l,f,a){return V(r.get(f)??s(f,a),a),Reflect.set(l,f,a)}});F(this,yt,(t.hydrate?Wl:ao)(t.component,{target:t.target,anchor:t.anchor,props:i,context:t.context,intro:t.intro??!1,recover:t.recover,transformError:t.transformError})),(!((o=t==null?void 0:t.props)!=null&&o.$$host)||t.sync===!1)&&Tr(),F(this,rr,i.$$events);for(const l of Object.keys(u(this,yt)))l==="$set"||l==="$destroy"||l==="$on"||zn(this,l,{get(){return u(this,yt)[l]},set(f){u(this,yt)[l]=f},enumerable:!0});u(this,yt).$set=l=>{Object.assign(i,l)},u(this,yt).$destroy=()=>{Yl(u(this,yt))}}$set(t){u(this,yt).$set(t)}$on(t,r){u(this,rr)[t]=u(this,rr)[t]||[];const s=(...i)=>r.call(this,...i);return u(this,rr)[t].push(s),()=>{u(this,rr)[t]=u(this,rr)[t].filter(i=>i!==s)}}$destroy(){u(this,yt).$destroy()}}rr=new WeakMap,yt=new WeakMap;let mo;typeof HTMLElement=="function"&&(mo=class extends HTMLElement{constructor(t,r,s){super();ye(this,"$$ctor");ye(this,"$$s");ye(this,"$$c");ye(this,"$$cn",!1);ye(this,"$$d",{});ye(this,"$$r",!1);ye(this,"$$p_d",{});ye(this,"$$l",{});ye(this,"$$l_u",new Map);ye(this,"$$me");ye(this,"$$shadowRoot",null);this.$$ctor=t,this.$$s=r,s&&(this.$$shadowRoot=this.attachShadow(s))}addEventListener(t,r,s){if(this.$$l[t]=this.$$l[t]||[],this.$$l[t].push(r),this.$$c){const i=this.$$c.$on(t,r);this.$$l_u.set(r,i)}super.addEventListener(t,r,s)}removeEventListener(t,r,s){if(super.removeEventListener(t,r,s),this.$$c){const i=this.$$l_u.get(r);i&&(i(),this.$$l_u.delete(r))}}async connectedCallback(){if(this.$$cn=!0,!this.$$c){let t=function(i){return o=>{const l=Qn("slot");i!=="default"&&(l.name=i),k(o,l)}};if(await Promise.resolve(),!this.$$cn||this.$$c)return;const r={},s=va(this);for(const i of this.$$s)i in s&&(i==="default"&&!this.$$d.children?(this.$$d.children=t(i),r.default=!0):r[i]=t(i));for(const i of this.attributes){const o=this.$$g_p(i.name);o in this.$$d||(this.$$d[o]=ts(o,i.value,this.$$p_d,"toProp"))}for(const i in this.$$p_d)!(i in this.$$d)&&this[i]!==void 0&&(this.$$d[i]=this[i],delete this[i]);this.$$c=ua({component:this.$$ctor,target:this.$$shadowRoot||this,props:{...this.$$d,$$slots:r,$$host:this}}),this.$$me=Rl(()=>{Sn(()=>{var i;this.$$r=!0;for(const o of Pn(this.$$c)){if(!((i=this.$$p_d[o])!=null&&i.reflect))continue;this.$$d[o]=this.$$c[o];const l=ts(o,this.$$d[o],this.$$p_d,"toAttribute");l==null?this.removeAttribute(this.$$p_d[o].attribute||o):this.setAttribute(this.$$p_d[o].attribute||o,l)}this.$$r=!1})});for(const i in this.$$l)for(const o of this.$$l[i]){const l=this.$$c.$on(i,o);this.$$l_u.set(o,l)}this.$$l={}}}attributeChangedCallback(t,r,s){var i;this.$$r||(t=this.$$g_p(t),this.$$d[t]=ts(t,s,this.$$p_d,"toProp"),(i=this.$$c)==null||i.$set({[t]:this.$$d[t]}))}disconnectedCallback(){this.$$cn=!1,Promise.resolve().then(()=>{!this.$$cn&&this.$$c&&(this.$$c.$destroy(),this.$$me(),this.$$c=void 0)})}$$g_p(t){return Pn(this.$$p_d).find(r=>this.$$p_d[r].attribute===t||!this.$$p_d[r].attribute&&r.toLowerCase()===t)||t}});function ts(e,t,r,s){var o;const i=(o=r[e])==null?void 0:o.type;if(t=i==="Boolean"&&typeof t!="boolean"?t!=null:t,!s||!r[e])return t;if(s==="toAttribute")switch(i){case"Object":case"Array":return t==null?null:JSON.stringify(t);case"Boolean":return t?"":null;case"Number":return t??null;default:return t}else switch(i){case"Object":case"Array":return t&&JSON.parse(t);case"Boolean":return t;case"Number":return t!=null?+t:t;default:return t}}function va(e){const t={};return e.childNodes.forEach(r=>{t[r.slot||"default"]=!0}),t}function js(e,t,r,s,i,o){let l=class extends mo{constructor(){super(e,r,i),this.$$p_d=t}static get observedAttributes(){return Pn(t).map(f=>(t[f].attribute||f).toLowerCase())}};return Pn(t).forEach(f=>{zn(l.prototype,f,{get(){return this.$$c&&f in this.$$c?this.$$c[f]:this.$$d[f]},set(a){var m;a=ts(f,a,t),this.$$d[f]=a;var c=this.$$c;if(c){var v=(m=kr(c,f))==null?void 0:m.get;v?c[f]=a:c.$set({[f]:a})}}})}),s.forEach(f=>{zn(l.prototype,f,{get(){var a;return(a=this.$$c)==null?void 0:a[f]}})}),e.element=l,l}async function xo(e,t){const r=t?`/api/orgs/${e}/projects/${t}/timeline`:`/api/orgs/${e}/timeline`,s=await fetch(r,{credentials:"same-origin"});if(!s.ok)throw new Error(`Timeline fetch failed: ${s.status}`);return s.json()}function pa(e,t,r){const s=t?`/orgs/${e}/projects/${t}/events`:`/orgs/${e}/events`;let i=1e3,o=null,l=!1;function f(){if(!l){o=new EventSource(s),o.addEventListener("open",()=>{i=1e3});for(const a of["destination","release","artifact","pipeline"])o.addEventListener(a,c=>{try{const v=JSON.parse(c.data);r(a,v)}catch(v){console.warn(`[release-timeline] bad ${a} event:`,v)}});o.addEventListener("error",()=>{o.close(),l||(setTimeout(f,i),i=Math.min(i*2,3e4))})}}return f(),()=>{l=!0,o&&o.close()}}function bo(e){if(e<0&&(e=0),e<60)return`${e}s`;const t=Math.floor(e/60),r=e%60;return t<60?`${t}m ${r}s`:`${Math.floor(t/60)}h ${t%60}m`}function on(e){if(!e)return"";const t=new Date(e),r=Date.now(),s=Math.floor((r-t.getTime())/1e3);return s<10?"just now":s<60?`${s}s ago`:s<3600?`${Math.floor(s/60)}m ago`:s<86400?`${Math.floor(s/3600)}h ago`:`${Math.floor(s/86400)}d ago`}const Fs={prod:["#ec4899","#fce7f3"],production:["#ec4899","#fce7f3"],preprod:["#f97316","#ffedd5"],"pre-prod":["#f97316","#ffedd5"],staging:["#eab308","#fef9c3"],stage:["#eab308","#fef9c3"],dev:["#8b5cf6","#ede9fe"],development:["#8b5cf6","#ede9fe"],test:["#06b6d4","#cffafe"]},ha=["#6b7280","#e5e7eb"];function _a(e){const t=e.toLowerCase();if(Fs[t])return Fs[t];for(const[r,s]of Object.entries(Fs))if(t.includes(r))return s;return ha}function pr(e){const t=e.toLowerCase();return t.includes("prod")&&!t.includes("preprod")&&!t.includes("pre-prod")?{bg:"bg-pink-100 text-pink-800",dot:"bg-pink-500"}:t.includes("preprod")||t.includes("pre-prod")?{bg:"bg-orange-100 text-orange-800",dot:"bg-orange-500"}:t.includes("stag")?{bg:"bg-yellow-100 text-yellow-800",dot:"bg-yellow-500"}:t.includes("dev")?{bg:"bg-violet-100 text-violet-800",dot:"bg-violet-500"}:{bg:"bg-gray-100 text-gray-700",dot:"bg-gray-400"}}function wo(e){switch(e){case"SUCCEEDED":return"bg-green-500";case"RUNNING":return"bg-yellow-500";case"FAILED":return"bg-red-500";default:return null}}const Bs={SUCCEEDED:{label:"Deployed to",stageLabel:"Deployed to",color:"text-green-600",icon:"check-circle",iconColor:"text-green-500"},RUNNING:{label:"Deploying to",stageLabel:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500"},ASSIGNED:{label:"Deploying to",stageLabel:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500"},QUEUED:{label:"Queued for",stageLabel:"Queued for",color:"text-blue-600",icon:"clock",iconColor:"text-blue-400"},FAILED:{label:"Failed on",stageLabel:"Failed on",color:"text-red-600",icon:"x-circle",iconColor:"text-red-500"},TIMED_OUT:{label:"Timed out on",stageLabel:"Timed out on",color:"text-orange-600",icon:"clock",iconColor:"text-orange-500"},CANCELLED:{label:"Cancelled",stageLabel:"Cancelled",color:"text-gray-500",icon:"ban",iconColor:"text-gray-400"}};function ln(e){if(!e||e.length===0)return null;let t=!0,r=!1,s=!1,i=!1,o=!1,l=0;const f=e.length;for(const a of e)a.status==="SUCCEEDED"&&l++,a.status!=="SUCCEEDED"&&(t=!1),a.status==="FAILED"&&(r=!0),a.status==="RUNNING"&&(s=!0),a.status==="QUEUED"&&(o=!0),a.stage_type==="wait"&&a.status==="RUNNING"&&(i=!0);return t?{label:"Pipeline complete",color:"text-gray-600",icon:"check-circle",iconColor:"text-green-500",done:l,total:f}:r?{label:"Pipeline failed",color:"text-red-600",icon:"x-circle",iconColor:"text-red-500",done:l,total:f}:i?{label:"Waiting for time window",color:"text-yellow-700",icon:"clock",iconColor:"text-yellow-500",done:l,total:f}:s?{label:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500",done:l,total:f}:o?{label:"Queued",color:"text-blue-600",icon:"clock",iconColor:"text-blue-400",done:l,total:f}:{label:"Pipeline pending",color:"text-gray-400",icon:"pending",iconColor:"text-gray-300",done:l,total:f}}function ko(e){switch(e){case"SUCCEEDED":return"Waited";case"RUNNING":return"Waiting";case"FAILED":return"Wait failed";case"CANCELLED":return"Wait cancelled";default:return"Wait"}}function yo(e){switch(e){case"SUCCEEDED":return"Deployed to";case"RUNNING":return"Deploying to";case"QUEUED":return"Queued for";case"FAILED":return"Failed on";case"TIMED_OUT":return"Timed out on";case"CANCELLED":return"Cancelled";default:return"Deploy to"}}var ga=N('

Loading releases...

'),ma=N('

'),xa=N('

No releases yet.

Create a release with forest release create

'),ba=N('
'),wa=N('
'),ka=N('
'),ya=N(" ",1),Ea=N('
'),$a=N(' '),Ca=N(' '),Sa=N(' '),Da=N(' '),Na=N(' Deployed',1),Ta=N(' Queued',1),Aa=Pe('',1),Ma=N(''),Ra=Pe(''),Ia=Pe(''),La=Pe(''),qa=Pe(''),Oa=N(" "),Ua=N(' ',1),ja=N(' Deployed',1),Fa=N(''),Ba=Pe(''),Pa=Pe(''),za=N(" "),Ha=N(" ",1),Ga=N(' Pending',1),Va=N('

'),Wa=N(' '),Ya=Pe(''),Qa=N(''),Ka=Pe(''),Ja=Pe(''),Xa=Pe(''),Za=N(" ",1),ef=N(" "),tf=N(' '),rf=N('
pipeline
'),nf=N('
'),sf=Pe(''),of=N(''),lf=Pe(''),af=Pe(''),ff=Pe(''),cf=N('Deployed'),uf=N('Deploying'),df=N(' '),vf=N('Failed'),pf=N(''),hf=N('
'),_f=N(''),gf=N(' '),mf=N(''),xf=N('
·
'),bf=N('
'),wf=N('
');const kf={hash:"svelte-4kxpm1",code:` +var gc=Object.defineProperty;var Gl=_e=>{throw TypeError(_e)};var mc=(_e,de,Me)=>de in _e?gc(_e,de,{enumerable:!0,configurable:!0,writable:!0,value:Me}):_e[de]=Me;var ke=(_e,de,Me)=>mc(_e,typeof de!="symbol"?de+"":de,Me),po=(_e,de,Me)=>de.has(_e)||Gl("Cannot "+Me);var u=(_e,de,Me)=>(po(_e,de,"read from private field"),Me?Me.call(_e):de.get(_e)),W=(_e,de,Me)=>de.has(_e)?Gl("Cannot add the same private member more than once"):de instanceof WeakSet?de.add(_e):de.set(_e,Me),G=(_e,de,Me,Jn)=>(po(_e,de,"write to private field"),Jn?Jn.call(_e,Me):de.set(_e,Me),Me),ye=(_e,de,Me)=>(po(_e,de,"access private method"),Me);(function(){"use strict";var jl,Bl,br,_n,Gr,gn,mn,xn,wr,Yt,bn,nt,ho,_o,go,mo,dt,Hn,Qt,Vr,st,Kt,vt,Ot,nr,Wr,kr,wn,kn,yn,sr,vs,we,Vl,Wl,Yl,xo,xs,bs,bo,Fl,Ut,Jt,pt,Yr,Gn,Vn,ps,or,St;typeof window<"u"&&((jl=window.__svelte??(window.__svelte={})).v??(jl.v=new Set)).add("5");let de=!1,Me=!1;function Jn(){de=!0}Jn();const Ql=1,Kl=2,wo=4,Jl=8,Xl=16,Zl=1,ei=2,ti=4,ri=8,ni=16,ko=1,si=2,yo="[",ws="[!",Eo="[?",ks="]",Sr={},Ue=Symbol(),$o="http://www.w3.org/1999/xhtml",oi="http://www.w3.org/2000/svg",li="http://www.w3.org/1998/Math/MathML",ys=!1;var Co=Array.isArray,ii=Array.prototype.indexOf,sn=Array.prototype.includes,Xn=Array.from,Zn=Object.keys,es=Object.defineProperty,Dr=Object.getOwnPropertyDescriptor,So=Object.getOwnPropertyDescriptors,ai=Object.prototype,fi=Array.prototype,Es=Object.getPrototypeOf,Do=Object.isExtensible;const ci=()=>{};function ui(e){return e()}function $s(e){for(var t=0;t{e=s,t=o});return{promise:r,resolve:e,reject:t}}const je=2,on=4,Nr=8,Cs=1<<24,fr=16,Mt=32,cr=64,Ss=128,bt=512,Re=1024,Be=2048,wt=4096,Ge=8192,Pt=16384,Tr=32768,ln=65536,To=1<<17,di=1<<18,Ar=1<<19,Ao=1<<20,zt=1<<25,Mr=65536,Ds=1<<21,Ns=1<<22,ur=1<<23,Rr=Symbol("$state"),Mo=Symbol("legacy props"),vi=Symbol(""),Ir=new class extends Error{constructor(){super(...arguments);ke(this,"name","StaleReactionError");ke(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}},pi=!!((Bl=globalThis.document)!=null&&Bl.contentType)&&globalThis.document.contentType.includes("xml"),An=3,an=8;function Ro(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function hi(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function _i(e,t,r){throw new Error("https://svelte.dev/e/each_key_duplicate")}function gi(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function mi(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function xi(e){throw new Error("https://svelte.dev/e/effect_orphan")}function bi(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function wi(){throw new Error("https://svelte.dev/e/hydration_failed")}function ki(e){throw new Error("https://svelte.dev/e/props_invalid_value")}function yi(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function Ei(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function $i(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function Ci(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}function Mn(e){console.warn("https://svelte.dev/e/hydration_mismatch")}function Si(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}let X=!1;function er(e){X=e}let F;function He(e){if(e===null)throw Mn(),Sr;return F=e}function fn(){return He(It(F))}function y(e){if(X){if(It(F)!==null)throw Mn(),Sr;F=e}}function dr(e=1){if(X){for(var t=e,r=F;t--;)r=It(r);F=r}}function ts(e=!0){for(var t=0,r=F;;){if(r.nodeType===an){var s=r.data;if(s===ks){if(t===0)return r;t-=1}else(s===yo||s===ws||s[0]==="["&&!isNaN(Number(s.slice(1))))&&(t+=1)}var o=It(r);e&&r.remove(),r=o}}function Io(e){if(!e||e.nodeType!==an)throw Mn(),Sr;return e.data}function Lo(e){return e===this.v}function Di(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function qo(e){return!Di(e,this.v)}let ge=null;function cn(e){ge=e}function rs(e,t=!1,r){ge={p:ge,i:!1,c:null,e:null,s:e,x:null,l:de&&!t?{s:null,u:null,$:[]}:null}}function ns(e){var t=ge,r=t.e;if(r!==null){t.e=null;for(var s of r)nl(s)}return e!==void 0&&(t.x=e),t.i=!0,ge=t.p,e??{}}function Rn(){return!de||ge!==null&&ge.l===null}let Lr=[];function Oo(){var e=Lr;Lr=[],$s(e)}function tr(e){if(Lr.length===0&&!In){var t=Lr;queueMicrotask(()=>{t===Lr&&Oo()})}Lr.push(e)}function Ni(){for(;Lr.length>0;)Oo()}function Uo(e){var t=Z;if(t===null)return Y.f|=ur,e;if((t.f&Tr)===0&&(t.f&on)===0)throw e;vr(e,t)}function vr(e,t){for(;t!==null;){if((t.f&Ss)!==0){if((t.f&Tr)===0)throw e;try{t.b.error(e);return}catch(r){e=r}}t=t.parent}throw e}const Ti=-7169;function be(e,t){e.f=e.f&Ti|t}function Ts(e){(e.f&bt)!==0||e.deps===null?be(e,Re):be(e,wt)}function jo(e){if(e!==null)for(const t of e)(t.f&je)===0||(t.f&Mr)===0||(t.f^=Mr,jo(t.deps))}function Bo(e,t,r){(e.f&Be)!==0?t.add(e):(e.f&wt)!==0&&r.add(e),jo(e.deps),be(e,Re)}const ss=new Set;let ie=null,Fe=null,et=[],os=null,In=!1,un=null,Ai=1;const Zs=class Zs{constructor(){W(this,nt);ke(this,"id",Ai++);ke(this,"current",new Map);ke(this,"previous",new Map);W(this,br,new Set);W(this,_n,new Set);W(this,Gr,0);W(this,gn,0);W(this,mn,null);W(this,xn,new Set);W(this,wr,new Set);W(this,Yt,new Map);ke(this,"is_fork",!1);W(this,bn,!1)}skip_effect(t){u(this,Yt).has(t)||u(this,Yt).set(t,{d:[],m:[]})}unskip_effect(t){var r=u(this,Yt).get(t);if(r){u(this,Yt).delete(t);for(var s of r.d)be(s,Be),Gt(s);for(s of r.m)be(s,wt),Gt(s)}}process(t){var o;et=[],this.apply();var r=un=[],s=[];for(const l of t)ye(this,nt,_o).call(this,l,r,s);if(un=null,ye(this,nt,ho).call(this)){ye(this,nt,go).call(this,s),ye(this,nt,go).call(this,r);for(const[l,i]of u(this,Yt))Go(l,i)}else{ie=null;for(const l of u(this,br))l(this);u(this,br).clear(),u(this,Gr)===0&&ye(this,nt,mo).call(this),Po(s),Po(r),u(this,xn).clear(),u(this,wr).clear(),(o=u(this,mn))==null||o.resolve()}Fe=null}capture(t,r){r!==Ue&&!this.previous.has(t)&&this.previous.set(t,r),(t.f&ur)===0&&(this.current.set(t,t.v),Fe==null||Fe.set(t,t.v))}activate(){ie=this,this.apply()}deactivate(){ie===this&&(ie=null,Fe=null)}flush(){var t;if(et.length>0)ie=this,Fo();else if(u(this,Gr)===0&&!this.is_fork){for(const r of u(this,br))r(this);u(this,br).clear(),ye(this,nt,mo).call(this),(t=u(this,mn))==null||t.resolve()}this.deactivate()}discard(){for(const t of u(this,_n))t(this);u(this,_n).clear()}increment(t){G(this,Gr,u(this,Gr)+1),t&&G(this,gn,u(this,gn)+1)}decrement(t){G(this,Gr,u(this,Gr)-1),t&&G(this,gn,u(this,gn)-1),!u(this,bn)&&(G(this,bn,!0),tr(()=>{G(this,bn,!1),ye(this,nt,ho).call(this)?et.length>0&&this.flush():this.revive()}))}revive(){for(const t of u(this,xn))u(this,wr).delete(t),be(t,Be),Gt(t);for(const t of u(this,wr))be(t,wt),Gt(t);this.flush()}oncommit(t){u(this,br).add(t)}ondiscard(t){u(this,_n).add(t)}settled(){return(u(this,mn)??G(this,mn,No())).promise}static ensure(){if(ie===null){const t=ie=new Zs;ss.add(ie),In||tr(()=>{ie===t&&t.flush()})}return ie}apply(){}};br=new WeakMap,_n=new WeakMap,Gr=new WeakMap,gn=new WeakMap,mn=new WeakMap,xn=new WeakMap,wr=new WeakMap,Yt=new WeakMap,bn=new WeakMap,nt=new WeakSet,ho=function(){return this.is_fork||u(this,gn)>0},_o=function(t,r,s){t.f^=Re;for(var o=t.first;o!==null;){var l=o.f,i=(l&(Mt|cr))!==0,a=i&&(l&Re)!==0,f=(l&Ge)!==0,c=a||u(this,Yt).has(o);if(!c&&o.fn!==null){i?f||(o.f^=Re):(l&on)!==0?r.push(o):(l&(Nr|Cs))!==0&&f?s.push(o):vn(o)&&(Fr(o),(l&fr)!==0&&(u(this,wr).add(o),f&&be(o,Be)));var v=o.first;if(v!==null){o=v;continue}}for(;o!==null;){var m=o.next;if(m!==null){o=m;break}o=o.parent}}},go=function(t){for(var r=0;r1){this.previous.clear();var t=ie,r=Fe,s=!0;for(const i of ss){if(i===this){s=!1;continue}const a=[];for(const[c,v]of this.current){if(i.current.has(c))if(s&&v!==i.current.get(c))i.current.set(c,v);else continue;a.push(c)}if(a.length===0)continue;const f=[...i.current.keys()].filter(c=>!this.current.has(c));if(f.length>0){var o=et;et=[];const c=new Set,v=new Map;for(const m of a)zo(m,f,c,v);if(et.length>0){ie=i,i.apply();for(const m of et)ye(l=i,nt,_o).call(l,m,[],[]);i.deactivate()}et=o}}ie=t,Fe=r}u(this,Yt).clear(),ss.delete(this)};let rr=Zs;function Ht(e){var t=In;In=!0;try{for(var r;;){if(Ni(),et.length===0&&(ie==null||ie.flush(),et.length===0))return os=null,r;Fo()}}finally{In=t}}function Fo(){var e=null;try{for(var t=0;et.length>0;){var r=rr.ensure();if(t++>1e3){var s,o;Mi()}r.process(et),pr.clear()}}finally{et=[],os=null,un=null}}function Mi(){try{bi()}catch(e){vr(e,os)}}let Rt=null;function Po(e){var t=e.length;if(t!==0){for(var r=0;r0)){pr.clear();for(const o of Rt){if((o.f&(Pt|Ge))!==0)continue;const l=[o];let i=o.parent;for(;i!==null;)Rt.has(i)&&(Rt.delete(i),l.push(i)),i=i.parent;for(let a=l.length-1;a>=0;a--){const f=l[a];(f.f&(Pt|Ge))===0&&Fr(f)}}Rt.clear()}}Rt=null}}function zo(e,t,r,s){if(!r.has(e)&&(r.add(e),e.reactions!==null))for(const o of e.reactions){const l=o.f;(l&je)!==0?zo(o,t,r,s):(l&(Ns|fr))!==0&&(l&Be)===0&&Ho(o,t,s)&&(be(o,Be),Gt(o))}}function Ho(e,t,r){const s=r.get(e);if(s!==void 0)return s;if(e.deps!==null)for(const o of e.deps){if(sn.call(t,o))return!0;if((o.f&je)!==0&&Ho(o,t,r))return r.set(o,!0),!0}return r.set(e,!1),!1}function Gt(e){var t=os=e,r=t.b;if(r!=null&&r.is_pending&&(e.f&(on|Nr|Cs))!==0&&(e.f&Tr)===0){r.defer_effect(e);return}for(;t.parent!==null;){t=t.parent;var s=t.f;if(un!==null&&t===Z&&(e.f&Nr)===0)return;if((s&(cr|Mt))!==0){if((s&Re)===0)return;t.f^=Re}}et.push(t)}function Go(e,t){if(!((e.f&Mt)!==0&&(e.f&Re)!==0)){(e.f&Be)!==0?t.d.push(e):(e.f&wt)!==0&&t.m.push(e),be(e,Re);for(var r=e.first;r!==null;)Go(r,t),r=r.next}}function Ri(e){let t=0,r=qr(0),s;return()=>{Os()&&(n(r),Un(()=>(t===0&&(s=d(()=>e(()=>qn(r)))),t+=1,()=>{tr(()=>{t-=1,t===0&&(s==null||s(),s=void 0,qn(r))})})))}}var Ii=ln|Ar;function Li(e,t,r,s){new qi(e,t,r,s)}class qi{constructor(t,r,s,o){W(this,we);ke(this,"parent");ke(this,"is_pending",!1);ke(this,"transform_error");W(this,dt);W(this,Hn,X?F:null);W(this,Qt);W(this,Vr);W(this,st);W(this,Kt,null);W(this,vt,null);W(this,Ot,null);W(this,nr,null);W(this,Wr,0);W(this,kr,0);W(this,wn,!1);W(this,kn,new Set);W(this,yn,new Set);W(this,sr,null);W(this,vs,Ri(()=>(G(this,sr,qr(u(this,Wr))),()=>{G(this,sr,null)})));var l;G(this,dt,t),G(this,Qt,r),G(this,Vr,i=>{var a=Z;a.b=this,a.f|=Ss,s(i)}),this.parent=Z.b,this.transform_error=o??((l=this.parent)==null?void 0:l.transform_error)??(i=>i),G(this,st,js(()=>{if(X){const i=u(this,Hn);fn();const a=i.data===ws;if(i.data.startsWith(Eo)){const c=JSON.parse(i.data.slice(Eo.length));ye(this,we,Wl).call(this,c)}else a?ye(this,we,Yl).call(this):ye(this,we,Vl).call(this)}else ye(this,we,xo).call(this)},Ii)),X&&G(this,dt,F)}defer_effect(t){Bo(t,u(this,kn),u(this,yn))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!u(this,Qt).pending}update_pending_count(t){ye(this,we,bo).call(this,t),G(this,Wr,u(this,Wr)+t),!(!u(this,sr)||u(this,wn))&&(G(this,wn,!0),tr(()=>{G(this,wn,!1),u(this,sr)&&dn(u(this,sr),u(this,Wr))}))}get_effect_pending(){return u(this,vs).call(this),n(u(this,sr))}error(t){var r=u(this,Qt).onerror;let s=u(this,Qt).failed;if(!r&&!s)throw t;u(this,Kt)&&(Ye(u(this,Kt)),G(this,Kt,null)),u(this,vt)&&(Ye(u(this,vt)),G(this,vt,null)),u(this,Ot)&&(Ye(u(this,Ot)),G(this,Ot,null)),X&&(He(u(this,Hn)),dr(),He(ts()));var o=!1,l=!1;const i=()=>{if(o){Si();return}o=!0,l&&Ci(),u(this,Ot)!==null&&Ur(u(this,Ot),()=>{G(this,Ot,null)}),ye(this,we,bs).call(this,()=>{rr.ensure(),ye(this,we,xo).call(this)})},a=f=>{try{l=!0,r==null||r(f,i),l=!1}catch(c){vr(c,u(this,st)&&u(this,st).parent)}s&&G(this,Ot,ye(this,we,bs).call(this,()=>{rr.ensure();try{return kt(()=>{var c=Z;c.b=this,c.f|=Ss,s(u(this,dt),()=>f,()=>i)})}catch(c){return vr(c,u(this,st).parent),null}}))};tr(()=>{var f;try{f=this.transform_error(t)}catch(c){vr(c,u(this,st)&&u(this,st).parent);return}f!==null&&typeof f=="object"&&typeof f.then=="function"?f.then(a,c=>vr(c,u(this,st)&&u(this,st).parent)):a(f)})}}dt=new WeakMap,Hn=new WeakMap,Qt=new WeakMap,Vr=new WeakMap,st=new WeakMap,Kt=new WeakMap,vt=new WeakMap,Ot=new WeakMap,nr=new WeakMap,Wr=new WeakMap,kr=new WeakMap,wn=new WeakMap,kn=new WeakMap,yn=new WeakMap,sr=new WeakMap,vs=new WeakMap,we=new WeakSet,Vl=function(){try{G(this,Kt,kt(()=>u(this,Vr).call(this,u(this,dt))))}catch(t){this.error(t)}},Wl=function(t){const r=u(this,Qt).failed;r&&G(this,Ot,kt(()=>{r(u(this,dt),()=>t,()=>()=>{})}))},Yl=function(){const t=u(this,Qt).pending;t&&(this.is_pending=!0,G(this,vt,kt(()=>t(u(this,dt)))),tr(()=>{var r=G(this,nr,document.createDocumentFragment()),s=tt();r.append(s),G(this,Kt,ye(this,we,bs).call(this,()=>(rr.ensure(),kt(()=>u(this,Vr).call(this,s))))),u(this,kr)===0&&(u(this,dt).before(r),G(this,nr,null),Ur(u(this,vt),()=>{G(this,vt,null)}),ye(this,we,xs).call(this))}))},xo=function(){try{if(this.is_pending=this.has_pending_snippet(),G(this,kr,0),G(this,Wr,0),G(this,Kt,kt(()=>{u(this,Vr).call(this,u(this,dt))})),u(this,kr)>0){var t=G(this,nr,document.createDocumentFragment());Ps(u(this,Kt),t);const r=u(this,Qt).pending;G(this,vt,kt(()=>r(u(this,dt))))}else ye(this,we,xs).call(this)}catch(r){this.error(r)}},xs=function(){this.is_pending=!1;for(const t of u(this,kn))be(t,Be),Gt(t);for(const t of u(this,yn))be(t,wt),Gt(t);u(this,kn).clear(),u(this,yn).clear()},bs=function(t){var r=Z,s=Y,o=ge;Wt(u(this,st)),yt(u(this,st)),cn(u(this,st).ctx);try{return t()}catch(l){return Uo(l),null}finally{Wt(r),yt(s),cn(o)}},bo=function(t){var r;if(!this.has_pending_snippet()){this.parent&&ye(r=this.parent,we,bo).call(r,t);return}G(this,kr,u(this,kr)+t),u(this,kr)===0&&(ye(this,we,xs).call(this),u(this,vt)&&Ur(u(this,vt),()=>{G(this,vt,null)}),u(this,nr)&&(u(this,dt).before(u(this,nr)),G(this,nr,null)))};function Oi(e,t,r,s){const o=Rn()?Ln:Ve;var l=e.filter(m=>!m.settled);if(r.length===0&&l.length===0){s(t.map(o));return}var i=Z,a=Ui(),f=l.length===1?l[0].promise:l.length>1?Promise.all(l.map(m=>m.promise)):null;function c(m){a();try{s(m)}catch(b){(i.f&Pt)===0&&vr(b,i)}As()}if(r.length===0){f.then(()=>c(t.map(o)));return}function v(){a(),Promise.all(r.map(m=>Bi(m))).then(m=>c([...t.map(o),...m])).catch(m=>vr(m,i))}f?f.then(v):v()}function Ui(){var e=Z,t=Y,r=ge,s=ie;return function(l=!0){Wt(e),yt(t),cn(r),l&&(s==null||s.activate())}}function As(e=!0){Wt(null),yt(null),cn(null),e&&(ie==null||ie.deactivate())}function ji(){var e=Z.b,t=ie,r=e.is_rendered();return e.update_pending_count(1),t.increment(r),()=>{e.update_pending_count(-1),t.decrement(r)}}function Ln(e){var t=je|Be,r=Y!==null&&(Y.f&je)!==0?Y:null;return Z!==null&&(Z.f|=Ar),{ctx:ge,deps:null,effects:null,equals:Lo,f:t,fn:e,reactions:null,rv:0,v:Ue,wv:0,parent:r??Z,ac:null}}function Bi(e,t,r){Z===null&&hi();var o=void 0,l=qr(Ue),i=!Y,a=new Map;return Ki(()=>{var b;var f=No();o=f.promise;try{Promise.resolve(e()).then(f.resolve,f.reject).finally(As)}catch(S){f.reject(S),As()}var c=ie;if(i){var v=ji();(b=a.get(c))==null||b.reject(Ir),a.delete(c),a.set(c,f)}const m=(S,$=void 0)=>{if(c.activate(),$)$!==Ir&&(l.f|=ur,dn(l,$));else{(l.f&ur)!==0&&(l.f^=ur),dn(l,S);for(const[B,x]of a){if(a.delete(B),B===c)break;x.reject(Ir)}}v&&v()};f.promise.then(m,S=>m(null,S||"unknown"))}),rl(()=>{for(const f of a.values())f.reject(Ir)}),new Promise(f=>{function c(v){function m(){v===o?f(l):c(o)}v.then(m,m)}c(o)})}function Vt(e){const t=Ln(e);return ul(t),t}function Ve(e){const t=Ln(e);return t.equals=qo,t}function Fi(e){var t=e.effects;if(t!==null){e.effects=null;for(var r=0;r0&&!Yo&&Hi()}return t}function Hi(){Yo=!1;for(const e of Rs)(e.f&Re)!==0&&be(e,wt),vn(e)&&Fr(e);Rs.clear()}function qn(e){U(e,e.v+1)}function Qo(e,t){var r=e.reactions;if(r!==null)for(var s=Rn(),o=r.length,l=0;l{if(Br===l)return a();var f=Y,c=Br;yt(null),vl(l);var v=a();return yt(f),vl(c),v};return s&&r.set("length",Ie(e.length)),new Proxy(e,{defineProperty(a,f,c){(!("value"in c)||c.configurable===!1||c.enumerable===!1||c.writable===!1)&&yi();var v=r.get(f);return v===void 0?i(()=>{var m=Ie(c.value);return r.set(f,m),m}):U(v,c.value,!0),!0},deleteProperty(a,f){var c=r.get(f);if(c===void 0){if(f in a){const v=i(()=>Ie(Ue));r.set(f,v),qn(o)}}else U(c,Ue),qn(o);return!0},get(a,f,c){var S;if(f===Rr)return e;var v=r.get(f),m=f in a;if(v===void 0&&(!m||(S=Dr(a,f))!=null&&S.writable)&&(v=i(()=>{var $=Or(m?a[f]:Ue),B=Ie($);return B}),r.set(f,v)),v!==void 0){var b=n(v);return b===Ue?void 0:b}return Reflect.get(a,f,c)},getOwnPropertyDescriptor(a,f){var c=Reflect.getOwnPropertyDescriptor(a,f);if(c&&"value"in c){var v=r.get(f);v&&(c.value=n(v))}else if(c===void 0){var m=r.get(f),b=m==null?void 0:m.v;if(m!==void 0&&b!==Ue)return{enumerable:!0,configurable:!0,value:b,writable:!0}}return c},has(a,f){var b;if(f===Rr)return!0;var c=r.get(f),v=c!==void 0&&c.v!==Ue||Reflect.has(a,f);if(c!==void 0||Z!==null&&(!v||(b=Dr(a,f))!=null&&b.writable)){c===void 0&&(c=i(()=>{var S=v?Or(a[f]):Ue,$=Ie(S);return $}),r.set(f,c));var m=n(c);if(m===Ue)return!1}return v},set(a,f,c,v){var ee;var m=r.get(f),b=f in a;if(s&&f==="length")for(var S=c;SIe(Ue)),r.set(S+"",$))}if(m===void 0)(!b||(ee=Dr(a,f))!=null&&ee.writable)&&(m=i(()=>Ie(void 0)),U(m,Or(c)),r.set(f,m));else{b=m.v!==Ue;var B=i(()=>Or(c));U(m,B)}var x=Reflect.getOwnPropertyDescriptor(a,f);if(x!=null&&x.set&&x.set.call(v,c),!b){if(s&&typeof f=="string"){var A=r.get("length"),ae=Number(f);Number.isInteger(ae)&&ae>=A.v&&U(A,ae+1)}qn(o)}return!0},ownKeys(a){n(o);var f=Reflect.ownKeys(a).filter(m=>{var b=r.get(m);return b===void 0||b.v!==Ue});for(var[c,v]of r)v.v!==Ue&&!(c in a)&&f.push(c);return f},setPrototypeOf(){Ei()}})}var Is,Ko,Jo,Xo;function Ls(){if(Is===void 0){Is=window,Ko=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,r=Text.prototype;Jo=Dr(t,"firstChild").get,Xo=Dr(t,"nextSibling").get,Do(e)&&(e.__click=void 0,e.__className=void 0,e.__attributes=null,e.__style=void 0,e.__e=void 0),Do(r)&&(r.__t=void 0)}}function tt(e=""){return document.createTextNode(e)}function We(e){return Jo.call(e)}function It(e){return Xo.call(e)}function E(e,t){if(!X)return We(e);var r=We(F);if(r===null)r=F.appendChild(tt());else if(t&&r.nodeType!==An){var s=tt();return r==null||r.before(s),He(s),s}return t&&is(r),He(r),r}function ft(e,t=!1){if(!X){var r=We(e);return r instanceof Comment&&r.data===""?It(r):r}if(t){if((F==null?void 0:F.nodeType)!==An){var s=tt();return F==null||F.before(s),He(s),s}is(F)}return F}function D(e,t=1,r=!1){let s=X?F:e;for(var o;t--;)o=s,s=It(s);if(!X)return s;if(r){if((s==null?void 0:s.nodeType)!==An){var l=tt();return s===null?o==null||o.after(l):s.before(l),He(l),l}is(s)}return He(s),s}function Zo(e){e.textContent=""}function el(){return!1}function ls(e,t,r){return document.createElementNS(t??$o,e,void 0)}function is(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===An;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function qs(e){var t=Y,r=Z;yt(null),Wt(null);try{return e()}finally{yt(t),Wt(r)}}function tl(e){Z===null&&(Y===null&&xi(),mi()),hr&&gi()}function Gi(e,t){var r=t.last;r===null?t.last=t.first=e:(r.next=e,e.prev=r,t.last=e)}function Lt(e,t){var r=Z;r!==null&&(r.f&Ge)!==0&&(e|=Ge);var s={ctx:ge,deps:null,nodes:null,f:e|Be|bt,first:null,fn:t,last:null,next:null,parent:r,b:r&&r.b,prev:null,teardown:null,wv:0,ac:null},o=s;if((e&on)!==0)un!==null?un.push(s):Gt(s);else if(t!==null){try{Fr(s)}catch(i){throw Ye(s),i}o.deps===null&&o.teardown===null&&o.nodes===null&&o.first===o.last&&(o.f&Ar)===0&&(o=o.first,(e&fr)!==0&&(e&ln)!==0&&o!==null&&(o.f|=ln))}if(o!==null&&(o.parent=r,r!==null&&Gi(o,r),Y!==null&&(Y.f&je)!==0&&(e&cr)===0)){var l=Y;(l.effects??(l.effects=[])).push(o)}return s}function Os(){return Y!==null&&!qt}function rl(e){const t=Lt(Nr,null);return be(t,Re),t.teardown=e,t}function On(e){tl();var t=Z.f,r=!Y&&(t&Mt)!==0&&(t&Tr)===0;if(r){var s=ge;(s.e??(s.e=[])).push(e)}else return nl(e)}function nl(e){return Lt(on|Ao,e)}function Vi(e){return tl(),Lt(Nr|Ao,e)}function Wi(e){rr.ensure();const t=Lt(cr|Ar,e);return()=>{Ye(t)}}function Yi(e){rr.ensure();const t=Lt(cr|Ar,e);return(r={})=>new Promise(s=>{r.outro?Ur(t,()=>{Ye(t),s(void 0)}):(Ye(t),s(void 0))})}function sl(e){return Lt(on,e)}function Us(e,t){var r=ge,s={effect:null,ran:!1,deps:e};r.l.$.push(s),s.effect=Un(()=>{e(),!s.ran&&(s.ran=!0,d(t))})}function Qi(){var e=ge;Un(()=>{for(var t of e.l.$){t.deps();var r=t.effect;(r.f&Re)!==0&&r.deps!==null&&be(r,wt),vn(r)&&Fr(r),t.ran=!1}})}function Ki(e){return Lt(Ns|Ar,e)}function Un(e,t=0){return Lt(Nr|t,e)}function j(e,t=[],r=[],s=[]){Oi(s,t,r,o=>{Lt(Nr,()=>e(...o.map(n)))})}function js(e,t=0){var r=Lt(fr|t,e);return r}function kt(e){return Lt(Mt|Ar,e)}function ol(e){var t=e.teardown;if(t!==null){const r=hr,s=Y;cl(!0),yt(null);try{t.call(null)}finally{cl(r),yt(s)}}}function Bs(e,t=!1){var r=e.first;for(e.first=e.last=null;r!==null;){const o=r.ac;o!==null&&qs(()=>{o.abort(Ir)});var s=r.next;(r.f&cr)!==0?r.parent=null:Ye(r,t),r=s}}function Ji(e){for(var t=e.first;t!==null;){var r=t.next;(t.f&Mt)===0&&Ye(t),t=r}}function Ye(e,t=!0){var r=!1;(t||(e.f&di)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(ll(e.nodes.start,e.nodes.end),r=!0),Bs(e,t&&!r),jn(e,0),be(e,Pt);var s=e.nodes&&e.nodes.t;if(s!==null)for(const l of s)l.stop();ol(e);var o=e.parent;o!==null&&o.first!==null&&il(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=null}function ll(e,t){for(;e!==null;){var r=e===t?null:It(e);e.remove(),e=r}}function il(e){var t=e.parent,r=e.prev,s=e.next;r!==null&&(r.next=s),s!==null&&(s.prev=r),t!==null&&(t.first===e&&(t.first=s),t.last===e&&(t.last=r))}function Ur(e,t,r=!0){var s=[];al(e,s,!0);var o=()=>{r&&Ye(e),t&&t()},l=s.length;if(l>0){var i=()=>--l||o();for(var a of s)a.out(i)}else o()}function al(e,t,r){if((e.f&Ge)===0){e.f^=Ge;var s=e.nodes&&e.nodes.t;if(s!==null)for(const a of s)(a.is_global||r)&&t.push(a);for(var o=e.first;o!==null;){var l=o.next,i=(o.f&ln)!==0||(o.f&Mt)!==0&&(e.f&fr)!==0;al(o,t,i?r:!1),o=l}}}function Fs(e){fl(e,!0)}function fl(e,t){if((e.f&Ge)!==0){e.f^=Ge;for(var r=e.first;r!==null;){var s=r.next,o=(r.f&ln)!==0||(r.f&Mt)!==0;fl(r,o?t:!1),r=s}var l=e.nodes&&e.nodes.t;if(l!==null)for(const i of l)(i.is_global||t)&&i.in()}}function Ps(e,t){if(e.nodes)for(var r=e.nodes.start,s=e.nodes.end;r!==null;){var o=r===s?null:It(r);t.append(r),r=o}}let as=!1,hr=!1;function cl(e){hr=e}let Y=null,qt=!1;function yt(e){Y=e}let Z=null;function Wt(e){Z=e}let Et=null;function ul(e){Y!==null&&(Et===null?Et=[e]:Et.push(e))}let rt=null,ct=0,$t=null;function Xi(e){$t=e}let dl=1,jr=0,Br=jr;function vl(e){Br=e}function pl(){return++dl}function vn(e){var t=e.f;if((t&Be)!==0)return!0;if(t&je&&(e.f&=~Mr),(t&wt)!==0){for(var r=e.deps,s=r.length,o=0;oe.wv)return!0}(t&bt)!==0&&Fe===null&&be(e,Re)}return!1}function hl(e,t,r=!0){var s=e.reactions;if(s!==null&&!(Et!==null&&sn.call(Et,e)))for(var o=0;o{e.ac.abort(Ir)}),e.ac=null);try{e.f|=Ds;var v=e.fn,m=v();e.f|=Tr;var b=e.deps,S=ie==null?void 0:ie.is_fork;if(rt!==null){var $;if(S||jn(e,ct),b!==null&&ct>0)for(b.length=ct+rt.length,$=0;$r==null?void 0:r.call(this,l))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?tr(()=>{t.addEventListener(e,o,s)}):t.addEventListener(e,o,s),o}function _r(e,t,r,s,o){var l={capture:s,passive:o},i=ta(e,t,r,l);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&rl(()=>{t.removeEventListener(e,i,l)})}function Bn(e,t,r){(t[Pr]??(t[Pr]={}))[e]=r}function bl(e){for(var t=0;t{throw ae});throw b}}finally{e[Pr]=t,delete e.currentTarget,yt(v),Wt(m)}}}const Vs=((Fl=globalThis==null?void 0:globalThis.window)==null?void 0:Fl.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function ra(e){return(Vs==null?void 0:Vs.createHTML(e))??e}function kl(e){var t=ls("template");return t.innerHTML=ra(e.replaceAll("","")),t.content}function ut(e,t){var r=Z;r.nodes===null&&(r.nodes={start:e,end:t,a:null,t:null})}function C(e,t){var r=(t&ko)!==0,s=(t&si)!==0,o,l=!e.startsWith("");return()=>{if(X)return ut(F,null),F;o===void 0&&(o=kl(l?e:""+e),r||(o=We(o)));var i=s||Ko?document.importNode(o,!0):o.cloneNode(!0);if(r){var a=We(i),f=i.lastChild;ut(a,f)}else ut(i,i);return i}}function na(e,t,r="svg"){var s=!e.startsWith(""),o=(t&ko)!==0,l=`<${r}>${s?e:""+e}`,i;return()=>{if(X)return ut(F,null),F;if(!i){var a=kl(l),f=We(a);if(o)for(i=document.createDocumentFragment();We(f);)i.appendChild(We(f));else i=We(f)}var c=i.cloneNode(!0);if(o){var v=We(c),m=c.lastChild;ut(v,m)}else ut(c,c);return c}}function Le(e,t){return na(e,t,"svg")}function sa(e=""){if(!X){var t=tt(e+"");return ut(t,t),t}var r=F;return r.nodeType!==An?(r.before(r=tt()),He(r)):is(r),ut(r,r),r}function Fn(){if(X)return ut(F,null),F;var e=document.createDocumentFragment(),t=document.createComment(""),r=tt();return e.append(t,r),ut(t,r),e}function w(e,t){if(X){var r=Z;((r.f&Tr)===0||r.nodes.end===null)&&(r.nodes.end=F),fn();return}e!==null&&e.before(t)}const oa=["touchstart","touchmove"];function la(e){return oa.includes(e)}function V(e,t){var r=t==null?"":typeof t=="object"?`${t}`:t;r!==(e.__t??(e.__t=e.nodeValue))&&(e.__t=r,e.nodeValue=`${r}`)}function yl(e,t){return El(e,t)}function ia(e,t){Ls(),t.intro=t.intro??!1;const r=t.target,s=X,o=F;try{for(var l=We(r);l&&(l.nodeType!==an||l.data!==yo);)l=It(l);if(!l)throw Sr;er(!0),He(l);const i=El(e,{...t,anchor:l});return er(!1),i}catch(i){if(i instanceof Error&&i.message.split(` +`).some(a=>a.startsWith("https://svelte.dev/e/")))throw i;return i!==Sr&&console.warn("Failed to hydrate: ",i),t.recover===!1&&wi(),Ls(),Zo(r),er(!1),yl(e,t)}finally{er(s),He(o)}}const fs=new Map;function El(e,{target:t,anchor:r,props:s={},events:o,context:l,intro:i=!0,transformError:a}){Ls();var f=void 0,c=Yi(()=>{var v=r??t.appendChild(tt());Li(v,{pending:()=>{}},S=>{rs({});var $=ge;if(l&&($.c=l),o&&(s.$$events=o),X&&ut(S,null),f=e(S,s)||{},X&&(Z.nodes.end=F,F===null||F.nodeType!==an||F.data!==ks))throw Mn(),Sr;ns()},a);var m=new Set,b=S=>{for(var $=0;${var x;for(var S of m)for(const A of[t,document]){var $=fs.get(A),B=$.get(S);--B==0?(A.removeEventListener(S,Gs),$.delete(S),$.size===0&&fs.delete(A)):$.set(S,B)}Hs.delete(b),v!==r&&((x=v.parentNode)==null||x.removeChild(v))}});return Ws.set(f,c),f}let Ws=new WeakMap;function aa(e,t){const r=Ws.get(e);return r?(Ws.delete(e),r(t)):Promise.resolve()}class fa{constructor(t,r=!0){ke(this,"anchor");W(this,Ut,new Map);W(this,Jt,new Map);W(this,pt,new Map);W(this,Yr,new Set);W(this,Gn,!0);W(this,Vn,t=>{if(u(this,Ut).has(t)){var r=u(this,Ut).get(t),s=u(this,Jt).get(r);if(s)Fs(s),u(this,Yr).delete(r);else{var o=u(this,pt).get(r);o&&(o.effect.f&Ge)===0&&(u(this,Jt).set(r,o.effect),u(this,pt).delete(r),o.fragment.lastChild.remove(),this.anchor.before(o.fragment),s=o.effect)}for(const[l,i]of u(this,Ut)){if(u(this,Ut).delete(l),l===t)break;const a=u(this,pt).get(i);a&&(Ye(a.effect),u(this,pt).delete(i))}for(const[l,i]of u(this,Jt)){if(l===r||u(this,Yr).has(l)||(i.f&Ge)!==0)continue;const a=()=>{if(Array.from(u(this,Ut).values()).includes(l)){var c=document.createDocumentFragment();Ps(i,c),c.append(tt()),u(this,pt).set(l,{effect:i,fragment:c})}else Ye(i);u(this,Yr).delete(l),u(this,Jt).delete(l)};u(this,Gn)||!s?(u(this,Yr).add(l),Ur(i,a,!1)):a()}}});W(this,ps,t=>{u(this,Ut).delete(t);const r=Array.from(u(this,Ut).values());for(const[s,o]of u(this,pt))r.includes(s)||(Ye(o.effect),u(this,pt).delete(s))});this.anchor=t,G(this,Gn,r)}ensure(t,r){var s=ie,o=el();if(r&&!u(this,Jt).has(t)&&!u(this,pt).has(t))if(o){var l=document.createDocumentFragment(),i=tt();l.append(i),u(this,pt).set(t,{effect:kt(()=>r(i)),fragment:l})}else u(this,Jt).set(t,kt(()=>r(this.anchor)));if(u(this,Ut).set(s,t),o){for(const[a,f]of u(this,Jt))a===t?s.unskip_effect(f):s.skip_effect(f);for(const[a,f]of u(this,pt))a===t?s.unskip_effect(f.effect):s.skip_effect(f.effect);s.oncommit(u(this,Vn)),s.ondiscard(u(this,ps))}else X&&(this.anchor=F),u(this,Vn).call(this,s)}}Ut=new WeakMap,Jt=new WeakMap,pt=new WeakMap,Yr=new WeakMap,Gn=new WeakMap,Vn=new WeakMap,ps=new WeakMap;function $l(e){ge===null&&Ro(),de&&ge.l!==null?ua(ge).m.push(e):On(()=>{const t=d(e);if(typeof t=="function")return t})}function ca(e){ge===null&&Ro(),$l(()=>()=>d(e))}function ua(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}function Q(e,t,r=!1){var s;X&&(s=F,fn());var o=new fa(e),l=r?ln:0;function i(a,f){if(X){var c=Io(s);if(a!==parseInt(c.substring(1))){var v=ts();He(v),o.anchor=v,er(!1),o.ensure(a,f),er(!0);return}}o.ensure(a,f)}js(()=>{var a=!1;t((f,c=0)=>{a=!0,i(c,f)}),a||i(-1,null)},l)}function cs(e,t){return t}function da(e,t,r){for(var s=[],o=t.length,l,i=t.length,a=0;a{if(l){if(l.pending.delete(m),l.done.add(m),l.pending.size===0){var b=e.outrogroups;Ys(e,Xn(l.done)),b.delete(l),b.size===0&&(e.outrogroups=null)}}else i-=1},!1)}if(i===0){var f=s.length===0&&r!==null;if(f){var c=r,v=c.parentNode;Zo(v),v.append(c),e.items.clear()}Ys(e,t,!f)}else l={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(l)}function Ys(e,t,r=!0){var s;if(e.pending.size>0){s=new Set;for(const i of e.pending.values())for(const a of i)s.add(e.items.get(a).e)}for(var o=0;o{var ee=r();return Co(ee)?ee:ee==null?[]:Xn(ee)}),b,S=new Map,$=!0;function B(ee){(ae.effect.f&Pt)===0&&(ae.pending.delete(ee),ae.fallback=v,va(ae,b,i,t,s),v!==null&&(b.length===0?(v.f&zt)===0?Fs(v):(v.f^=zt,zn(v,null,i)):Ur(v,()=>{v=null})))}function x(ee){ae.pending.delete(ee)}var A=js(()=>{b=n(m);var ee=b.length;let ne=!1;if(X){var Qe=Io(i)===ws;Qe!==(ee===0)&&(i=ts(),He(i),er(!1),ne=!0)}for(var pe=new Set,K=ie,Ne=el(),Ke=0;Kel(i)):(v=kt(()=>l(Cl??(Cl=tt()))),v.f|=zt)),ee>pe.size&&_i(),X&&ee>0&&He(ts()),!$)if(S.set(K,pe),Ne){for(const[se,J]of a)pe.has(se)||K.skip_effect(J.e);K.oncommit(B),K.ondiscard(x)}else B(K);ne&&er(!0),n(m)}),ae={effect:A,items:a,pending:S,outrogroups:null,fallback:v};$=!1,X&&(i=F)}function Pn(e){for(;e!==null&&(e.f&Mt)===0;)e=e.next;return e}function va(e,t,r,s,o){var ht,Dt,M,se,J,fe,Pe,qe,ot;var l=(s&Jl)!==0,i=t.length,a=e.items,f=Pn(e.effect.first),c,v=null,m,b=[],S=[],$,B,x,A;if(l)for(A=0;A0){var Ke=(s&wo)!==0&&i===0?r:null;if(l){for(A=0;A{var Je,yr;if(m!==void 0)for(x of m)(yr=(Je=x.nodes)==null?void 0:Je.a)==null||yr.apply()})}function pa(e,t,r,s,o,l,i,a){var f=(i&Ql)!==0?(i&Xl)===0?at(r,!1,!1):qr(r):null,c=(i&Kl)!==0?qr(o):null;return{v:f,i:c,e:kt(()=>(l(t,f??r,c??o,a),()=>{e.delete(s)}))}}function zn(e,t,r){if(e.nodes)for(var s=e.nodes.start,o=e.nodes.end,l=t&&(t.f&zt)===0?t.nodes.start:r;s!==null;){var i=It(s);if(l.before(s),s===o)return;s=i}}function gr(e,t,r){t===null?e.effect.first=r:t.next=r,r===null?e.effect.last=t:r.prev=t}function ha(e,t,r=!1,s=!1,o=!1){var l=e,i="";j(()=>{var a=Z;if(i===(i=t()??"")){X&&fn();return}if(a.nodes!==null&&(ll(a.nodes.start,a.nodes.end),a.nodes=null),i!==""){if(X){F.data;for(var f=fn(),c=f;f!==null&&(f.nodeType!==an||f.data!=="");)c=f,f=It(f);if(f===null)throw Mn(),Sr;ut(F,c),l=He(f);return}var v=r?oi:s?li:void 0,m=ls(r?"svg":s?"math":"template",v);m.innerHTML=i;var b=r||s?m:m.content;if(ut(We(b),b.lastChild),r||s)for(;We(b);)l.before(We(b));else l.before(b)}})}function Qs(e,t){sl(()=>{var r=e.getRootNode(),s=r.host?r:r.head??r.ownerDocument.head;if(!s.querySelector("#"+t.hash)){const o=ls("style");o.id=t.hash,o.textContent=t.code,s.appendChild(o)}})}const Sl=[...` +\r\f \v\uFEFF`];function _a(e,t,r){var s=e==null?"":""+e;if(t&&(s=s?s+" "+t:t),r){for(var o of Object.keys(r))if(r[o])s=s?s+" "+o:o;else if(s.length)for(var l=o.length,i=0;(i=s.indexOf(o,i))>=0;){var a=i+l;(i===0||Sl.includes(s[i-1]))&&(a===s.length||Sl.includes(s[a]))?s=(i===0?"":s.substring(0,i))+s.substring(a+1):i=a}}return s===""?null:s}function ga(e,t){return e==null?null:String(e)}function he(e,t,r,s,o,l){var i=e.__className;if(X||i!==r||i===void 0){var a=_a(r,s,l);(!X||a!==e.getAttribute("class"))&&(a==null?e.removeAttribute("class"):t?e.className=a:e.setAttribute("class",a)),e.__className=r}else if(l&&o!==l)for(var f in l){var c=!!l[f];(o==null||c!==!!o[f])&&e.classList.toggle(f,c)}return l}function zr(e,t,r,s){var o=e.__style;if(X||o!==t){var l=ga(t);(!X||l!==e.getAttribute("style"))&&(l==null?e.removeAttribute("style"):e.style.cssText=l),e.__style=t}return s}const ma=Symbol("is custom element"),xa=Symbol("is html"),ba=pi?"link":"LINK";function Hr(e,t,r,s){var o=wa(e);X&&(o[t]=e.getAttribute(t),t==="src"||t==="srcset"||t==="href"&&e.nodeName===ba)||o[t]!==(o[t]=r)&&(t==="loading"&&(e[vi]=r),r==null?e.removeAttribute(t):typeof r!="string"&&ka(e).includes(t)?e[t]=r:e.setAttribute(t,r))}function wa(e){return e.__attributes??(e.__attributes={[ma]:e.nodeName.includes("-"),[xa]:e.namespaceURI===$o})}var Dl=new Map;function ka(e){var t=e.getAttribute("is")||e.nodeName,r=Dl.get(t);if(r)return r;Dl.set(t,r=[]);for(var s,o=e,l=Element.prototype;l!==o;){s=So(o);for(var i in s)s[i].set&&r.push(i);o=Es(o)}return r}function Nl(e,t){return e===t||(e==null?void 0:e[Rr])===t}function Tl(e={},t,r,s){return sl(()=>{var o,l;return Un(()=>{o=l,l=[],d(()=>{e!==r(...l)&&(t(e,...l),o&&Nl(r(...o),e)&&t(null,...o))})}),()=>{tr(()=>{l&&Nl(r(...l),e)&&t(null,...l)})}}),e}function Al(e){return function(...t){var r=t[0];return r.stopPropagation(),e==null?void 0:e.apply(this,t)}}function ya(e=!1){const t=ge,r=t.l.u;if(!r)return;let s=()=>_(t.s);if(e){let o=0,l={};const i=Ln(()=>{let a=!1;const f=t.s;for(const c in f)f[c]!==l[c]&&(l[c]=f[c],a=!0);return a&&o++,o});s=()=>n(i)}r.b.length&&Vi(()=>{Ml(t,s),$s(r.b)}),On(()=>{const o=d(()=>r.m.map(ui));return()=>{for(const l of o)typeof l=="function"&&l()}}),r.a.length&&On(()=>{Ml(t,s),$s(r.a)})}function Ml(e,t){if(e.l.s)for(const r of e.l.s)n(r);t()}let us=!1;function Ea(e){var t=us;try{return us=!1,[e(),us]}finally{us=t}}function mr(e,t,r,s){var ee;var o=!de||(r&ei)!==0,l=(r&ri)!==0,i=(r&ni)!==0,a=s,f=!0,c=()=>(f&&(f=!1,a=i?d(s):s),a),v;if(l){var m=Rr in e||Mo in e;v=((ee=Dr(e,t))==null?void 0:ee.set)??(m&&t in e?ne=>e[t]=ne:void 0)}var b,S=!1;l?[b,S]=Ea(()=>e[t]):b=e[t],b===void 0&&s!==void 0&&(b=c(),v&&(o&&ki(),v(b)));var $;if(o?$=()=>{var ne=e[t];return ne===void 0?c():(f=!0,ne)}:$=()=>{var ne=e[t];return ne!==void 0&&(a=void 0),ne===void 0?a:ne},o&&(r&ti)===0)return $;if(v){var B=e.$$legacy;return(function(ne,Qe){return arguments.length>0?((!o||!Qe||B||S)&&v(Qe?$():ne),ne):$()})}var x=!1,A=((r&Zl)!==0?Ln:Ve)(()=>(x=!1,$()));l&&n(A);var ae=Z;return(function(ne,Qe){if(arguments.length>0){const pe=Qe?n(A):o&&l?Or(ne):ne;return U(A,pe),x=!0,a!==void 0&&(a=pe),ne}return hr&&x||(ae.f&Pt)!==0?A.v:n(A)})}function $a(e){return new Ca(e)}class Ca{constructor(t){W(this,or);W(this,St);var l;var r=new Map,s=(i,a)=>{var f=at(a,!1,!1);return r.set(i,f),f};const o=new Proxy({...t.props||{},$$events:{}},{get(i,a){return n(r.get(a)??s(a,Reflect.get(i,a)))},has(i,a){return a===Mo?!0:(n(r.get(a)??s(a,Reflect.get(i,a))),Reflect.has(i,a))},set(i,a,f){return U(r.get(a)??s(a,f),f),Reflect.set(i,a,f)}});G(this,St,(t.hydrate?ia:yl)(t.component,{target:t.target,anchor:t.anchor,props:o,context:t.context,intro:t.intro??!1,recover:t.recover,transformError:t.transformError})),(!((l=t==null?void 0:t.props)!=null&&l.$$host)||t.sync===!1)&&Ht(),G(this,or,o.$$events);for(const i of Object.keys(u(this,St)))i==="$set"||i==="$destroy"||i==="$on"||es(this,i,{get(){return u(this,St)[i]},set(a){u(this,St)[i]=a},enumerable:!0});u(this,St).$set=i=>{Object.assign(o,i)},u(this,St).$destroy=()=>{aa(u(this,St))}}$set(t){u(this,St).$set(t)}$on(t,r){u(this,or)[t]=u(this,or)[t]||[];const s=(...o)=>r.call(this,...o);return u(this,or)[t].push(s),()=>{u(this,or)[t]=u(this,or)[t].filter(o=>o!==s)}}$destroy(){u(this,St).$destroy()}}or=new WeakMap,St=new WeakMap;let Rl;typeof HTMLElement=="function"&&(Rl=class extends HTMLElement{constructor(t,r,s){super();ke(this,"$$ctor");ke(this,"$$s");ke(this,"$$c");ke(this,"$$cn",!1);ke(this,"$$d",{});ke(this,"$$r",!1);ke(this,"$$p_d",{});ke(this,"$$l",{});ke(this,"$$l_u",new Map);ke(this,"$$me");ke(this,"$$shadowRoot",null);this.$$ctor=t,this.$$s=r,s&&(this.$$shadowRoot=this.attachShadow(s))}addEventListener(t,r,s){if(this.$$l[t]=this.$$l[t]||[],this.$$l[t].push(r),this.$$c){const o=this.$$c.$on(t,r);this.$$l_u.set(r,o)}super.addEventListener(t,r,s)}removeEventListener(t,r,s){if(super.removeEventListener(t,r,s),this.$$c){const o=this.$$l_u.get(r);o&&(o(),this.$$l_u.delete(r))}}async connectedCallback(){if(this.$$cn=!0,!this.$$c){let t=function(o){return l=>{const i=ls("slot");o!=="default"&&(i.name=o),w(l,i)}};if(await Promise.resolve(),!this.$$cn||this.$$c)return;const r={},s=Sa(this);for(const o of this.$$s)o in s&&(o==="default"&&!this.$$d.children?(this.$$d.children=t(o),r.default=!0):r[o]=t(o));for(const o of this.attributes){const l=this.$$g_p(o.name);l in this.$$d||(this.$$d[l]=ds(l,o.value,this.$$p_d,"toProp"))}for(const o in this.$$p_d)!(o in this.$$d)&&this[o]!==void 0&&(this.$$d[o]=this[o],delete this[o]);this.$$c=$a({component:this.$$ctor,target:this.$$shadowRoot||this,props:{...this.$$d,$$slots:r,$$host:this}}),this.$$me=Wi(()=>{Un(()=>{var o;this.$$r=!0;for(const l of Zn(this.$$c)){if(!((o=this.$$p_d[l])!=null&&o.reflect))continue;this.$$d[l]=this.$$c[l];const i=ds(l,this.$$d[l],this.$$p_d,"toAttribute");i==null?this.removeAttribute(this.$$p_d[l].attribute||l):this.setAttribute(this.$$p_d[l].attribute||l,i)}this.$$r=!1})});for(const o in this.$$l)for(const l of this.$$l[o]){const i=this.$$c.$on(o,l);this.$$l_u.set(l,i)}this.$$l={}}}attributeChangedCallback(t,r,s){var o;this.$$r||(t=this.$$g_p(t),this.$$d[t]=ds(t,s,this.$$p_d,"toProp"),(o=this.$$c)==null||o.$set({[t]:this.$$d[t]}))}disconnectedCallback(){this.$$cn=!1,Promise.resolve().then(()=>{!this.$$cn&&this.$$c&&(this.$$c.$destroy(),this.$$me(),this.$$c=void 0)})}$$g_p(t){return Zn(this.$$p_d).find(r=>this.$$p_d[r].attribute===t||!this.$$p_d[r].attribute&&r.toLowerCase()===t)||t}});function ds(e,t,r,s){var l;const o=(l=r[e])==null?void 0:l.type;if(t=o==="Boolean"&&typeof t!="boolean"?t!=null:t,!s||!r[e])return t;if(s==="toAttribute")switch(o){case"Object":case"Array":return t==null?null:JSON.stringify(t);case"Boolean":return t?"":null;case"Number":return t??null;default:return t}else switch(o){case"Object":case"Array":return t&&JSON.parse(t);case"Boolean":return t;case"Number":return t!=null?+t:t;default:return t}}function Sa(e){const t={};return e.childNodes.forEach(r=>{t[r.slot||"default"]=!0}),t}function Ks(e,t,r,s,o,l){let i=class extends Rl{constructor(){super(e,r,o),this.$$p_d=t}static get observedAttributes(){return Zn(t).map(a=>(t[a].attribute||a).toLowerCase())}};return Zn(t).forEach(a=>{es(i.prototype,a,{get(){return this.$$c&&a in this.$$c?this.$$c[a]:this.$$d[a]},set(f){var m;f=ds(a,f,t),this.$$d[a]=f;var c=this.$$c;if(c){var v=(m=Dr(c,a))==null?void 0:m.get;v?c[a]=f:c.$set({[a]:f})}}})}),s.forEach(a=>{es(i.prototype,a,{get(){var f;return(f=this.$$c)==null?void 0:f[a]}})}),e.element=i,i}async function Il(e,t){const r=t?`/api/orgs/${e}/projects/${t}/timeline`:`/api/orgs/${e}/timeline`,s=await fetch(r,{credentials:"same-origin"});if(!s.ok)throw new Error(`Timeline fetch failed: ${s.status}`);return s.json()}function Da(e,t,r){const s=t?`/orgs/${e}/projects/${t}/events`:`/orgs/${e}/events`;let o=1e3,l=null,i=!1;function a(){if(!i){l=new EventSource(s),l.addEventListener("open",()=>{o=1e3});for(const f of["destination","release","artifact","pipeline"])l.addEventListener(f,c=>{try{const v=JSON.parse(c.data);r(f,v)}catch(v){console.warn(`[release-timeline] bad ${f} event:`,v)}});l.addEventListener("error",()=>{l.close(),i||(setTimeout(a,o),o=Math.min(o*2,3e4))})}}return a(),()=>{i=!0,l&&l.close()}}function Ll(e){if(e<0&&(e=0),e<60)return`${e}s`;const t=Math.floor(e/60),r=e%60;return t<60?`${t}m ${r}s`:`${Math.floor(t/60)}h ${t%60}m`}function pn(e){if(!e)return"";const t=new Date(e),r=Date.now(),s=Math.floor((r-t.getTime())/1e3);return s<10?"just now":s<60?`${s}s ago`:s<3600?`${Math.floor(s/60)}m ago`:s<86400?`${Math.floor(s/3600)}h ago`:`${Math.floor(s/86400)}d ago`}const Js={prod:["#ec4899","#fce7f3"],production:["#ec4899","#fce7f3"],preprod:["#f97316","#ffedd5"],"pre-prod":["#f97316","#ffedd5"],staging:["#eab308","#fef9c3"],stage:["#eab308","#fef9c3"],dev:["#8b5cf6","#ede9fe"],development:["#8b5cf6","#ede9fe"],test:["#06b6d4","#cffafe"]},Na=["#6b7280","#e5e7eb"];function Ta(e){const t=e.toLowerCase();if(Js[t])return Js[t];for(const[r,s]of Object.entries(Js))if(t.includes(r))return s;return Na}function xr(e){const t=e.toLowerCase();return t.includes("prod")&&!t.includes("preprod")&&!t.includes("pre-prod")?{bg:"bg-pink-100 text-pink-800",dot:"bg-pink-500"}:t.includes("preprod")||t.includes("pre-prod")?{bg:"bg-orange-100 text-orange-800",dot:"bg-orange-500"}:t.includes("stag")?{bg:"bg-yellow-100 text-yellow-800",dot:"bg-yellow-500"}:t.includes("dev")?{bg:"bg-violet-100 text-violet-800",dot:"bg-violet-500"}:{bg:"bg-gray-100 text-gray-700",dot:"bg-gray-400"}}function ql(e){switch(e){case"SUCCEEDED":return"bg-green-500";case"RUNNING":return"bg-yellow-500";case"FAILED":return"bg-red-500";default:return null}}const Xs={SUCCEEDED:{label:"Deployed to",stageLabel:"Deployed to",color:"text-green-600",icon:"check-circle",iconColor:"text-green-500"},RUNNING:{label:"Deploying to",stageLabel:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500"},ASSIGNED:{label:"Deploying to",stageLabel:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500"},QUEUED:{label:"Queued for",stageLabel:"Queued for",color:"text-blue-600",icon:"clock",iconColor:"text-blue-400"},FAILED:{label:"Failed on",stageLabel:"Failed on",color:"text-red-600",icon:"x-circle",iconColor:"text-red-500"},TIMED_OUT:{label:"Timed out on",stageLabel:"Timed out on",color:"text-orange-600",icon:"clock",iconColor:"text-orange-500"},CANCELLED:{label:"Cancelled",stageLabel:"Cancelled",color:"text-gray-500",icon:"ban",iconColor:"text-gray-400"}};function hn(e){if(!e||e.length===0)return null;let t=!0,r=!1,s=!1,o=!1,l=!1,i=0;const a=e.length;for(const c of e)c.status==="SUCCEEDED"&&i++,c.status!=="SUCCEEDED"&&(t=!1),c.status==="FAILED"&&(r=!0),c.status==="RUNNING"&&(s=!0),c.status==="QUEUED"&&(l=!0),c.stage_type==="wait"&&c.status==="RUNNING"&&(o=!0);let f=e.some(c=>c.blocked_by);return t?{label:"Pipeline complete",color:"text-gray-600",icon:"check-circle",iconColor:"text-green-500",done:i,total:a}:r?{label:"Pipeline failed",color:"text-red-600",icon:"x-circle",iconColor:"text-red-500",done:i,total:a}:f?{label:"Awaiting approval",color:"text-emerald-700",icon:"shield",iconColor:"text-emerald-500",done:i,total:a}:o?{label:"Waiting for time window",color:"text-yellow-700",icon:"clock",iconColor:"text-yellow-500",done:i,total:a}:s?{label:"Deploying to",color:"text-yellow-700",icon:"pulse",iconColor:"text-yellow-500",done:i,total:a}:l?{label:"Queued",color:"text-blue-600",icon:"clock",iconColor:"text-blue-400",done:i,total:a}:{label:"Pipeline pending",color:"text-gray-400",icon:"pending",iconColor:"text-gray-300",done:i,total:a}}function Ol(e){switch(e){case"SUCCEEDED":return"Waited";case"RUNNING":return"Waiting";case"FAILED":return"Wait failed";case"CANCELLED":return"Wait cancelled";default:return"Wait"}}function Ul(e){switch(e){case"SUCCEEDED":return"Deployed to";case"RUNNING":return"Deploying to";case"QUEUED":return"Queued for";case"FAILED":return"Failed on";case"TIMED_OUT":return"Timed out on";case"CANCELLED":return"Cancelled";default:return"Deploy to"}}var Aa=C('
'),Ma=C('

Loading releases...

'),Ra=C('

'),Ia=C('

No releases yet.

Create a release with forest release create

'),La=C('
'),qa=C('
'),Oa=C('
'),Ua=C(" ",1),ja=C('
'),Ba=C(' '),Fa=C(' '),Pa=C(' '),za=C(' '),Ha=C(' Deployed',1),Ga=C(' Queued',1),Va=Le('',1),Wa=C(''),Ya=Le(''),Qa=Le(''),Ka=Le(''),Ja=Le(''),Xa=Le(''),Za=C(" "),ef=C(''),tf=C(''),rf=C(" ",1),nf=C(' ',1),sf=C(' Deployed',1),of=C(''),lf=Le(''),af=Le(''),ff=C(" "),cf=C(" ",1),uf=C(' Pending',1),df=C('

'),vf=C(' '),pf=Le(''),hf=C(''),_f=Le(''),gf=Le(''),mf=Le(''),xf=C(" ",1),bf=C(" "),wf=C(' '),kf=C('
pipeline
'),yf=C('
'),Ef=Le(''),$f=C(''),Cf=Le(''),Sf=Le(''),Df=Le(''),Nf=C('Deployed'),Tf=C('Deploying'),Af=C(' '),Mf=C('Failed'),Rf=C(''),If=C('
'),Lf=C(''),qf=C(' '),Of=C(''),Uf=C('
·
'),jf=C('
'),Bf=C('
'),Ff=C(" ",1);const Pf={hash:"svelte-4kxpm1",code:` @keyframes svelte-4kxpm1-lane-pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }.lane-pulse { - animation: svelte-4kxpm1-lane-pulse 2s ease-in-out infinite;}`};function yf(e,t){Gn(t,!1),Us(e,kf);const r=Mt(),s=Mt();let i=Rn(t,"org",12,""),o=Rn(t,"project",12,""),l=Mt([]),f=Mt([]),a=Mt(!0),c=Mt(null),v=Mt(null),m=Date.now(),b=null,C=Mt(null),E=Mt({});const q=20,x=4,A=12,ce=new Set(["QUEUED","RUNNING","ASSIGNED"]),J=new Set(["SUCCEEDED"]);let z=null;function qe(){z||(z=setTimeout(()=>{z=null,pe()},300))}async function ue(){try{V(c,null);const _=await xo(i(),o());st(_.timeline,_.lanes),V(a,!1),me()}catch(_){V(c,_.message),V(a,!1)}}async function pe(){try{const _=await xo(i(),o());st(_.timeline,_.lanes),me()}catch(_){console.warn("[release-timeline] refresh failed:",_)}}function st(_,w){const D=new Map;for(const H of n(l))H.kind==="release"&&H.release&&D.set(H.release.slug,H);const Z=_.map(H=>{if(H.kind!=="release"||!H.release)return H;const re=D.get(H.release.slug);if(!re)return H;const L=re.release,R=H.release;return L.dest_envs===R.dest_envs&&L.has_pipeline===R.has_pipeline&&Je(L.pipeline_stages,R.pipeline_stages)&&Et(L.destinations,R.destinations)?re:H});V(l,Z),V(f,w)}function Je(_,w){if(_.length!==w.length)return!1;for(let D=0;D<_.length;D++)if(_[D].status!==w[D].status||_[D].started_at!==w[D].started_at||_[D].completed_at!==w[D].completed_at)return!1;return!0}function Et(_,w){if(_.length!==w.length)return!1;for(let D=0;D<_.length;D++)if(_[D].status!==w[D].status||_[D].completed_at!==w[D].completed_at)return!1;return!0}function Ut(_,w){_==="destination"&&w.action==="status_changed"?M(w):_==="release"?w.action==="created"?qe():(w.action==="status_changed"||w.action==="updated")&&ie(w):_==="artifact"&&(w.action==="created"||w.action==="updated")?qe():_==="pipeline"&&X(w)}function M(_){var re,L,R;const w=(re=_.metadata)==null?void 0:re.status,D=((L=_.metadata)==null?void 0:L.destination_name)||_.resource_id,Z=(R=_.metadata)==null?void 0:R.environment;if(!w||!D)return;let H=!1;V(l,n(l).map(se=>{if(se.kind!=="release"||!se.release)return se;const Te=se.release;if(Te.destinations.findIndex(de=>de.name===D)===-1)return se;H=!0;const Oe=Te.destinations.map(de=>de.name===D?{...de,status:w,...["SUCCEEDED","FAILED","TIMED_OUT","CANCELLED"].includes(w)?{completed_at:new Date().toISOString()}:{}}:de),p=Oe.map(de=>`${de.environment}:${de.status||"PENDING"}`).join(","),ze=Z?Te.pipeline_stages.map(de=>de.stage_type==="deploy"&&de.environment===Z?{...de,status:w==="ASSIGNED"?"RUNNING":w}:de):Te.pipeline_stages;return{...se,release:{...Te,destinations:Oe,dest_envs:p,pipeline_stages:ze}}})),H&&me()}function ie(_){var Z,H;const w=(Z=_.metadata)==null?void 0:Z.status,D=(H=_.metadata)==null?void 0:H.environment;w&&D?M(_):qe()}function X(_){var re,L,R;const w=(re=_.metadata)==null?void 0:re.status,D=(L=_.metadata)==null?void 0:L.environment,Z=(R=_.metadata)==null?void 0:R.stage_type;if(!w){(_.action==="created"||_.action==="updated")&&qe();return}let H=!1;V(l,n(l).map(se=>{if(se.kind!=="release"||!se.release)return se;const Te=se.release;let Xe=!1;const Oe=Te.pipeline_stages.map(p=>D&&p.stage_type==="deploy"&&p.environment===D?(Xe=!0,{...p,status:w,...p.started_at?{}:{started_at:new Date().toISOString()}}):Z==="wait"&&p.stage_type==="wait"?(Xe=!0,{...p,status:w}):p);return Xe?(H=!0,{...se,release:{...Te,pipeline_stages:Oe}}):se})),H&&me()}function oe(_){return _?_.split(",").map(w=>w.trim()).filter(Boolean).map(w=>{const D=w.indexOf(":");return D===-1?{env:w,status:"SUCCEEDED"}:{env:w.slice(0,D),status:w.slice(D+1)}}):[]}let Ne=null;function me(){Ne||(Ne=requestAnimationFrame(()=>{Ne=null,Fl().then(it)}))}function it(){if(!n(C))return;const _=n(C).getBoundingClientRect();if(_.height===0)return;const w=_.height,D=Array.from(n(C).querySelectorAll("[data-release]")),Z={};for(const H of n(f)){const re=H.name;let L=null,R=null,se=-1,Te=-1;for(let Ce=0;Cebr.env===re))continue;const xr=(Ce.querySelector("[data-avatar]")||Ce).getBoundingClientRect();nr.push(xr.top+xr.height/2-_.top)}Z[re]={solidH:p,hasHatch:ze,hatchTop:de,hatchH:Ct,isForward:jt,dots:nr,color:_a(re)}}V(E,Z)}const Ge=new Map;function mr(_,w){const D=`${_}|${w}`;let Z=Ge.get(D);if(Z)return Z;const H=``;return Z=`url("data:image/svg+xml,${encodeURIComponent(H)}")`,Ge.set(D,Z),Z}co(()=>{ue(),b=setInterval(()=>{m=Date.now()},1e4)}),Kl(()=>{n(v)&&n(v)(),b&&clearInterval(b),z&&clearTimeout(z),Ne&&cancelAnimationFrame(Ne)});function _n(){me()}function zs(_,w,D){if(!_)return"";const Z=new Date(_).getTime();if(isNaN(Z))return"";if(w&&D!=="RUNNING"&&D!=="QUEUED"){const H=new Date(w).getTime();if(!isNaN(H))return bo(Math.floor((H-Z)/1e3))}return bo(Math.floor((m-Z)/1e3))}function Hs(_){var w;return _.kind==="release"&&_.release?`r:${_.release.slug}`:_.kind==="hidden"?`h:${_.count}:${((w=(_.releases||[])[0])==null?void 0:w.slug)||""}`:`u:${Math.random()}`}function Gs(_,w){if(!_)return!1;switch(_.label){case"Pipeline complete":return w==="SUCCEEDED";case"Pipeline failed":return w==="FAILED"||w==="RUNNING"||w==="ASSIGNED";case"Deploying to":return w==="RUNNING"||w==="ASSIGNED";case"Queued":return w==="QUEUED";case"Waiting for time window":return w==="RUNNING"||w==="ASSIGNED";default:return w!=="PENDING"&&w!=="SUCCEEDED"}}Ss(()=>(n(a),n(c),h(i()),n(v),h(o())),()=>{!n(a)&&!n(c)&&i()&&!n(v)&&V(v,pa(i(),o(),Ut))}),Ss(()=>n(f),()=>{V(r,n(f).length)}),Ss(()=>n(r),()=>{V(s,n(r)*(q+x)+8)}),Ll();var Hr={get org(){return i()},set org(_){i(_),Tr()},get project(){return o()},set project(_){o(_),Tr()}};fa();var ss=sn();Nn("resize",ys,_n);var Vs=gt(ss);{var ke=_=>{var w=ga();k(_,w)},$e=_=>{var w=ma(),D=$(w),Z=$(D,!0);y(D);var H=T(D,2);y(w),B(()=>Y(Z,n(c))),Nn("click",H,ue),k(_,w)},$t=_=>{var w=xa();k(_,w)},Gr=_=>{var w=wf(),D=$(w);kt(D,5,()=>n(f),re=>re.name,(re,L)=>{const R=Ye(()=>(n(E),n(L),d(()=>n(E)[n(L).name]))),se=Ye(()=>{const[p,ze]=(h(n(R)),n(L),d(()=>{var de;return((de=n(R))==null?void 0:de.color)||[n(L).color,"#e5e7eb"]}));return{barColor:p,lightColor:ze}});var Te=Ea();Ur(Te,"width: 20px; margin-right: 4px; position: relative;");var Xe=$(Te);{var Oe=p=>{var ze=ya(),de=gt(ze);{var Ct=Se=>{var Ae=ba();B(De=>Ur(Ae,`position: absolute; left: 0; width: 100%; top: ${h(n(R)),d(()=>n(R).hatchTop)??""}px; height: ${h(n(R)),d(()=>n(R).hatchH+(n(R).solidH>0?q/2:0))??""}px; background-image: ${De??""}; background-size: 8px 8px; background-repeat: repeat; border-radius: 9999px; z-index: 0;`),[()=>(h(n(R)),h(n(se).barColor),h(n(se).lightColor),d(()=>n(R).isForward?mr(n(se).barColor,n(se).lightColor):mr("#f59e0b","#fef3c7")))]),k(Se,Ae)};te(de,Se=>{h(n(R)),d(()=>n(R).hasHatch)&&Se(Ct)})}var jt=T(de,2);{var nr=Se=>{var Ae=wa();B(()=>Ur(Ae,`position: absolute; bottom: 0; left: 0; width: 100%; height: ${h(n(R)),d(()=>n(R).solidH+(n(R).hasHatch?q/2:0))??""}px; background: ${n(se).barColor??""}; border-radius: 9999px; z-index: 1;`)),k(Se,Ae)};te(jt,Se=>{h(n(R)),d(()=>n(R).solidH>0)&&Se(nr)})}var Ce=T(jt,2);kt(Ce,1,()=>(h(n(R)),d(()=>n(R).dots)),Zn,(Se,Ae)=>{var De=ka();B(()=>Ur(De,`position: absolute; left: 50%; transform: translateX(-50%); top: ${n(Ae)-A/2}px; width: 12px; height: 12px; border-radius: 50%; background: #fff; border: 2px solid ${n(se).barColor??""}; z-index: 2;`)),k(Se,De)}),k(p,ze)};te(Xe,p=>{n(R)&&p(Oe)})}y(Te),k(re,Te)}),y(D);var Z=T(D,2);kt(Z,5,()=>n(l),re=>Hs(re),(re,L)=>{var R=sn(),se=gt(R);{var Te=Oe=>{const p=Ye(()=>(n(L),d(()=>n(L).release)));var ze=_f(),de=$(ze),Ct=$(de),jt=T($(Ct),2),nr=$(jt,!0);y(jt),y(Ct);var Ce=T(Ct,2),Se=$(Ce);{var Ae=I=>{var g=$a(),le=T($(g));y(g),B(()=>Y(le,` ${h(n(p)),d(()=>n(p).branch)??""}`)),k(I,g)};te(Se,I=>{h(n(p)),d(()=>n(p).branch)&&I(Ae)})}var De=T(Se,2);{var xr=I=>{var g=Ca(),le=$(g,!0);y(g),B(S=>Y(le,S),[()=>(h(n(p)),d(()=>n(p).commit_sha.slice(0,7)))]),k(I,g)};te(De,I=>{h(n(p)),d(()=>n(p).commit_sha)&&I(xr)})}var br=T(De,2),On=$(br,!0);y(br);var gn=T(br,2);{var Ws=I=>{var g=Sa(),le=T($(g),2),S=$(le,!0);y(le),y(g),B(()=>{jr(le,"href",`/users/${h(n(p)),d(()=>n(p).source_user)??""}`),Y(S,(h(n(p)),d(()=>n(p).source_user)))}),k(I,g)};te(gn,I=>{h(n(p)),d(()=>n(p).source_user)&&I(Ws)})}var is=T(gn,2);{var os=I=>{var g=Da(),le=$(g,!0);y(g),B(()=>{jr(g,"href",`/orgs/${i()??""}/projects/${h(n(p)),d(()=>n(p).project_name)??""}`),Y(le,(h(n(p)),d(()=>n(p).project_name)))}),k(I,g)};te(is,I=>{h(n(p)),h(o()),d(()=>n(p).project_name&&n(p).project_name!==o())&&I(os)})}y(Ce),y(de);var Un=T(de,2),mn=$(Un),Ys=$(mn);{var Vr=I=>{const g=Ye(()=>(h(n(p)),d(()=>n(p).env_groups&&n(p).env_groups.length>0&&n(p).env_groups.every(xe=>xe.status==="SUCCEEDED"))));var le=Aa(),S=T(gt(le));{var dt=xe=>{var ot=Na();ar(2),k(xe,ot)},vt=xe=>{var ot=Ta();ar(2),k(xe,ot)};te(S,xe=>{n(g)?xe(dt):xe(vt,-1)})}k(I,le)},jn=cr(()=>(h(n(p)),h(ln),d(()=>n(p).has_pipeline&&!ln(n(p).pipeline_stages)))),Qs=I=>{const g=Ye(()=>(h(ln),h(n(p)),d(()=>ln(n(p).pipeline_stages))));var le=Ua(),S=T(gt(le),2);{var dt=ae=>{var P=Ma();k(ae,P)},vt=ae=>{var P=Ra();B(()=>he(P,0,`w-4 h-4 ${h(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),k(ae,P)},xe=ae=>{var P=Ia();B(()=>he(P,0,`w-4 h-4 ${h(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),k(ae,P)},ot=ae=>{var P=La();B(()=>he(P,0,`w-4 h-4 ${h(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),k(ae,P)},Wr=ae=>{var P=qa();k(ae,P)};te(S,ae=>{h(n(g)),d(()=>n(g).icon==="pulse")?ae(dt):(h(n(g)),d(()=>n(g).icon==="check-circle")?ae(vt,1):(h(n(g)),d(()=>n(g).icon==="x-circle")?ae(xe,2):(h(n(g)),d(()=>n(g).icon==="clock")?ae(ot,3):ae(Wr,-1))))})}var Yt=T(S,2),St=$(Yt,!0);y(Yt);var Dt=T(Yt,2);kt(Dt,1,()=>(h(n(p)),d(()=>n(p).pipeline_stages)),ae=>ae.id||ae.environment||ae.stage_type,(ae,P)=>{var Kt=sn(),ne=gt(Kt);{var ve=Ve=>{const pt=Ye(()=>(h(pr),n(P),d(()=>pr(n(P).environment||"")))),sr=Ye(()=>(h(wo),n(P),h(n(pt)),d(()=>wo(n(P).status)||n(pt).dot)));var O=Oa(),U=$(O),Re=T(U);y(O),B(()=>{he(O,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${h(n(pt)),d(()=>n(pt).bg)??""}`,"svelte-4kxpm1"),Y(U,`${n(P),d(()=>n(P).environment)??""} `),he(Re,1,`w-1.5 h-1.5 rounded-full ${n(sr)??""}`,"svelte-4kxpm1")}),k(Ve,O)},Nt=cr(()=>(n(P),h(n(g)),d(()=>n(P).stage_type==="deploy"&&Gs(n(g),n(P).status))));te(ne,Ve=>{n(Nt)&&Ve(ve)})}k(ae,Kt)});var Qt=T(Dt,2),Yr=$(Qt);y(Qt),B(()=>{he(Yt,1,`${h(n(g)),d(()=>n(g).color)??""} text-sm`,"svelte-4kxpm1"),Y(St,(h(n(g)),d(()=>n(g).label))),Y(Yr,`${h(n(g)),d(()=>n(g).done)??""}/${h(n(g)),d(()=>n(g).total)??""}`)}),k(I,le)},Ks=cr(()=>(h(n(p)),h(ln),d(()=>n(p).has_pipeline&&ln(n(p).pipeline_stages)))),Pf=I=>{const g=Ye(()=>(h(n(p)),d(()=>n(p).env_groups.every(xe=>xe.status==="SUCCEEDED"))));var le=sn(),S=gt(le);{var dt=xe=>{var ot=ja();ar(2),k(xe,ot)},vt=xe=>{var ot=sn(),Wr=gt(ot);kt(Wr,1,()=>(h(n(p)),d(()=>n(p).env_groups)),Zn,(Yt,St)=>{var Dt=sn(),Qt=gt(Dt);{var Yr=ae=>{const P=Ye(()=>(h(Bs),n(St),d(()=>Bs[n(St).status]||Bs.SUCCEEDED)));var Kt=Ha(),ne=gt(Kt);{var ve=U=>{var Re=Fa();k(U,Re)},Nt=U=>{var Re=Ba();B(()=>he(Re,0,`w-4 h-4 ${h(n(P)),d(()=>n(P).iconColor)??""} shrink-0`,"svelte-4kxpm1")),k(U,Re)},Ve=U=>{var Re=Pa();B(()=>he(Re,0,`w-4 h-4 ${h(n(P)),d(()=>n(P).iconColor)??""} shrink-0`,"svelte-4kxpm1")),k(U,Re)};te(ne,U=>{h(n(P)),d(()=>n(P).icon==="pulse")?U(ve):(h(n(P)),d(()=>n(P).icon==="check-circle")?U(Nt,1):U(Ve,-1))})}var pt=T(ne,2),sr=$(pt,!0);y(pt);var O=T(pt,2);kt(O,1,()=>(n(St),d(()=>n(St).envs)),U=>U,(U,Re)=>{const xn=Ye(()=>(h(pr),n(Re),d(()=>pr(n(Re)))));var ls=za(),To=$(ls),Kf=T(To);y(ls),B(()=>{he(ls,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${h(n(xn)),d(()=>n(xn).bg)??""}`,"svelte-4kxpm1"),Y(To,`${n(Re)??""} `),he(Kf,1,`w-1.5 h-1.5 rounded-full ${h(n(xn)),d(()=>n(xn).dot)??""}`,"svelte-4kxpm1")}),k(U,ls)}),B(()=>{he(pt,1,`${h(n(P)),d(()=>n(P).color)??""} text-sm`,"svelte-4kxpm1"),Y(sr,(h(n(P)),d(()=>n(P).label)))}),k(ae,Kt)};te(Qt,ae=>{n(St),d(()=>n(St).status!=="SUCCEEDED")&&ae(Yr)})}k(Yt,Dt)}),k(xe,ot)};te(S,xe=>{n(g)?xe(dt):xe(vt,-1)})}k(I,le)},zf=I=>{var g=Ga();ar(2),k(I,g)};te(Ys,I=>{n(jn)?I(Vr):n(Ks)?I(Qs,1):(h(n(p)),d(()=>n(p).env_groups&&n(p).env_groups.length>0)?I(Pf,2):I(zf,-1))})}ar(2),y(mn);var Js=T(mn,2),So=$(Js);{var Hf=I=>{var g=Va(),le=$(g,!0);y(g),B(()=>Y(le,(h(n(p)),d(()=>n(p).description)))),k(I,g)};te(So,I=>{h(n(p)),d(()=>n(p).description)&&I(Hf)})}var Do=T(So,2),Xs=$(Do),Gf=$(Xs,!0);y(Xs);var Vf=T(Xs,2);{var Wf=I=>{var g=Wa(),le=$(g,!0);y(g),B(()=>Y(le,(h(n(p)),d(()=>n(p).version)))),k(I,g)};te(Vf,I=>{h(n(p)),d(()=>n(p).version)&&I(Wf)})}y(Do),y(Js);var No=T(Js,2);{var Yf=I=>{var g=nf();kt(g,7,()=>(h(n(p)),d(()=>n(p).pipeline_stages)),(le,S)=>le.id||`${le.stage_type}-${le.environment}-${S}`,(le,S,dt)=>{var vt=rf(),xe=$(vt);{var ot=ne=>{var ve=Ya();k(ne,ve)},Wr=ne=>{var ve=Qa();k(ne,ve)},Yt=ne=>{var ve=Ka();k(ne,ve)},St=ne=>{var ve=Ja();k(ne,ve)},Dt=ne=>{var ve=Xa();k(ne,ve)};te(xe,ne=>{n(S),d(()=>n(S).status==="SUCCEEDED")?ne(ot):(n(S),d(()=>n(S).status==="RUNNING")?ne(Wr,1):(n(S),d(()=>n(S).status==="QUEUED")?ne(Yt,2):(n(S),d(()=>n(S).status==="FAILED")?ne(St,3):ne(Dt,-1))))})}var Qt=T(xe,2);{var Yr=ne=>{const ve=Ye(()=>(h(pr),n(S),d(()=>pr(n(S).environment||""))));var Nt=Za(),Ve=gt(Nt),pt=$(Ve,!0);y(Ve);var sr=T(Ve,2),O=$(sr),U=T(O);y(sr),B(Re=>{he(Ve,1,`text-sm ${n(S),d(()=>n(S).status==="SUCCEEDED"?"text-gray-700":n(S).status==="RUNNING"?"text-yellow-700":n(S).status==="FAILED"?"text-red-700":"text-gray-400")??""}`,"svelte-4kxpm1"),Y(pt,Re),he(sr,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${h(n(ve)),d(()=>n(ve).bg)??""}`,"svelte-4kxpm1"),Y(O,`${n(S),d(()=>n(S).environment)??""} `),he(U,1,`w-1.5 h-1.5 rounded-full ${h(n(ve)),d(()=>n(ve).dot)??""}`,"svelte-4kxpm1")},[()=>(h(yo),n(S),d(()=>yo(n(S).status)))]),k(ne,Nt)},ae=ne=>{var ve=ef(),Nt=$(ve);y(ve),B(Ve=>{he(ve,1,`text-sm ${n(S),d(()=>n(S).status==="SUCCEEDED"?"text-gray-700":n(S).status==="RUNNING"?"text-yellow-700":"text-gray-400")??""}`,"svelte-4kxpm1"),Y(Nt,`${Ve??""} ${n(S),d(()=>n(S).duration_seconds)??""}s`)},[()=>(h(ko),n(S),d(()=>ko(n(S).status)))]),k(ne,ve)};te(Qt,ne=>{n(S),d(()=>n(S).stage_type==="deploy")?ne(Yr):(n(S),d(()=>n(S).stage_type==="wait")&&ne(ae,1))})}var P=T(Qt,2);{var Kt=ne=>{var ve=tf(),Nt=$(ve,!0);y(ve),B(Ve=>Y(Nt,Ve),[()=>(n(S),d(()=>zs(n(S).started_at,n(S).completed_at,n(S).status)))]),k(ne,ve)};te(P,ne=>{n(S),d(()=>n(S).started_at&&(n(S).status==="RUNNING"||n(S).status==="QUEUED"||n(S).completed_at))&&ne(Kt)})}ar(2),y(vt),B(()=>he(vt,1,`px-4 py-2.5 flex items-center gap-3 text-sm ${h(n(dt)),h(n(p)),d(()=>n(dt)n(S).status==="PENDING"?"opacity-50":"")??""}`,"svelte-4kxpm1")),k(le,vt)}),y(g),k(I,g)};te(No,I=>{h(n(p)),d(()=>n(p).has_pipeline)&&I(Yf)})}var Qf=T(No,2);kt(Qf,3,()=>(h(n(p)),d(()=>n(p).destinations)),I=>I.name,(I,g,le)=>{const S=Ye(()=>(h(pr),n(g),d(()=>pr(n(g).environment||""))));var dt=hf(),vt=$(dt);{var xe=O=>{var U=sf();k(O,U)},ot=O=>{var U=of();k(O,U)},Wr=O=>{var U=lf();k(O,U)},Yt=O=>{var U=af();k(O,U)},St=O=>{var U=ff();k(O,U)};te(vt,O=>{n(g),d(()=>n(g).status==="SUCCEEDED")?O(xe):(n(g),d(()=>n(g).status==="RUNNING"||n(g).status==="ASSIGNED")?O(ot,1):(n(g),d(()=>n(g).status==="QUEUED")?O(Wr,2):(n(g),d(()=>n(g).status==="FAILED")?O(Yt,3):O(St,-1))))})}var Dt=T(vt,2),Qt=$(Dt),Yr=T(Qt);y(Dt);var ae=T(Dt,2),P=$(ae,!0);y(ae);var Kt=T(ae,2);{var ne=O=>{var U=cf();k(O,U)},ve=O=>{var U=uf();k(O,U)},Nt=O=>{var U=df(),Re=$(U);y(U),B(()=>Y(Re,`Queued${n(g),d(()=>n(g).queue_position?` #${n(g).queue_position}`:"")??""}`)),k(O,U)},Ve=O=>{var U=vf();k(O,U)};te(Kt,O=>{n(g),d(()=>n(g).status==="SUCCEEDED")?O(ne):(n(g),d(()=>n(g).status==="RUNNING")?O(ve,1):(n(g),d(()=>n(g).status==="QUEUED")?O(Nt,2):(n(g),d(()=>n(g).status==="FAILED")&&O(Ve,3))))})}var pt=T(Kt,2);{var sr=O=>{var U=pf(),Re=$(U,!0);y(U),B(xn=>Y(Re,xn),[()=>(h(on),n(g),d(()=>on(n(g).completed_at)))]),k(O,U)};te(pt,O=>{n(g),d(()=>n(g).completed_at)&&O(sr)})}y(dt),B(()=>{he(dt,1,`px-4 py-2 flex items-center gap-3 text-sm ${h(n(le)),h(n(p)),d(()=>n(le)n(S).bg)??""}`,"svelte-4kxpm1"),Y(Qt,`${n(g),d(()=>n(g).environment)??""} `),he(Yr,1,`w-1.5 h-1.5 rounded-full ${h(n(S)),d(()=>n(S).dot)??""}`,"svelte-4kxpm1"),Y(P,(n(g),d(()=>n(g).name)))}),k(I,dt)}),y(Un),y(ze),B(I=>{jr(ze,"data-envs",(h(n(p)),d(()=>n(p).dest_envs))),jr(jt,"href",`/orgs/${i()??""}/projects/${h(n(p)),h(o()),d(()=>n(p).project_name||o())??""}/releases/${h(n(p)),d(()=>n(p).slug)??""}`),Y(nr,(h(n(p)),d(()=>n(p).title))),Y(On,I),Y(Gf,(h(n(p)),d(()=>n(p).slug)))},[()=>(h(on),h(n(p)),d(()=>on(n(p).created_at)))]),Nn("toggle",Un,me),k(Oe,ze)},Xe=Oe=>{var p=xf(),ze=$(p),de=T($(ze)),Ct=T(de,3),jt=$(Ct);y(Ct);var nr=T(Ct,2),Ce=$(nr);y(nr),y(ze);var Se=T(ze,2);kt(Se,5,()=>(n(L),d(()=>n(L).releases||[])),Ae=>Ae.slug,(Ae,De)=>{var xr=mf(),br=$(xr),On=$(br),gn=T($(On),2),Ws=$(gn,!0);y(gn),y(On);var is=T(On,2),os=$(is);{var Un=Vr=>{var jn=gf(),Qs=$(jn,!0);y(jn),B(Ks=>Y(Qs,Ks),[()=>(n(De),d(()=>n(De).commit_sha.slice(0,7)))]),k(Vr,jn)};te(os,Vr=>{n(De),d(()=>n(De).commit_sha)&&Vr(Un)})}var mn=T(os,2),Ys=$(mn,!0);y(mn),y(is),y(br),y(xr),B(Vr=>{jr(gn,"href",`/orgs/${i()??""}/projects/${n(De),h(o()),d(()=>n(De).project_name||o())??""}/releases/${n(De),d(()=>n(De).slug)??""}`),Y(Ws,(n(De),d(()=>n(De).title))),Y(Ys,Vr)},[()=>(h(on),n(De),d(()=>on(n(De).created_at)))]),k(Ae,xr)}),y(Se),y(p),B(()=>{Y(de,` ${n(L),d(()=>n(L).count)??""} hidden commit${n(L),d(()=>n(L).count!==1?"s":"")??""} `),Y(jt,`Show commit${n(L),d(()=>n(L).count!==1?"s":"")??""}`),Y(Ce,`Hide commit${n(L),d(()=>n(L).count!==1?"s":"")??""}`)}),Nn("toggle",p,me),k(Oe,p)};te(se,Oe=>{n(L),d(()=>n(L).kind==="release"&&n(L).release)?Oe(Te):(n(L),d(()=>n(L).kind==="hidden")&&Oe(Xe,1))})}k(re,R)}),y(Z),_o(Z,re=>V(C,re),()=>n(C));var H=T(Z,2);kt(H,5,()=>n(f),re=>re.name,(re,L)=>{var R=bf();Ur(R,"width: 20px; margin-right: 4px; display: flex; justify-content: center;");var se=$(R),Te=$(se,!0);y(se),y(R),B(()=>{Ur(se,`writing-mode: vertical-rl; transform: rotate(180deg); font-size: 10px; font-weight: 500; color: ${n(L),d(()=>n(L).color)??""}; white-space: nowrap;`),Y(Te,(n(L),d(()=>n(L).name)))}),k(re,R)}),y(H),y(w),B(()=>Ur(w,`grid-template-columns: ${n(s)??""}px 1fr; grid-template-rows: 1fr auto;`)),k(_,w)};te(Vs,_=>{n(a)?_(ke):n(c)?_($e,1):(n(l),d(()=>n(l).length===0)?_($t,2):_(Gr,-1))})}return k(e,ss),Vn(Hr)}customElements.define("release-timeline",js(yf,{org:{},project:{}},[],[]));var Ef=N(' Waiting for logs…',1),$f=N('
'),Cf=N('
No logs recorded for this release.
'),Sf=N(''),Df=N(' Live'),Nf=Pe(''),Tf=Pe(''),Af=N(' '),Mf=N('
'),Rf=N(''),If=N('
',1),Lf=N("
");const qf={hash:"svelte-qvn6bd",code:`.logs-root.svelte-qvn6bd {position:relative;border:1px solid #e5e7eb;border-radius:0.5rem;overflow:hidden;font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;line-height:1.625;background:#111827;color:#d1d5db;}.logs-empty.svelte-qvn6bd {padding:2rem;text-align:center;color:#6b7280;font-family:system-ui, -apple-system, sans-serif;font-size:0.875rem;display:flex;align-items:center;justify-content:center;gap:0.5rem;}.logs-header.svelte-qvn6bd {display:flex;align-items:center;background:#1f2937;border-bottom:1px solid #374151;}.logs-tabs.svelte-qvn6bd {display:flex;gap:0;overflow-x:auto;flex:1;min-width:0;}.logs-tab.svelte-qvn6bd {padding:0.5rem 1rem;font-size:0.75rem;font-family:system-ui, -apple-system, sans-serif;color:#9ca3af;background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:0.375rem;transition:color 0.15s, border-color 0.15s;}.logs-tab.svelte-qvn6bd:hover {color:#e5e7eb;}.logs-tab.active.svelte-qvn6bd {color:#f9fafb;border-bottom-color:#3b82f6;}.logs-count.svelte-qvn6bd {font-size:0.625rem;padding:0.0625rem 0.375rem;border-radius:9999px;background:#374151;color:#9ca3af;}.logs-controls.svelte-qvn6bd {display:flex;align-items:center;gap:0.25rem;padding:0 0.5rem;flex-shrink:0;}.logs-ctrl-btn.svelte-qvn6bd {display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:0.25rem;border:none;background:transparent;color:#6b7280;cursor:pointer;transition:color 0.15s, background 0.15s;}.logs-ctrl-btn.svelte-qvn6bd:hover {color:#d1d5db;background:#374151;}.logs-ctrl-btn.active.svelte-qvn6bd {color:#93c5fd;background:#1e3a5f;}.logs-live.svelte-qvn6bd {display:flex;align-items:center;gap:0.375rem;font-family:system-ui, -apple-system, sans-serif;font-size:0.6875rem;color:#34d399;text-transform:uppercase;letter-spacing:0.05em;padding-right:0.5rem;}.logs-dot.svelte-qvn6bd {width:0.5rem;height:0.5rem;border-radius:9999px;background:#34d399;display:inline-block; + animation: svelte-4kxpm1-lane-pulse 2s ease-in-out infinite;}`};function zf(e,t){rs(t,!1),Qs(e,Pf);const r=at(),s=at();let o=mr(t,"org",12,""),l=mr(t,"project",12,""),i=mr(t,"csrf",12,""),a=mr(t,"username",12,""),f=mr(t,"role",12,""),c=at([]),v=at([]),m=at(!0),b=at(null),S=at(null),$=Date.now(),B=null,x=at(null),A=at({});const ae=20,ee=4,ne=12,Qe=new Set(["QUEUED","RUNNING","ASSIGNED"]),pe=new Set(["SUCCEEDED"]);let K=at(new Set),Ne=at(null);function Ke(){return f()==="owner"||f()==="admin"}function ht(h){return a()&&h.source_user===a()}async function Dt(h,k,T=!1){const te=`${h.release_intent_id}:${k.environment}`;if(!n(K).has(te)){n(K).add(te),U(K,n(K)),U(Ne,null);try{const P=new URLSearchParams;P.set("csrf_token",i()),P.set("release_intent_id",h.release_intent_id),P.set("target_environment",k.environment),T&&P.set("force_bypass","true");const re=await fetch(`/orgs/${o()}/projects/${h.project_name}/releases/${h.slug}/approve`,{method:"POST",body:P,credentials:"same-origin",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},redirect:"manual"});if(re.ok||re.status===303||re.status===302||re.status===0)await fe();else{const L=await re.text().catch(()=>"");let R;try{R=JSON.parse(L).error}catch{}if(!R){const oe=L.match(/]*>\s*(.*?)\s*<\/p>/);R=oe==null?void 0:oe[1]}U(Ne,R||`Approval failed (${re.status})`),setTimeout(()=>{U(Ne,null)},8e3)}}catch(P){U(Ne,P.message||"Approval request failed"),setTimeout(()=>{U(Ne,null)},8e3)}finally{n(K).delete(te),U(K,n(K))}}}let M=null;function se(){M||(M=setTimeout(()=>{M=null,fe()},300))}async function J(){try{U(b,null);const h=await Il(o(),l());Pe(h.timeline,h.lanes),U(m,!1),_t()}catch(h){U(b,h.message),U(m,!1)}}async function fe(){try{const h=await Il(o(),l());Pe(h.timeline,h.lanes),_t()}catch(h){console.warn("[release-timeline] refresh failed:",h)}}function Pe(h,k){const T=new Map;for(const P of n(c))P.kind==="release"&&P.release&&T.set(P.release.slug,P);const te=h.map(P=>{if(P.kind!=="release"||!P.release)return P;const re=T.get(P.release.slug);if(!re)return P;const L=re.release,R=P.release;return L.dest_envs===R.dest_envs&&L.has_pipeline===R.has_pipeline&&qe(L.pipeline_stages,R.pipeline_stages)&&ot(L.destinations,R.destinations)?re:P});U(c,te),U(v,k)}function qe(h,k){if(h.length!==k.length)return!1;for(let T=0;T{if(oe.kind!=="release"||!oe.release)return oe;const Te=oe.release;if(Te.destinations.findIndex(ve=>ve.name===T)===-1)return oe;P=!0;const Oe=Te.destinations.map(ve=>ve.name===T?{...ve,status:k,...["SUCCEEDED","FAILED","TIMED_OUT","CANCELLED"].includes(k)?{completed_at:new Date().toISOString()}:{}}:ve),p=Oe.map(ve=>`${ve.environment}:${ve.status||"PENDING"}`).join(","),ze=te?Te.pipeline_stages.map(ve=>ve.stage_type==="deploy"&&ve.environment===te?{...ve,status:k==="ASSIGNED"?"RUNNING":k}:ve):Te.pipeline_stages;return{...oe,release:{...Te,destinations:Oe,dest_envs:p,pipeline_stages:ze}}})),P&&_t()}function En(h){var te,P;const k=(te=h.metadata)==null?void 0:te.status,T=(P=h.metadata)==null?void 0:P.environment;k&&T?yr(h):se()}function eo(h){var re,L,R;const k=(re=h.metadata)==null?void 0:re.status,T=(L=h.metadata)==null?void 0:L.environment,te=(R=h.metadata)==null?void 0:R.stage_type;if(!k){(h.action==="created"||h.action==="updated")&&se();return}let P=!1;U(c,n(c).map(oe=>{if(oe.kind!=="release"||!oe.release)return oe;const Te=oe.release;let Xe=!1;const Oe=Te.pipeline_stages.map(p=>T&&p.stage_type==="deploy"&&p.environment===T?(Xe=!0,{...p,status:k,...p.started_at?{}:{started_at:new Date().toISOString()}}):te==="wait"&&p.stage_type==="wait"?(Xe=!0,{...p,status:k}):p);return Xe?(P=!0,{...oe,release:{...Te,pipeline_stages:Oe}}):oe})),P&&_t()}function hs(h){return h?h.split(",").map(k=>k.trim()).filter(Boolean).map(k=>{const T=k.indexOf(":");return T===-1?{env:k,status:"SUCCEEDED"}:{env:k.slice(0,T),status:k.slice(T+1)}}):[]}let Qr=null;function _t(){Qr||(Qr=requestAnimationFrame(()=>{Qr=null,ea().then(to)}))}function to(){if(!n(x))return;const h=n(x).getBoundingClientRect();if(h.height===0)return;const k=h.height,T=Array.from(n(x).querySelectorAll("[data-release]")),te={};for(const P of n(v)){const re=P.name;let L=null,R=null,oe=-1,Te=-1;for(let $e=0;$e$r.env===re))continue;const Er=($e.querySelector("[data-avatar]")||$e).getBoundingClientRect();lr.push(Er.top+Er.height/2-h.top)}te[re]={solidH:p,hasHatch:ze,hatchTop:ve,hatchH:Tt,isForward:jt,dots:lr,color:Ta(re)}}U(A,te)}const _s=new Map;function xe(h,k){const T=`${h}|${k}`;let te=_s.get(T);if(te)return te;const P=``;return te=`url("data:image/svg+xml,${encodeURIComponent(P)}")`,_s.set(T,te),te}$l(()=>{J(),B=setInterval(()=>{$=Date.now()},1e4)}),ca(()=>{n(S)&&n(S)(),B&&clearInterval(B),M&&clearTimeout(M),Qr&&cancelAnimationFrame(Qr)});function Ee(){_t()}function Nt(h,k,T){if(!h)return"";const te=new Date(h).getTime();if(isNaN(te))return"";if(k&&T!=="RUNNING"&&T!=="QUEUED"){const P=new Date(k).getTime();if(!isNaN(P))return Ll(Math.floor((P-te)/1e3))}return Ll(Math.floor(($-te)/1e3))}function Kr(h){var k;return h.kind==="release"&&h.release?`r:${h.release.slug}`:h.kind==="hidden"?`h:${h.count}:${((k=(h.releases||[])[0])==null?void 0:k.slug)||""}`:`u:${Math.random()}`}function Jr(h,k){if(!h)return!1;switch(h.label){case"Pipeline complete":return k==="SUCCEEDED";case"Pipeline failed":return k==="FAILED"||k==="RUNNING"||k==="ASSIGNED";case"Deploying to":return k==="RUNNING"||k==="ASSIGNED";case"Queued":return k==="QUEUED";case"Waiting for time window":return k==="RUNNING"||k==="ASSIGNED";default:return k!=="PENDING"&&k!=="SUCCEEDED"}}Us(()=>(n(m),n(b),_(o()),n(S),_(l())),()=>{!n(m)&&!n(b)&&o()&&!n(S)&&U(S,Da(o(),l(),Je))}),Us(()=>n(v),()=>{U(r,n(v).length)}),Us(()=>n(r),()=>{U(s,n(r)*(ae+ee)+8)}),Qi();var $n={get org(){return o()},set org(h){o(h),Ht()},get project(){return l()},set project(h){l(h),Ht()},get csrf(){return i()},set csrf(h){i(h),Ht()},get username(){return a()},set username(h){a(h),Ht()},get role(){return f()},set role(h){f(h),Ht()}};ya();var Xr=Ff();_r("resize",Is,Ee);var Zr=ft(Xr);{var Wn=h=>{var k=Aa(),T=D(E(k)),te=D(T);y(k),j(()=>V(T,` ${n(Ne)??""} `)),_r("click",te,()=>U(Ne,null)),w(h,k)};Q(Zr,h=>{n(Ne)&&h(Wn)})}var Cn=D(Zr,2);{var ro=h=>{var k=Ma();w(h,k)},no=h=>{var k=Ra(),T=E(k),te=E(T,!0);y(T);var P=D(T,2);y(k),j(()=>V(te,n(b))),_r("click",P,J),w(h,k)},so=h=>{var k=Ia();w(h,k)},ac=h=>{var k=Bf(),T=E(k);Ct(T,5,()=>n(v),re=>re.name,(re,L)=>{const R=Ve(()=>(n(A),n(L),d(()=>n(A)[n(L).name]))),oe=Ve(()=>{const[p,ze]=(_(n(R)),n(L),d(()=>{var ve;return((ve=n(R))==null?void 0:ve.color)||[n(L).color,"#e5e7eb"]}));return{barColor:p,lightColor:ze}});var Te=ja();zr(Te,"width: 20px; margin-right: 4px; position: relative;");var Xe=E(Te);{var Oe=p=>{var ze=Ua(),ve=ft(ze);{var Tt=Ce=>{var Ae=La();j(Se=>zr(Ae,`position: absolute; left: 0; width: 100%; top: ${_(n(R)),d(()=>n(R).hatchTop)??""}px; height: ${_(n(R)),d(()=>n(R).hatchH+(n(R).solidH>0?ae/2:0))??""}px; background-image: ${Se??""}; background-size: 8px 8px; background-repeat: repeat; border-radius: 9999px; z-index: 0;`),[()=>(_(n(R)),_(n(oe).barColor),_(n(oe).lightColor),d(()=>n(R).isForward?xe(n(oe).barColor,n(oe).lightColor):xe("#f59e0b","#fef3c7")))]),w(Ce,Ae)};Q(ve,Ce=>{_(n(R)),d(()=>n(R).hasHatch)&&Ce(Tt)})}var jt=D(ve,2);{var lr=Ce=>{var Ae=qa();j(()=>zr(Ae,`position: absolute; bottom: 0; left: 0; width: 100%; height: ${_(n(R)),d(()=>n(R).solidH+(n(R).hasHatch?ae/2:0))??""}px; background: ${n(oe).barColor??""}; border-radius: 9999px; z-index: 1;`)),w(Ce,Ae)};Q(jt,Ce=>{_(n(R)),d(()=>n(R).solidH>0)&&Ce(lr)})}var $e=D(jt,2);Ct($e,1,()=>(_(n(R)),d(()=>n(R).dots)),cs,(Ce,Ae)=>{var Se=Oa();j(()=>zr(Se,`position: absolute; left: 50%; transform: translateX(-50%); top: ${n(Ae)-ne/2}px; width: 12px; height: 12px; border-radius: 50%; background: #fff; border: 2px solid ${n(oe).barColor??""}; z-index: 2;`)),w(Ce,Se)}),w(p,ze)};Q(Xe,p=>{n(R)&&p(Oe)})}y(Te),w(re,Te)}),y(T);var te=D(T,2);Ct(te,5,()=>n(c),re=>Kr(re),(re,L)=>{var R=Fn(),oe=ft(R);{var Te=Oe=>{const p=Ve(()=>(n(L),d(()=>n(L).release)));var ze=Lf(),ve=E(ze),Tt=E(ve),jt=D(E(Tt),2),lr=E(jt,!0);y(jt),y(Tt);var $e=D(Tt,2),Ce=E($e);{var Ae=q=>{var g=Ba(),ce=D(E(g));y(g),j(()=>V(ce,` ${_(n(p)),d(()=>n(p).branch)??""}`)),w(q,g)};Q(Ce,q=>{_(n(p)),d(()=>n(p).branch)&&q(Ae)})}var Se=D(Ce,2);{var Er=q=>{var g=Fa(),ce=E(g,!0);y(g),j(N=>V(ce,N),[()=>(_(n(p)),d(()=>n(p).commit_sha.slice(0,7)))]),w(q,g)};Q(Se,q=>{_(n(p)),d(()=>n(p).commit_sha)&&q(Er)})}var $r=D(Se,2),Yn=E($r,!0);y($r);var Sn=D($r,2);{var oo=q=>{var g=Pa(),ce=D(E(g),2),N=E(ce,!0);y(ce),y(g),j(()=>{Hr(ce,"href",`/users/${_(n(p)),d(()=>n(p).source_user)??""}`),V(N,(_(n(p)),d(()=>n(p).source_user)))}),w(q,g)};Q(Sn,q=>{_(n(p)),d(()=>n(p).source_user)&&q(oo)})}var gs=D(Sn,2);{var ms=q=>{var g=za(),ce=E(g,!0);y(g),j(()=>{Hr(g,"href",`/orgs/${o()??""}/projects/${_(n(p)),d(()=>n(p).project_name)??""}`),V(ce,(_(n(p)),d(()=>n(p).project_name)))}),w(q,g)};Q(gs,q=>{_(n(p)),_(l()),d(()=>n(p).project_name&&n(p).project_name!==l())&&q(ms)})}y($e),y(ve);var Qn=D(ve,2),Dn=E(Qn),lo=E(Dn);{var en=q=>{const g=Ve(()=>(_(n(p)),d(()=>n(p).env_groups&&n(p).env_groups.length>0&&n(p).env_groups.every(me=>me.status==="SUCCEEDED"))));var ce=Va(),N=D(ft(ce));{var gt=me=>{var lt=Ha();dr(2),w(me,lt)},mt=me=>{var lt=Ga();dr(2),w(me,lt)};Q(N,me=>{n(g)?me(gt):me(mt,-1)})}w(q,ce)},Kn=Vt(()=>(_(n(p)),_(hn),d(()=>n(p).has_pipeline&&!hn(n(p).pipeline_stages)))),io=q=>{const g=Ve(()=>(_(hn),_(n(p)),d(()=>hn(n(p).pipeline_stages))));var ce=nf(),N=D(ft(ce),2);{var gt=z=>{var H=Wa();w(z,H)},mt=z=>{var H=Ya();j(()=>he(H,0,`w-4 h-4 ${_(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(z,H)},me=z=>{var H=Qa();j(()=>he(H,0,`w-4 h-4 ${_(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(z,H)},lt=z=>{var H=Ka();j(()=>he(H,0,`w-4 h-4 ${_(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(z,H)},tn=z=>{var H=Ja();j(()=>he(H,0,`w-4 h-4 ${_(n(g)),d(()=>n(g).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(z,H)},rn=z=>{var H=Xa();w(z,H)};Q(N,z=>{_(n(g)),d(()=>n(g).icon==="pulse")?z(gt):(_(n(g)),d(()=>n(g).icon==="check-circle")?z(mt,1):(_(n(g)),d(()=>n(g).icon==="x-circle")?z(me,2):(_(n(g)),d(()=>n(g).icon==="clock")?z(lt,3):(_(n(g)),d(()=>n(g).icon==="shield")?z(tn,4):z(rn,-1)))))})}var Ze=D(N,2),Bt=E(Ze,!0);y(Ze);var Xt=D(Ze,2);Ct(Xt,1,()=>(_(n(p)),d(()=>n(p).pipeline_stages)),z=>z.id||z.environment||z.stage_type,(z,H)=>{var le=rf(),ue=ft(le);{var At=O=>{const I=Ve(()=>(_(xr),n(H),d(()=>xr(n(H).environment||"")))),De=Ve(()=>(_(ql),n(H),_(n(I)),d(()=>ql(n(H).status)||n(I).dot)));var xt=Za(),ar=E(xt),Nn=D(ar);y(xt),j(()=>{he(xt,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${_(n(I)),d(()=>n(I).bg)??""}`,"svelte-4kxpm1"),V(ar,`${n(H),d(()=>n(H).environment)??""} `),he(Nn,1,`w-1.5 h-1.5 rounded-full ${n(De)??""}`,"svelte-4kxpm1")}),w(O,xt)},it=Vt(()=>(n(H),_(n(g)),d(()=>n(H).stage_type==="deploy"&&Jr(n(g),n(H).status))));Q(ue,O=>{n(it)&&O(At)})}var Zt=D(ue,2);{var ir=O=>{var I=Fn(),De=ft(I);{var xt=Tn=>{var nn=ef();j(vo=>nn.disabled=vo,[()=>(n(K),_(n(p)),n(H),d(()=>n(K).has(`${n(p).release_intent_id}:${n(H).environment}`)))]),_r("click",nn,Al(()=>{confirm("You are the release author. Bypass approval?")&&Dt(n(p),n(H),!0)})),w(Tn,nn)},ar=Vt(()=>(_(n(p)),d(()=>ht(n(p))&&Ke()))),Nn=Tn=>{var nn=tf();j(vo=>nn.disabled=vo,[()=>(n(K),_(n(p)),n(H),d(()=>n(K).has(`${n(p).release_intent_id}:${n(H).environment}`)))]),_r("click",nn,Al(()=>Dt(n(p),n(H)))),w(Tn,nn)},uo=Vt(()=>(_(n(p)),d(()=>!ht(n(p)))));Q(De,Tn=>{n(ar)?Tn(xt):n(uo)&&Tn(Nn,1)})}w(O,I)};Q(Zt,O=>{n(H),_(n(p)),_(i()),d(()=>n(H).blocked_by&&n(p).release_intent_id&&i())&&O(ir)})}w(z,le)});var Cr=D(Xt,2),Ft=E(Cr);y(Cr),j(()=>{he(Ze,1,`${_(n(g)),d(()=>n(g).color)??""} text-sm`,"svelte-4kxpm1"),V(Bt,(_(n(g)),d(()=>n(g).label))),V(Ft,`${_(n(g)),d(()=>n(g).done)??""}/${_(n(g)),d(()=>n(g).total)??""}`)}),w(q,ce)},ao=Vt(()=>(_(n(p)),_(hn),d(()=>n(p).has_pipeline&&hn(n(p).pipeline_stages)))),fc=q=>{const g=Ve(()=>(_(n(p)),d(()=>n(p).env_groups.every(me=>me.status==="SUCCEEDED"))));var ce=Fn(),N=ft(ce);{var gt=me=>{var lt=sf();dr(2),w(me,lt)},mt=me=>{var lt=Fn(),tn=ft(lt);Ct(tn,1,()=>(_(n(p)),d(()=>n(p).env_groups)),cs,(rn,Ze)=>{var Bt=Fn(),Xt=ft(Bt);{var Cr=Ft=>{const z=Ve(()=>(_(Xs),n(Ze),d(()=>Xs[n(Ze).status]||Xs.SUCCEEDED)));var H=cf(),le=ft(H);{var ue=I=>{var De=of();w(I,De)},At=I=>{var De=lf();j(()=>he(De,0,`w-4 h-4 ${_(n(z)),d(()=>n(z).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(I,De)},it=I=>{var De=af();j(()=>he(De,0,`w-4 h-4 ${_(n(z)),d(()=>n(z).iconColor)??""} shrink-0`,"svelte-4kxpm1")),w(I,De)};Q(le,I=>{_(n(z)),d(()=>n(z).icon==="pulse")?I(ue):(_(n(z)),d(()=>n(z).icon==="check-circle")?I(At,1):I(it,-1))})}var Zt=D(le,2),ir=E(Zt,!0);y(Zt);var O=D(Zt,2);Ct(O,1,()=>(n(Ze),d(()=>n(Ze).envs)),I=>I,(I,De)=>{const xt=Ve(()=>(_(xr),n(De),d(()=>xr(n(De)))));var ar=ff(),Nn=E(ar),uo=D(Nn);y(ar),j(()=>{he(ar,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${_(n(xt)),d(()=>n(xt).bg)??""}`,"svelte-4kxpm1"),V(Nn,`${n(De)??""} `),he(uo,1,`w-1.5 h-1.5 rounded-full ${_(n(xt)),d(()=>n(xt).dot)??""}`,"svelte-4kxpm1")}),w(I,ar)}),j(()=>{he(Zt,1,`${_(n(z)),d(()=>n(z).color)??""} text-sm`,"svelte-4kxpm1"),V(ir,(_(n(z)),d(()=>n(z).label)))}),w(Ft,H)};Q(Xt,Ft=>{n(Ze),d(()=>n(Ze).status!=="SUCCEEDED")&&Ft(Cr)})}w(rn,Bt)}),w(me,lt)};Q(N,me=>{n(g)?me(gt):me(mt,-1)})}w(q,ce)},cc=q=>{var g=uf();dr(2),w(q,g)};Q(lo,q=>{n(Kn)?q(en):n(ao)?q(io,1):(_(n(p)),d(()=>n(p).env_groups&&n(p).env_groups.length>0)?q(fc,2):q(cc,-1))})}dr(2),y(Dn);var fo=D(Dn,2),Pl=E(fo);{var uc=q=>{var g=df(),ce=E(g,!0);y(g),j(()=>V(ce,(_(n(p)),d(()=>n(p).description)))),w(q,g)};Q(Pl,q=>{_(n(p)),d(()=>n(p).description)&&q(uc)})}var zl=D(Pl,2),co=E(zl),dc=E(co,!0);y(co);var vc=D(co,2);{var pc=q=>{var g=vf(),ce=E(g,!0);y(g),j(()=>V(ce,(_(n(p)),d(()=>n(p).version)))),w(q,g)};Q(vc,q=>{_(n(p)),d(()=>n(p).version)&&q(pc)})}y(zl),y(fo);var Hl=D(fo,2);{var hc=q=>{var g=yf();Ct(g,7,()=>(_(n(p)),d(()=>n(p).pipeline_stages)),(ce,N)=>ce.id||`${ce.stage_type}-${ce.environment}-${N}`,(ce,N,gt)=>{var mt=kf(),me=E(mt);{var lt=le=>{var ue=pf();w(le,ue)},tn=le=>{var ue=hf();w(le,ue)},rn=le=>{var ue=_f();w(le,ue)},Ze=le=>{var ue=gf();w(le,ue)},Bt=le=>{var ue=mf();w(le,ue)};Q(me,le=>{n(N),d(()=>n(N).status==="SUCCEEDED")?le(lt):(n(N),d(()=>n(N).status==="RUNNING")?le(tn,1):(n(N),d(()=>n(N).status==="QUEUED")?le(rn,2):(n(N),d(()=>n(N).status==="FAILED")?le(Ze,3):le(Bt,-1))))})}var Xt=D(me,2);{var Cr=le=>{const ue=Ve(()=>(_(xr),n(N),d(()=>xr(n(N).environment||""))));var At=xf(),it=ft(At),Zt=E(it,!0);y(it);var ir=D(it,2),O=E(ir),I=D(O);y(ir),j(De=>{he(it,1,`text-sm ${n(N),d(()=>n(N).status==="SUCCEEDED"?"text-gray-700":n(N).status==="RUNNING"?"text-yellow-700":n(N).status==="FAILED"?"text-red-700":"text-gray-400")??""}`,"svelte-4kxpm1"),V(Zt,De),he(ir,1,`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${_(n(ue)),d(()=>n(ue).bg)??""}`,"svelte-4kxpm1"),V(O,`${n(N),d(()=>n(N).environment)??""} `),he(I,1,`w-1.5 h-1.5 rounded-full ${_(n(ue)),d(()=>n(ue).dot)??""}`,"svelte-4kxpm1")},[()=>(_(Ul),n(N),d(()=>Ul(n(N).status)))]),w(le,At)},Ft=le=>{var ue=bf(),At=E(ue);y(ue),j(it=>{he(ue,1,`text-sm ${n(N),d(()=>n(N).status==="SUCCEEDED"?"text-gray-700":n(N).status==="RUNNING"?"text-yellow-700":"text-gray-400")??""}`,"svelte-4kxpm1"),V(At,`${it??""} ${n(N),d(()=>n(N).duration_seconds)??""}s`)},[()=>(_(Ol),n(N),d(()=>Ol(n(N).status)))]),w(le,ue)};Q(Xt,le=>{n(N),d(()=>n(N).stage_type==="deploy")?le(Cr):(n(N),d(()=>n(N).stage_type==="wait")&&le(Ft,1))})}var z=D(Xt,2);{var H=le=>{var ue=wf(),At=E(ue,!0);y(ue),j(it=>V(At,it),[()=>(n(N),d(()=>Nt(n(N).started_at,n(N).completed_at,n(N).status)))]),w(le,ue)};Q(z,le=>{n(N),d(()=>n(N).started_at&&(n(N).status==="RUNNING"||n(N).status==="QUEUED"||n(N).completed_at))&&le(H)})}dr(2),y(mt),j(()=>he(mt,1,`px-4 py-2.5 flex items-center gap-3 text-sm ${_(n(gt)),_(n(p)),d(()=>n(gt)n(N).status==="PENDING"?"opacity-50":"")??""}`,"svelte-4kxpm1")),w(ce,mt)}),y(g),w(q,g)};Q(Hl,q=>{_(n(p)),d(()=>n(p).has_pipeline)&&q(hc)})}var _c=D(Hl,2);Ct(_c,3,()=>(_(n(p)),d(()=>n(p).destinations)),q=>q.name,(q,g,ce)=>{const N=Ve(()=>(_(xr),n(g),d(()=>xr(n(g).environment||""))));var gt=If(),mt=E(gt);{var me=O=>{var I=Ef();w(O,I)},lt=O=>{var I=$f();w(O,I)},tn=O=>{var I=Cf();w(O,I)},rn=O=>{var I=Sf();w(O,I)},Ze=O=>{var I=Df();w(O,I)};Q(mt,O=>{n(g),d(()=>n(g).status==="SUCCEEDED")?O(me):(n(g),d(()=>n(g).status==="RUNNING"||n(g).status==="ASSIGNED")?O(lt,1):(n(g),d(()=>n(g).status==="QUEUED")?O(tn,2):(n(g),d(()=>n(g).status==="FAILED")?O(rn,3):O(Ze,-1))))})}var Bt=D(mt,2),Xt=E(Bt),Cr=D(Xt);y(Bt);var Ft=D(Bt,2),z=E(Ft,!0);y(Ft);var H=D(Ft,2);{var le=O=>{var I=Nf();w(O,I)},ue=O=>{var I=Tf();w(O,I)},At=O=>{var I=Af(),De=E(I);y(I),j(()=>V(De,`Queued${n(g),d(()=>n(g).queue_position?` #${n(g).queue_position}`:"")??""}`)),w(O,I)},it=O=>{var I=Mf();w(O,I)};Q(H,O=>{n(g),d(()=>n(g).status==="SUCCEEDED")?O(le):(n(g),d(()=>n(g).status==="RUNNING")?O(ue,1):(n(g),d(()=>n(g).status==="QUEUED")?O(At,2):(n(g),d(()=>n(g).status==="FAILED")&&O(it,3))))})}var Zt=D(H,2);{var ir=O=>{var I=Rf(),De=E(I,!0);y(I),j(xt=>V(De,xt),[()=>(_(pn),n(g),d(()=>pn(n(g).completed_at)))]),w(O,I)};Q(Zt,O=>{n(g),d(()=>n(g).completed_at)&&O(ir)})}y(gt),j(()=>{he(gt,1,`px-4 py-2 flex items-center gap-3 text-sm ${_(n(ce)),_(n(p)),d(()=>n(ce)n(N).bg)??""}`,"svelte-4kxpm1"),V(Xt,`${n(g),d(()=>n(g).environment)??""} `),he(Cr,1,`w-1.5 h-1.5 rounded-full ${_(n(N)),d(()=>n(N).dot)??""}`,"svelte-4kxpm1"),V(z,(n(g),d(()=>n(g).name)))}),w(q,gt)}),y(Qn),y(ze),j(q=>{Hr(ze,"data-envs",(_(n(p)),d(()=>n(p).dest_envs))),Hr(jt,"href",`/orgs/${o()??""}/projects/${_(n(p)),_(l()),d(()=>n(p).project_name||l())??""}/releases/${_(n(p)),d(()=>n(p).slug)??""}`),V(lr,(_(n(p)),d(()=>n(p).title))),V(Yn,q),V(dc,(_(n(p)),d(()=>n(p).slug)))},[()=>(_(pn),_(n(p)),d(()=>pn(n(p).created_at)))]),_r("toggle",Qn,_t),w(Oe,ze)},Xe=Oe=>{var p=Uf(),ze=E(p),ve=D(E(ze)),Tt=D(ve,3),jt=E(Tt);y(Tt);var lr=D(Tt,2),$e=E(lr);y(lr),y(ze);var Ce=D(ze,2);Ct(Ce,5,()=>(n(L),d(()=>n(L).releases||[])),Ae=>Ae.slug,(Ae,Se)=>{var Er=Of(),$r=E(Er),Yn=E($r),Sn=D(E(Yn),2),oo=E(Sn,!0);y(Sn),y(Yn);var gs=D(Yn,2),ms=E(gs);{var Qn=en=>{var Kn=qf(),io=E(Kn,!0);y(Kn),j(ao=>V(io,ao),[()=>(n(Se),d(()=>n(Se).commit_sha.slice(0,7)))]),w(en,Kn)};Q(ms,en=>{n(Se),d(()=>n(Se).commit_sha)&&en(Qn)})}var Dn=D(ms,2),lo=E(Dn,!0);y(Dn),y(gs),y($r),y(Er),j(en=>{Hr(Sn,"href",`/orgs/${o()??""}/projects/${n(Se),_(l()),d(()=>n(Se).project_name||l())??""}/releases/${n(Se),d(()=>n(Se).slug)??""}`),V(oo,(n(Se),d(()=>n(Se).title))),V(lo,en)},[()=>(_(pn),n(Se),d(()=>pn(n(Se).created_at)))]),w(Ae,Er)}),y(Ce),y(p),j(()=>{V(ve,` ${n(L),d(()=>n(L).count)??""} hidden commit${n(L),d(()=>n(L).count!==1?"s":"")??""} `),V(jt,`Show commit${n(L),d(()=>n(L).count!==1?"s":"")??""}`),V($e,`Hide commit${n(L),d(()=>n(L).count!==1?"s":"")??""}`)}),_r("toggle",p,_t),w(Oe,p)};Q(oe,Oe=>{n(L),d(()=>n(L).kind==="release"&&n(L).release)?Oe(Te):(n(L),d(()=>n(L).kind==="hidden")&&Oe(Xe,1))})}w(re,R)}),y(te),Tl(te,re=>U(x,re),()=>n(x));var P=D(te,2);Ct(P,5,()=>n(v),re=>re.name,(re,L)=>{var R=jf();zr(R,"width: 20px; margin-right: 4px; display: flex; justify-content: center;");var oe=E(R),Te=E(oe,!0);y(oe),y(R),j(()=>{zr(oe,`writing-mode: vertical-rl; transform: rotate(180deg); font-size: 10px; font-weight: 500; color: ${n(L),d(()=>n(L).color)??""}; white-space: nowrap;`),V(Te,(n(L),d(()=>n(L).name)))}),w(re,R)}),y(P),y(k),j(()=>zr(k,`grid-template-columns: ${n(s)??""}px 1fr; grid-template-rows: 1fr auto;`)),w(h,k)};Q(Cn,h=>{n(m)?h(ro):n(b)?h(no,1):(n(c),d(()=>n(c).length===0)?h(so,2):h(ac,-1))})}return w(e,Xr),ns($n)}customElements.define("release-timeline",Ks(zf,{org:{},project:{},csrf:{},username:{},role:{}},[],[]));var Hf=C(' Waiting for logs…',1),Gf=C('
'),Vf=C('
No logs recorded for this release.
'),Wf=C(''),Yf=C(' Live'),Qf=Le(''),Kf=Le(''),Jf=C(' '),Xf=C('
'),Zf=C(''),ec=C('
',1),tc=C("
");const rc={hash:"svelte-qvn6bd",code:`.logs-root.svelte-qvn6bd {position:relative;border:1px solid #e5e7eb;border-radius:0.5rem;overflow:hidden;font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;line-height:1.625;background:#111827;color:#d1d5db;}.logs-empty.svelte-qvn6bd {padding:2rem;text-align:center;color:#6b7280;font-family:system-ui, -apple-system, sans-serif;font-size:0.875rem;display:flex;align-items:center;justify-content:center;gap:0.5rem;}.logs-header.svelte-qvn6bd {display:flex;align-items:center;background:#1f2937;border-bottom:1px solid #374151;}.logs-tabs.svelte-qvn6bd {display:flex;gap:0;overflow-x:auto;flex:1;min-width:0;}.logs-tab.svelte-qvn6bd {padding:0.5rem 1rem;font-size:0.75rem;font-family:system-ui, -apple-system, sans-serif;color:#9ca3af;background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:0.375rem;transition:color 0.15s, border-color 0.15s;}.logs-tab.svelte-qvn6bd:hover {color:#e5e7eb;}.logs-tab.active.svelte-qvn6bd {color:#f9fafb;border-bottom-color:#3b82f6;}.logs-count.svelte-qvn6bd {font-size:0.625rem;padding:0.0625rem 0.375rem;border-radius:9999px;background:#374151;color:#9ca3af;}.logs-controls.svelte-qvn6bd {display:flex;align-items:center;gap:0.25rem;padding:0 0.5rem;flex-shrink:0;}.logs-ctrl-btn.svelte-qvn6bd {display:flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border-radius:0.25rem;border:none;background:transparent;color:#6b7280;cursor:pointer;transition:color 0.15s, background 0.15s;}.logs-ctrl-btn.svelte-qvn6bd:hover {color:#d1d5db;background:#374151;}.logs-ctrl-btn.active.svelte-qvn6bd {color:#93c5fd;background:#1e3a5f;}.logs-live.svelte-qvn6bd {display:flex;align-items:center;gap:0.375rem;font-family:system-ui, -apple-system, sans-serif;font-size:0.6875rem;color:#34d399;text-transform:uppercase;letter-spacing:0.05em;padding-right:0.5rem;}.logs-dot.svelte-qvn6bd {width:0.5rem;height:0.5rem;border-radius:9999px;background:#34d399;display:inline-block; animation: svelte-qvn6bd-pulse 2s ease-in-out infinite;} @keyframes svelte-qvn6bd-pulse { @@ -16,7 +16,7 @@ var Jf=Object.defineProperty;var Ao=_e=>{throw TypeError(_e)};var Xf=(_e,fe,Me)= 50% { opacity: 0.4; } - }.logs-output.svelte-qvn6bd {max-height:60vh;overflow-y:auto;padding:0.25rem 0;}.logs-root.expanded.svelte-qvn6bd .logs-output:where(.svelte-qvn6bd) {max-height:85vh;}.logs-output.svelte-qvn6bd::-webkit-scrollbar {width:0.5rem;}.logs-output.svelte-qvn6bd::-webkit-scrollbar-track {background:#1f2937;}.logs-output.svelte-qvn6bd::-webkit-scrollbar-thumb {background:#4b5563;border-radius:0.25rem;}.logs-line.svelte-qvn6bd {display:flex;padding:0 1rem 0 0;gap:0;min-height:1.5rem;}.logs-line.svelte-qvn6bd:hover {background:rgba(255, 255, 255, 0.04);}.logs-line.stderr.svelte-qvn6bd {color:#fca5a5;background:rgba(239, 68, 68, 0.06);}.logs-line.stderr.svelte-qvn6bd:hover {background:rgba(239, 68, 68, 0.1);}.logs-line.status-line.svelte-qvn6bd {color:#93c5fd;font-weight:600;padding-top:0.375rem;padding-bottom:0.375rem;border-top:1px solid #1e3a5f;margin-top:0.25rem;}.logs-ts.svelte-qvn6bd {color:#4b5563;white-space:nowrap;user-select:none;flex-shrink:0;width:3.5rem;text-align:right;padding-right:1rem;padding-left:0.75rem;border-right:1px solid #1f2937;margin-right:0.75rem;}.logs-text.svelte-qvn6bd {white-space:pre-wrap;word-break:break-all;flex:1;min-width:0;padding-left:1rem;}.logs-line.svelte-qvn6bd .logs-ts:where(.svelte-qvn6bd) + .logs-text:where(.svelte-qvn6bd) {padding-left:0;}.logs-scroll-btn.svelte-qvn6bd {position:absolute;bottom:0.75rem;left:50%;transform:translateX(-50%);padding:0.25rem 0.75rem;font-size:0.6875rem;font-family:system-ui, -apple-system, sans-serif;color:#d1d5db;background:#374151;border:1px solid #4b5563;border-radius:9999px;cursor:pointer;opacity:0.9;transition:opacity 0.15s;}.logs-scroll-btn.svelte-qvn6bd:hover {opacity:1;background:#4b5563;}`};function Of(e,t){Gn(t,!0),Us(e,qf);let r=Rn(t,"url",7,""),s=Le(Mr({})),i=Le(null),o=Le(!1),l=Le(!1),f=Le(!0),a=Le(!0),c=Le(!1),v=Le(null),m=cr(()=>Object.keys(n(s)).sort()),b=cr(()=>n(i)&&n(s)[n(i)]?n(s)[n(i)]:[]);function C(){if(!r())return;const M=new EventSource(r());return V(o,!0),M.addEventListener("log",ie=>{try{const X=JSON.parse(ie.data),oe=X.destination||"unknown";n(s)[oe]||(n(s)[oe]=[],n(i)||V(i,oe,!0)),n(s)[oe]=[...n(s)[oe],{line:X.line,timestamp:X.timestamp,channel:X.channel||"stdout"}],n(f)&&requestAnimationFrame(()=>{n(v)&&(n(v).scrollTop=n(v).scrollHeight)})}catch(X){console.warn("[release-logs] bad log event:",X)}}),M.addEventListener("status",ie=>{try{const X=JSON.parse(ie.data),oe=X.destination||"unknown";n(s)[oe]||(n(s)[oe]=[],n(i)||V(i,oe,!0)),n(s)[oe]=[...n(s)[oe],{line:`── ${X.status} ──`,timestamp:"",channel:"status"}]}catch{}}),M.addEventListener("done",()=>{V(l,!0)}),M.addEventListener("error",()=>{V(o,!1),M.close()}),()=>{M.close(),V(o,!1)}}Cn(()=>{if(r())return C()});function E(){if(!n(v))return;const M=n(v).scrollHeight-n(v).scrollTop-n(v).clientHeight<40;V(f,M)}function q(){n(v)&&(n(v).scrollTop=n(v).scrollHeight,V(f,!0))}function x(M){if(!M)return null;const ie=Number(M);if(Number.isFinite(ie)&&ie>1e12)return ie;const X=new Date(M);return isNaN(X.getTime())?null:X.getTime()}function A(M,ie){const X=x(M);if(X===null||ie===null)return"";const oe=X-ie;if(oe<0)return"0s";const Ne=Math.floor(oe/1e3);if(Ne<60)return`${Ne}s`;const me=Math.floor(Ne/60),it=Ne%60;return`${me}m${String(it).padStart(2,"0")}s`}let ce=cr(()=>{const M={};for(const[ie,X]of Object.entries(n(s)))for(const oe of X)if(oe.timestamp){M[ie]=x(oe.timestamp);break}return M}),J=cr(()=>n(i)?n(ce)[n(i)]??null:null);function z(M){const ie=x(M);if(ie===null)return"";const X=new Date(ie),oe=String(X.getHours()).padStart(2,"0"),Ne=String(X.getMinutes()).padStart(2,"0"),me=String(X.getSeconds()).padStart(2,"0"),it=String(X.getMilliseconds()).padStart(3,"0");return`${oe}:${Ne}:${me}.${it}`}var qe={get url(){return r()},set url(M=""){r(M),Tr()}},ue=Lf();let pe;var st=$(ue);{var Je=M=>{var ie=$f(),X=$(ie);{var oe=me=>{var it=Ef();ar(),k(me,it)},Ne=me=>{var it=Hl("No logs available");k(me,it)};te(X,me=>{n(o)?me(oe):me(Ne,-1)})}y(ie),k(M,ie)},Et=M=>{var ie=Cf();k(M,ie)},Ut=M=>{var ie=If(),X=gt(ie),oe=$(X);kt(oe,21,()=>n(m),Zn,(ke,$e)=>{var $t=Sf();let Gr;var _=$($t),w=T(_),D=$(w,!0);y(w),y($t),B(()=>{var Z;Gr=he($t,1,"logs-tab svelte-qvn6bd",null,Gr,{active:n(i)===n($e)}),Y(_,`${n($e)??""} `),Y(D,((Z=n(s)[n($e)])==null?void 0:Z.length)||0)}),Tn("click",$t,()=>V(i,n($e),!0)),k(ke,$t)}),y(oe);var Ne=T(oe,2),me=$(Ne);{var it=ke=>{var $e=Df();k(ke,$e)};te(me,ke=>{n(o)&&!n(l)&&ke(it)})}var Ge=T(me,2);let mr;var _n=T(Ge,2),zs=$(_n);{var Hs=ke=>{var $e=Nf();k(ke,$e)},Gs=ke=>{var $e=Tf();k(ke,$e)};te(zs,ke=>{n(c)?ke(Hs):ke(Gs,-1)})}y(_n),y(Ne),y(X);var Hr=T(X,2);kt(Hr,21,()=>n(b),Zn,(ke,$e)=>{var $t=Mf();let Gr;var _=$($t);{var w=H=>{var re=Af(),L=$(re,!0);y(re),B((R,se)=>{jr(re,"title",R),Y(L,se)},[()=>z(n($e).timestamp),()=>A(n($e).timestamp,n(J))]),k(H,re)};te(_,H=>{n(a)&&H(w)})}var D=T(_,2),Z=$(D,!0);y(D),y($t),B(()=>{Gr=he($t,1,"logs-line svelte-qvn6bd",null,Gr,{stderr:n($e).channel==="stderr","status-line":n($e).channel==="status"}),Y(Z,n($e).line)}),k(ke,$t)}),y(Hr),_o(Hr,ke=>V(v,ke),()=>n(v));var ss=T(Hr,2);{var Vs=ke=>{var $e=Rf();Tn("click",$e,q),k(ke,$e)};te(ss,ke=>{n(f)||ke(Vs)})}B(()=>{mr=he(Ge,1,"logs-ctrl-btn svelte-qvn6bd",null,mr,{active:n(a)}),jr(_n,"title",n(c)?"Collapse":"Expand")}),Tn("click",Ge,()=>V(a,!n(a))),Tn("click",_n,()=>V(c,!n(c))),Nn("scroll",Hr,E),k(M,ie)};te(st,M=>{n(m).length===0&&!n(l)?M(Je):n(m).length===0&&n(l)?M(Et,1):M(Ut,-1)})}return y(ue),B(()=>pe=he(ue,1,"logs-root svelte-qvn6bd",null,pe,{expanded:n(c)})),k(e,ue),Vn(qe)}io(["click"]),customElements.define("release-logs",js(Of,{url:{}},[],[],{mode:"open"}));var Uf=N('
'),jf=N('
');const Ff={hash:"svelte-47dto6",code:`.spec-root.svelte-47dto6 {border:1px solid #e5e7eb;border-radius:0.5rem;overflow:hidden;font-family:system-ui, -apple-system, sans-serif;}.spec-root.expanded.svelte-47dto6 {max-height:36rem;overflow-y:auto;}.spec-header.svelte-47dto6 {display:flex;align-items:center;justify-content:space-between;width:100%;padding:0.5rem 0.75rem;background:#f9fafb;border:none;border-bottom:1px solid transparent;cursor:pointer;transition:background 0.15s;}.spec-root.expanded.svelte-47dto6 .spec-header:where(.svelte-47dto6) {position:sticky;top:0;z-index:1;border-bottom-color:#e5e7eb;}.spec-header.svelte-47dto6:hover {background:#f3f4f6;}.spec-header-left.svelte-47dto6 {display:flex;align-items:center;gap:0.375rem;}.spec-chevron.svelte-47dto6 {color:#6b7280;transition:transform 0.15s ease;flex-shrink:0;}.spec-chevron.rotated.svelte-47dto6 {transform:rotate(90deg);}.spec-filename.svelte-47dto6 {font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;font-weight:500;color:#374151;}.spec-meta.svelte-47dto6 {font-size:0.75rem;color:#9ca3af;}.spec-code.svelte-47dto6 {background:#111827;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar {width:0.5rem;height:0.5rem;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar-track {background:#1f2937;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar-thumb {background:#4b5563;border-radius:0.25rem;}.spec-code.svelte-47dto6 pre:where(.svelte-47dto6) {margin:0;padding:1rem;font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;line-height:1.625;color:#e5e7eb;white-space:pre;tab-size:4;overflow-x:auto;}.spec-code.svelte-47dto6 code:where(.svelte-47dto6) {color:inherit;} + }.logs-output.svelte-qvn6bd {max-height:60vh;overflow-y:auto;padding:0.25rem 0;}.logs-root.expanded.svelte-qvn6bd .logs-output:where(.svelte-qvn6bd) {max-height:85vh;}.logs-output.svelte-qvn6bd::-webkit-scrollbar {width:0.5rem;}.logs-output.svelte-qvn6bd::-webkit-scrollbar-track {background:#1f2937;}.logs-output.svelte-qvn6bd::-webkit-scrollbar-thumb {background:#4b5563;border-radius:0.25rem;}.logs-line.svelte-qvn6bd {display:flex;padding:0 1rem 0 0;gap:0;min-height:1.5rem;}.logs-line.svelte-qvn6bd:hover {background:rgba(255, 255, 255, 0.04);}.logs-line.stderr.svelte-qvn6bd {color:#fca5a5;background:rgba(239, 68, 68, 0.06);}.logs-line.stderr.svelte-qvn6bd:hover {background:rgba(239, 68, 68, 0.1);}.logs-line.status-line.svelte-qvn6bd {color:#93c5fd;font-weight:600;padding-top:0.375rem;padding-bottom:0.375rem;border-top:1px solid #1e3a5f;margin-top:0.25rem;}.logs-ts.svelte-qvn6bd {color:#4b5563;white-space:nowrap;user-select:none;flex-shrink:0;width:3.5rem;text-align:right;padding-right:1rem;padding-left:0.75rem;border-right:1px solid #1f2937;margin-right:0.75rem;}.logs-text.svelte-qvn6bd {white-space:pre-wrap;word-break:break-all;flex:1;min-width:0;padding-left:1rem;}.logs-line.svelte-qvn6bd .logs-ts:where(.svelte-qvn6bd) + .logs-text:where(.svelte-qvn6bd) {padding-left:0;}.logs-scroll-btn.svelte-qvn6bd {position:absolute;bottom:0.75rem;left:50%;transform:translateX(-50%);padding:0.25rem 0.75rem;font-size:0.6875rem;font-family:system-ui, -apple-system, sans-serif;color:#d1d5db;background:#374151;border:1px solid #4b5563;border-radius:9999px;cursor:pointer;opacity:0.9;transition:opacity 0.15s;}.logs-scroll-btn.svelte-qvn6bd:hover {opacity:1;background:#4b5563;}`};function nc(e,t){rs(t,!0),Qs(e,rc);let r=mr(t,"url",7,""),s=Ie(Or({})),o=Ie(null),l=Ie(!1),i=Ie(!1),a=Ie(!0),f=Ie(!0),c=Ie(!1),v=Ie(null),m=Vt(()=>Object.keys(n(s)).sort()),b=Vt(()=>n(o)&&n(s)[n(o)]?n(s)[n(o)]:[]);function S(){if(!r())return;const M=new EventSource(r());return U(l,!0),M.addEventListener("log",se=>{try{const J=JSON.parse(se.data),fe=J.destination||"unknown";n(s)[fe]||(n(s)[fe]=[],n(o)||U(o,fe,!0)),n(s)[fe]=[...n(s)[fe],{line:J.line,timestamp:J.timestamp,channel:J.channel||"stdout"}],n(a)&&requestAnimationFrame(()=>{n(v)&&(n(v).scrollTop=n(v).scrollHeight)})}catch(J){console.warn("[release-logs] bad log event:",J)}}),M.addEventListener("status",se=>{try{const J=JSON.parse(se.data),fe=J.destination||"unknown";n(s)[fe]||(n(s)[fe]=[],n(o)||U(o,fe,!0)),n(s)[fe]=[...n(s)[fe],{line:`── ${J.status} ──`,timestamp:"",channel:"status"}]}catch{}}),M.addEventListener("done",()=>{U(i,!0)}),M.addEventListener("error",()=>{U(l,!1),M.close()}),()=>{M.close(),U(l,!1)}}On(()=>{if(r())return S()});function $(){if(!n(v))return;const M=n(v).scrollHeight-n(v).scrollTop-n(v).clientHeight<40;U(a,M)}function B(){n(v)&&(n(v).scrollTop=n(v).scrollHeight,U(a,!0))}function x(M){if(!M)return null;const se=Number(M);if(Number.isFinite(se)&&se>1e12)return se;const J=new Date(M);return isNaN(J.getTime())?null:J.getTime()}function A(M,se){const J=x(M);if(J===null||se===null)return"";const fe=J-se;if(fe<0)return"0s";const Pe=Math.floor(fe/1e3);if(Pe<60)return`${Pe}s`;const qe=Math.floor(Pe/60),ot=Pe%60;return`${qe}m${String(ot).padStart(2,"0")}s`}let ae=Vt(()=>{const M={};for(const[se,J]of Object.entries(n(s)))for(const fe of J)if(fe.timestamp){M[se]=x(fe.timestamp);break}return M}),ee=Vt(()=>n(o)?n(ae)[n(o)]??null:null);function ne(M){const se=x(M);if(se===null)return"";const J=new Date(se),fe=String(J.getHours()).padStart(2,"0"),Pe=String(J.getMinutes()).padStart(2,"0"),qe=String(J.getSeconds()).padStart(2,"0"),ot=String(J.getMilliseconds()).padStart(3,"0");return`${fe}:${Pe}:${qe}.${ot}`}var Qe={get url(){return r()},set url(M=""){r(M),Ht()}},pe=tc();let K;var Ne=E(pe);{var Ke=M=>{var se=Gf(),J=E(se);{var fe=qe=>{var ot=Hf();dr(),w(qe,ot)},Pe=qe=>{var ot=sa("No logs available");w(qe,ot)};Q(J,qe=>{n(l)?qe(fe):qe(Pe,-1)})}y(se),w(M,se)},ht=M=>{var se=Vf();w(M,se)},Dt=M=>{var se=ec(),J=ft(se),fe=E(J);Ct(fe,21,()=>n(m),cs,(xe,Ee)=>{var Nt=Wf();let Kr;var Jr=E(Nt),$n=D(Jr),Xr=E($n,!0);y($n),y(Nt),j(()=>{var Zr;Kr=he(Nt,1,"logs-tab svelte-qvn6bd",null,Kr,{active:n(o)===n(Ee)}),V(Jr,`${n(Ee)??""} `),V(Xr,((Zr=n(s)[n(Ee)])==null?void 0:Zr.length)||0)}),Bn("click",Nt,()=>U(o,n(Ee),!0)),w(xe,Nt)}),y(fe);var Pe=D(fe,2),qe=E(Pe);{var ot=xe=>{var Ee=Yf();w(xe,Ee)};Q(qe,xe=>{n(l)&&!n(i)&&xe(ot)})}var Je=D(qe,2);let yr;var En=D(Je,2),eo=E(En);{var hs=xe=>{var Ee=Qf();w(xe,Ee)},Qr=xe=>{var Ee=Kf();w(xe,Ee)};Q(eo,xe=>{n(c)?xe(hs):xe(Qr,-1)})}y(En),y(Pe),y(J);var _t=D(J,2);Ct(_t,21,()=>n(b),cs,(xe,Ee)=>{var Nt=Xf();let Kr;var Jr=E(Nt);{var $n=Wn=>{var Cn=Jf(),ro=E(Cn,!0);y(Cn),j((no,so)=>{Hr(Cn,"title",no),V(ro,so)},[()=>ne(n(Ee).timestamp),()=>A(n(Ee).timestamp,n(ee))]),w(Wn,Cn)};Q(Jr,Wn=>{n(f)&&Wn($n)})}var Xr=D(Jr,2),Zr=E(Xr,!0);y(Xr),y(Nt),j(()=>{Kr=he(Nt,1,"logs-line svelte-qvn6bd",null,Kr,{stderr:n(Ee).channel==="stderr","status-line":n(Ee).channel==="status"}),V(Zr,n(Ee).line)}),w(xe,Nt)}),y(_t),Tl(_t,xe=>U(v,xe),()=>n(v));var to=D(_t,2);{var _s=xe=>{var Ee=Zf();Bn("click",Ee,B),w(xe,Ee)};Q(to,xe=>{n(a)||xe(_s)})}j(()=>{yr=he(Je,1,"logs-ctrl-btn svelte-qvn6bd",null,yr,{active:n(f)}),Hr(En,"title",n(c)?"Collapse":"Expand")}),Bn("click",Je,()=>U(f,!n(f))),Bn("click",En,()=>U(c,!n(c))),_r("scroll",_t,$),w(M,se)};Q(Ne,M=>{n(m).length===0&&!n(i)?M(Ke):n(m).length===0&&n(i)?M(ht,1):M(Dt,-1)})}return y(pe),j(()=>K=he(pe,1,"logs-root svelte-qvn6bd",null,K,{expanded:n(c)})),w(e,pe),ns(Qe)}bl(["click"]),customElements.define("release-logs",Ks(nc,{url:{}},[],[],{mode:"open"}));var sc=C('
'),oc=C('
');const lc={hash:"svelte-47dto6",code:`.spec-root.svelte-47dto6 {border:1px solid #e5e7eb;border-radius:0.5rem;overflow:hidden;font-family:system-ui, -apple-system, sans-serif;}.spec-root.expanded.svelte-47dto6 {max-height:36rem;overflow-y:auto;}.spec-header.svelte-47dto6 {display:flex;align-items:center;justify-content:space-between;width:100%;padding:0.5rem 0.75rem;background:#f9fafb;border:none;border-bottom:1px solid transparent;cursor:pointer;transition:background 0.15s;}.spec-root.expanded.svelte-47dto6 .spec-header:where(.svelte-47dto6) {position:sticky;top:0;z-index:1;border-bottom-color:#e5e7eb;}.spec-header.svelte-47dto6:hover {background:#f3f4f6;}.spec-header-left.svelte-47dto6 {display:flex;align-items:center;gap:0.375rem;}.spec-chevron.svelte-47dto6 {color:#6b7280;transition:transform 0.15s ease;flex-shrink:0;}.spec-chevron.rotated.svelte-47dto6 {transform:rotate(90deg);}.spec-filename.svelte-47dto6 {font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;font-weight:500;color:#374151;}.spec-meta.svelte-47dto6 {font-size:0.75rem;color:#9ca3af;}.spec-code.svelte-47dto6 {background:#111827;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar {width:0.5rem;height:0.5rem;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar-track {background:#1f2937;}.spec-root.expanded.svelte-47dto6::-webkit-scrollbar-thumb {background:#4b5563;border-radius:0.25rem;}.spec-code.svelte-47dto6 pre:where(.svelte-47dto6) {margin:0;padding:1rem;font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;font-size:0.8125rem;line-height:1.625;color:#e5e7eb;white-space:pre;tab-size:4;overflow-x:auto;}.spec-code.svelte-47dto6 code:where(.svelte-47dto6) {color:inherit;} - /* Syntax highlighting tokens */.spec-code.svelte-47dto6 .hl-comment {color:#6b7280;font-style:italic;}.spec-code.svelte-47dto6 .hl-string {color:#a5d6ff;}.spec-code.svelte-47dto6 .hl-keyword {color:#ff7b72;}.spec-code.svelte-47dto6 .hl-number {color:#79c0ff;}`};function Bf(e,t){Gn(t,!0),Us(e,Ff);let r=Rn(t,"content",7,""),s=Rn(t,"filename",7,"forest.cue"),i=Le(!1),o=Le("");function l(ue){let pe=ue.replace(/&/g,"&").replace(//g,">");return pe=pe.replace(/(\/\/.*)/g,'$1').replace(/"(?:[^"\\]|\\.)*"/g,'$&').replace(/\b(package|import|let|if|for|in|true|false|null|enabled|path)\b/g,'$1').replace(/\b(\d+)\b/g,'$1'),pe}Cn(()=>{n(i)&&r()&&!n(o)&&V(o,l(r()),!0)});function f(){V(i,!n(i))}let a=cr(()=>r()?r().split(` -`).length:0);var c={get content(){return r()},set content(ue=""){r(ue),Tr()},get filename(){return s()},set filename(ue="forest.cue"){s(ue),Tr()}},v=jf();let m;var b=$(v),C=$(b),E=$(C);let q;var x=T(E,2),A=$(x,!0);y(x),y(C);var ce=T(C,2),J=$(ce);y(ce),y(b);var z=T(b,2);{var qe=ue=>{var pe=Uf(),st=$(pe),Je=$(st),Et=$(Je);ta(Et,()=>n(o)),y(Je),y(st),y(pe),k(ue,pe)};te(z,ue=>{n(i)&&ue(qe)})}return y(v),B(()=>{m=he(v,1,"spec-root svelte-47dto6",null,m,{expanded:n(i)}),q=he(E,0,"spec-chevron svelte-47dto6",null,q,{rotated:n(i)}),Y(A,s()),Y(J,`${n(a)??""} lines`)}),Tn("click",b,f),k(e,v),Vn(c)}io(["click"]),customElements.define("spec-viewer",js(Bf,{content:{},filename:{}},[],[],{mode:"open"}))})(); + /* Syntax highlighting tokens */.spec-code.svelte-47dto6 .hl-comment {color:#6b7280;font-style:italic;}.spec-code.svelte-47dto6 .hl-string {color:#a5d6ff;}.spec-code.svelte-47dto6 .hl-keyword {color:#ff7b72;}.spec-code.svelte-47dto6 .hl-number {color:#79c0ff;}`};function ic(e,t){rs(t,!0),Qs(e,lc);let r=mr(t,"content",7,""),s=mr(t,"filename",7,"forest.cue"),o=Ie(!1),l=Ie("");function i(pe){let K=pe.replace(/&/g,"&").replace(//g,">");return K=K.replace(/(\/\/.*)/g,'$1').replace(/"(?:[^"\\]|\\.)*"/g,'$&').replace(/\b(package|import|let|if|for|in|true|false|null|enabled|path)\b/g,'$1').replace(/\b(\d+)\b/g,'$1'),K}On(()=>{n(o)&&r()&&!n(l)&&U(l,i(r()),!0)});function a(){U(o,!n(o))}let f=Vt(()=>r()?r().split(` +`).length:0);var c={get content(){return r()},set content(pe=""){r(pe),Ht()},get filename(){return s()},set filename(pe="forest.cue"){s(pe),Ht()}},v=oc();let m;var b=E(v),S=E(b),$=E(S);let B;var x=D($,2),A=E(x,!0);y(x),y(S);var ae=D(S,2),ee=E(ae);y(ae),y(b);var ne=D(b,2);{var Qe=pe=>{var K=sc(),Ne=E(K),Ke=E(Ne),ht=E(Ke);ha(ht,()=>n(l)),y(Ke),y(Ne),y(K),w(pe,K)};Q(ne,pe=>{n(o)&&pe(Qe)})}return y(v),j(()=>{m=he(v,1,"spec-root svelte-47dto6",null,m,{expanded:n(o)}),B=he($,0,"spec-chevron svelte-47dto6",null,B,{rotated:n(o)}),V(A,s()),V(ee,`${n(f)??""} lines`)}),Bn("click",b,a),w(e,v),ns(c)}bl(["click"]),customElements.define("spec-viewer",Ks(ic,{content:{},filename:{}},[],[],{mode:"open"}))})(); diff --git a/tasks/approval-gate.md b/tasks/approval-gate.md new file mode 100644 index 0000000..a9710a5 --- /dev/null +++ b/tasks/approval-gate.md @@ -0,0 +1,171 @@ +# Approval Gate — Implementation Log + +## Overview + +New policy type `POLICY_TYPE_EXTERNAL_APPROVAL` that requires human approval before a release can deploy to a target environment. + +**Rules:** +- Scoped to a single release intent + target environment +- Release author cannot approve (unless admin → red "Bypass" button) +- All org members can approve +- Rejection is a vote, not a permanent block +- No timer retry — NATS signal on decision triggers re-evaluation + +--- + +## Forage (client) Changes — DONE + +### Proto + +**New file:** `interface/proto/forest/v1/policies.proto` +- `POLICY_TYPE_EXTERNAL_APPROVAL = 3` added to `PolicyType` enum +- `ApprovalConfig { target_environment, required_approvals }` message +- `ApprovalState { required_approvals, current_approvals, decisions }` message +- `ApprovalDecisionEntry { user_id, username, decision, decided_at, comment }` message +- `PolicyEvaluation` extended with `optional ApprovalState approval_state = 10` +- `EvaluatePoliciesRequest` extended with `optional string release_intent_id = 4` +- `Policy`, `CreatePolicyRequest`, `UpdatePolicyRequest` oneofs extended with `ApprovalConfig approval = 12` +- New RPCs: `ApproveRelease`, `RejectRelease`, `GetApprovalState` with request/response messages + +**New file:** `scripts/sync-protos.sh` +- Copies all `.proto` files from forest repo to forage, runs `buf generate` + +**Regenerated:** `crates/forage-grpc/src/grpc/forest/v1/forest.v1.rs` and `forest.v1.tonic.rs` + +### Domain Model + +**File:** `crates/forage-core/src/platform/mod.rs` +- `PolicyConfig::Approval { target_environment, required_approvals }` variant +- `ApprovalState` struct +- `ApprovalDecisionEntry` struct +- `PolicyEvaluation.approval_state: Option` field +- New `ForestPlatform` trait methods: `evaluate_policies`, `approve_release`, `reject_release`, `get_approval_state` + +### gRPC Client + +**File:** `crates/forage-server/src/forest_client.rs` +- `convert_policy`: handles `policy::Config::Approval` → `PolicyConfig::Approval` +- `policy_config_to_grpc`: handles `PolicyConfig::Approval` → gRPC +- `convert_policy_evaluation`: maps policy type 3 → "approval", maps `approval_state` +- `convert_approval_state`: maps gRPC `ApprovalState` → domain +- `evaluate_policies` impl: calls `PolicyServiceClient::evaluate_policies` with `release_intent_id` +- `approve_release` impl: calls `PolicyServiceClient::approve_release` +- `reject_release` impl: calls `PolicyServiceClient::reject_release` +- `get_approval_state` impl: calls `PolicyServiceClient::get_approval_state` +- Fixed `PipelineStage::Plan` match arm (new variant from forest proto sync) +- Fixed `ReleaseRequest` missing `prepare_only` field (new field from forest proto sync) + +### Test Support + +**File:** `crates/forage-server/src/test_support.rs` +- `MockPlatformClient`: default impls for `evaluate_policies`, `approve_release`, `reject_release`, `get_approval_state` + +### Routes + +**File:** `crates/forage-server/src/routes/platform.rs` + +**New routes:** +- `POST /orgs/{org}/projects/{project}/releases/{slug}/approve` → `approve_release_submit` +- `POST /orgs/{org}/projects/{project}/releases/{slug}/reject` → `reject_release_submit` + +**New handler structs:** +- `ApprovalForm { csrf_token, release_intent_id, target_environment, comment, force_bypass }` +- `CreatePolicyForm` extended with `required_approvals: Option` + +**Modified handlers:** +- `create_policy_submit`: handles `policy_type = "approval"` with validation +- `policies_page`: maps `PolicyConfig::Approval` to template context +- `edit_policy_page`: maps `PolicyConfig::Approval` to template context +- `artifact_detail`: fetches policy evaluations per environment, passes `policy_evaluations`, `release_intent_id`, `is_release_author`, `is_admin` to template + +### Templates + +**File:** `templates/pages/policies.html.jinja` +- Policy list: "Approval Required" badge with target env + approval count +- Create form: "Approval Required" option in type dropdown +- Approval fields: target environment select + required approvals number input +- JavaScript: toggles visibility of soak/branch/approval field sets + +**File:** `templates/pages/artifact_detail.html.jinja` +- New "Policy Requirements" section between Pipeline and Destinations +- Shows all policy evaluations (soak, branch, approval) with pass/fail icons +- Approval UI: + - Approval count badge (current/required) + - Decision history (username, approved/rejected, comment) + - **Approve** button (green) — shown to non-authors + - **Bypass (Admin)** button (red) — shown to admin authors with confirmation dialog + - **Reject** button (red outline) — shown to all eligible members + - "You cannot approve your own release" message for non-admin authors + +--- + +## Forest (core) Changes — NEEDS MANUAL APPLICATION + +### Proto +**File:** `interface/proto/forest/v1/policies.proto` — same changes as forage copy above + +### DB Migration +**New file:** `crates/forest-server/migrations/20260315000001_approval_decisions.sql` +```sql +CREATE TABLE approval_decisions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + release_intent_id UUID NOT NULL REFERENCES release_intents(id) ON DELETE CASCADE, + policy_id UUID NOT NULL REFERENCES policies(id) ON DELETE CASCADE, + target_environment TEXT NOT NULL, + user_id UUID NOT NULL, + username TEXT NOT NULL, + decision TEXT NOT NULL CHECK (decision IN ('approved', 'rejected')), + comment TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +-- unique per user per intent per env, lookup index for counting +``` + +### Policy Engine +**File:** `crates/forest-server/src/services/policy.rs` +- `PolicyType::Approval`, `ApprovalConfig` struct +- `PolicyConfig::Approval(ApprovalConfig)` variant +- `ApprovalStateInfo`, `ApprovalDecisionInfo` structs +- `PolicyEvaluation.approval_state: Option` +- `evaluate_for_environment` gains `release_intent_id: Option<&Uuid>` param +- `check_approval`: queries approval_decisions, compares count vs required +- `record_approval_decision`: upserts into approval_decisions +- `get_intent_actor_id`: queries release_intents.actor_id +- `find_approval_policy_for_environment`: finds enabled approval policy for target env +- `get_approval_state`: returns current approval state for display + +### Intent Coordinator +**File:** `crates/forest-server/src/intent_coordinator.rs` +- `check_approval_policies` called after `check_soak_time_policies` for deploy stages +- If blocked: logs and continues (no timer retry, NATS-triggered re-eval on decision) + +### Release Event Store +**File:** `crates/forest-server/src/services/release_event_store.rs` +- `check_approval_policies(tx, project_id, release_intent_id, target_environment) -> Option` +- Loads enabled approval policies, counts approved decisions, blocks if insufficient + +### gRPC Handlers +**File:** `crates/forest-server/src/grpc/policies.rs` +- `record_to_grpc`: handles `PolicyConfig::Approval` +- `eval_to_grpc`: handles `PolicyType::Approval`, maps `approval_state` +- `extract_config` / `extract_update_config`: handles approval config +- `evaluate_policies`: passes `release_intent_id` through +- `approve_release`: validates actor != intent author (unless force_bypass), records decision, publishes NATS +- `reject_release`: records rejection decision +- `get_approval_state`: returns current approval state + +### Caller Updates +- `src/grpc/release.rs`: `evaluate_for_environment` calls gain `None` as 4th arg +- `src/scheduler.rs`: same + +--- + +## Verification +- Forage: **169 tests passing**, compiles clean (0 errors, 0 warnings) +- Forest: tool permissions blocked writes — all code is ready, needs to be applied from forest repo context + +## Next Steps +1. Apply forest changes (run claude from the forest directory, or grant write access) +2. Run `buf generate` in forest to regenerate gRPC interface stubs +3. Run forest tests +4. E2E test: create approval policy, trigger release, verify UI shows approval buttons diff --git a/templates/base.html.jinja b/templates/base.html.jinja index 6b08c7e..60d9efd 100644 --- a/templates/base.html.jinja +++ b/templates/base.html.jinja @@ -5,6 +5,7 @@ {{ title }} + diff --git a/templates/pages/artifact_detail.html.jinja b/templates/pages/artifact_detail.html.jinja index c9578a0..00cd8d6 100644 --- a/templates/pages/artifact_detail.html.jinja +++ b/templates/pages/artifact_detail.html.jinja @@ -144,6 +144,97 @@
{% endif %} + {# ── Policy evaluations (approval, soak, branch) ──────────── #} + {% if policy_evaluations | length > 0 %} +
+

Policy Requirements

+
+ {% for eval in policy_evaluations %} +
+
+ {% if eval.passed %} + + {% else %} + + {% endif %} + + {% if eval.policy_type == "approval" %} + Approval + {% elif eval.policy_type == "soak_time" %} + Soak Time + {% elif eval.policy_type == "branch_restriction" %} + Branch + {% endif %} + + {{ eval.policy_name }} + {{ eval.reason }} +
+ + {# ── Approval UI ──────────────────────────────── #} + {% if eval.policy_type == "approval" and eval.approval_state %} +
+
+ {{ eval.approval_state.current_approvals }}/{{ eval.approval_state.required_approvals }} approvals +
+ + {% if eval.approval_state.decisions | length > 0 %} +
+ {% for d in eval.approval_state.decisions %} +
+ {% if d.decision == "approved" %} + + {{ d.username }} + approved + {% else %} + + {{ d.username }} + rejected + {% endif %} + {% if d.comment %}— {{ d.comment }}{% endif %} +
+ {% endfor %} +
+ {% endif %} + + {% if not eval.passed %} + {% if is_release_author and not is_admin %} +

You cannot approve your own release.

+ {% else %} +
+ {% if not is_release_author %} +
+ + + + +
+ {% endif %} + {% if is_release_author and is_admin %} +
+ + + + + +
+ {% endif %} +
+ + + + +
+
+ {% endif %} + {% endif %} +
+ {% endif %} +
+ {% endfor %} +
+
+ {% endif %} + {# ── Destinations with status ──────────────────────────────── #} {% if destinations | length > 0 or configured_destinations | length > 0 %}
diff --git a/templates/pages/policies.html.jinja b/templates/pages/policies.html.jinja index 31e7fe8..b40c76f 100644 --- a/templates/pages/policies.html.jinja +++ b/templates/pages/policies.html.jinja @@ -7,7 +7,7 @@

Deployment Policies

{{ current_project }} - · Gate deployments with soak times and branch restrictions + · Gate deployments with soak times, branch restrictions, and approvals

@@ -39,6 +39,11 @@ {{ policy.config.branch_pattern }} {{ policy.config.target_environment }} + {% elif policy.policy_type == "approval" %} + Approval Required + {{ policy.config.target_environment }} + · + {{ policy.config.required_approvals }} approval{{ 's' if policy.config.required_approvals != 1 }} {% endif %} @@ -68,7 +73,7 @@

No deployment policies configured.

{% if is_admin %} -

Create one below to gate deployments with soak times or branch restrictions.

+

Create one below to gate deployments with soak times, branch restrictions, or approvals.

{% endif %}
{% endif %} @@ -91,6 +96,7 @@ class="border border-gray-300 rounded-md px-3 py-1.5 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-gray-900"> +

Require an artifact to succeed in a source environment for a duration before deploying to target. @@ -144,6 +150,26 @@ + {# Approval fields #} +

+ @@ -153,21 +179,33 @@ {% endif %} diff --git a/templates/pages/project_detail.html.jinja b/templates/pages/project_detail.html.jinja index 3b2b1f7..0fd229b 100644 --- a/templates/pages/project_detail.html.jinja +++ b/templates/pages/project_detail.html.jinja @@ -20,7 +20,7 @@ - + {% endblock %}