From 7eb6ae7cbb847fa9f89cc18b0abd4a30d2eb3b93 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 15 Mar 2026 19:46:33 +0100 Subject: [PATCH] feat: add approval step Signed-off-by: kjuulh --- .../console-2026-03-15T17-34-15-943Z.log | 29 + .../console-2026-03-15T17-40-47-271Z.log | 1 + .../console-2026-03-15T17-40-57-477Z.log | 70 + .../console-2026-03-15T18-28-02-722Z.log | 7 + .../console-2026-03-15T18-30-49-631Z.log | 6 + .../console-2026-03-15T18-31-57-555Z.log | 1 + .../console-2026-03-15T18-32-21-150Z.log | 1 + CLAUDE.md | 1 + crates/forage-core/src/platform/mod.rs | 61 + .../src/grpc/forest/v1/forest.v1.rs | 2569 ++++++++--- .../src/grpc/forest/v1/forest.v1.tonic.rs | 3835 ++++++++++++----- crates/forage-server/src/auth.rs | 14 +- crates/forage-server/src/forest_client.rs | 204 +- crates/forage-server/src/routes/platform.rs | 308 +- crates/forage-server/src/test_support.rs | 59 + dashboard-after-fix.png | Bin 0 -> 39506 bytes frontend/src/ReleaseTimeline.svelte | 94 + frontend/src/lib/status.js | 3 + interface/proto/forest/v1/apps.proto | 109 + interface/proto/forest/v1/artifacts.proto | 62 + interface/proto/forest/v1/events.proto | 119 + interface/proto/forest/v1/forage.proto | 864 ++++ interface/proto/forest/v1/health.proto | 10 + interface/proto/forest/v1/notifications.proto | 98 + interface/proto/forest/v1/policies.proto | 178 + interface/proto/forest/v1/registry.proto | 80 + .../proto/forest/v1/release_pipelines.proto | 8 + interface/proto/forest/v1/releases.proto | 170 + interface/proto/forest/v1/runner.proto | 212 + interface/proto/forest/v1/triggers.proto | 79 + policies-with-approval.png | Bin 0 -> 55883 bytes project-overview-hidden.png | Bin 0 -> 35780 bytes project-overview.png | Bin 0 -> 51260 bytes scripts/sync-protos.sh | 19 + static/css/style.css | 2 +- static/js/components/forage-components.js | 14 +- tasks/approval-gate.md | 171 + templates/base.html.jinja | 1 + templates/pages/artifact_detail.html.jinja | 91 + templates/pages/policies.html.jinja | 58 +- templates/pages/project_detail.html.jinja | 2 +- 41 files changed, 7886 insertions(+), 1724 deletions(-) create mode 100644 .playwright-mcp/console-2026-03-15T17-34-15-943Z.log create mode 100644 .playwright-mcp/console-2026-03-15T17-40-47-271Z.log create mode 100644 .playwright-mcp/console-2026-03-15T17-40-57-477Z.log create mode 100644 .playwright-mcp/console-2026-03-15T18-28-02-722Z.log create mode 100644 .playwright-mcp/console-2026-03-15T18-30-49-631Z.log create mode 100644 .playwright-mcp/console-2026-03-15T18-31-57-555Z.log create mode 100644 .playwright-mcp/console-2026-03-15T18-32-21-150Z.log create mode 100644 dashboard-after-fix.png create mode 100644 interface/proto/forest/v1/apps.proto create mode 100644 interface/proto/forest/v1/artifacts.proto create mode 100644 interface/proto/forest/v1/events.proto create mode 100644 interface/proto/forest/v1/forage.proto create mode 100644 interface/proto/forest/v1/health.proto create mode 100644 interface/proto/forest/v1/notifications.proto create mode 100644 interface/proto/forest/v1/policies.proto create mode 100644 interface/proto/forest/v1/registry.proto create mode 100644 interface/proto/forest/v1/runner.proto create mode 100644 interface/proto/forest/v1/triggers.proto create mode 100644 policies-with-approval.png create mode 100644 project-overview-hidden.png create mode 100644 project-overview.png create mode 100755 scripts/sync-protos.sh create mode 100644 tasks/approval-gate.md 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 0000000000000000000000000000000000000000..750eb2a2264d7f5993833d6c8f8f51f6f8f62255 GIT binary patch literal 39506 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 ""); + 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}
{% 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 %}
@@ -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 0000000000000000000000000000000000000000..894645810caa2eecefd72c215ec5ee7478836150 GIT binary patch literal 55883 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 NLPS=$SV+g`zW~F8S*`#8 literal 0 HcmV?d00001 diff --git a/project-overview.png b/project-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..cc4fd5b0e3f63e2ca8186b1bb584c70b9a7668e9 GIT binary patch literal 51260 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

6AtmDnLeo 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 literal 0 HcmV?d00001 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 @@