From d74db2096c219a27eef6f2ecf0bcd37d782b7447 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 5 Oct 2026 20:44:57 +0000 Subject: [PATCH 1/8] Project website + use Apalis crate for task queue (with redis backend) instead channel and semaphore queue + upgrade to rust 1.99 --- .devcontainer/Dockerfile | 4 +- .woodpecker/release.yml | 3 +- .woodpecker/tests.yml | 6 +- Cargo.lock | 293 +++++++- Cargo.toml | 3 + README.md | 6 +- crates/steep-server/Cargo.toml | 7 +- crates/steep-server/src/api.rs | 22 +- crates/steep-server/src/bot.rs | 208 ++---- crates/steep-server/src/dedup.rs | 120 ++++ crates/steep-server/src/env.rs | 3 + crates/steep-server/src/errors.rs | 11 +- crates/steep-server/src/gitea.rs | 12 +- crates/steep-server/src/main.rs | 17 +- crates/steep-server/src/metrics.rs | 17 +- crates/steep-server/src/state.rs | 9 +- logo.png | Bin 0 -> 152163 bytes public/Caddyfile | 5 + public/Containerfile | 4 + .../banniere_readme.png | Bin public/favicon.png | Bin 0 -> 210651 bytes public/index.html | 644 ++++++++++++++++++ scripts/build-website.sh | 23 + 23 files changed, 1234 insertions(+), 183 deletions(-) create mode 100644 crates/steep-server/src/dedup.rs create mode 100644 logo.png create mode 100644 public/Caddyfile create mode 100644 public/Containerfile rename banniere_readme.png => public/banniere_readme.png (100%) create mode 100644 public/favicon.png create mode 100644 public/index.html create mode 100755 scripts/build-website.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d28c98e..44eff67 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.98-trixie +FROM rust:1.99-trixie ARG USERNAME=dev ARG USER_UID=1000 @@ -15,6 +15,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ cmake \ pkg-config \ clang \ + valkey-server \ + valkey-tools \ && rm -rf /var/lib/apt/lists/* RUN groupadd --gid ${USER_GID:-1000} $USERNAME \ diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml index 812b4d8..4dcbf08 100644 --- a/.woodpecker/release.yml +++ b/.woodpecker/release.yml @@ -10,8 +10,9 @@ steps: - /data/woodpecker-builds:/data commands: - echo $DOCKER_PASSWORD | buildah login docker.io -u $DOCKER_USERNAME --password-stdin - - chmod +x scripts/build.sh + - chmod +x scripts/build.sh scripts/build-website.sh - bash scripts/build.sh + - bash scripts/build-website.sh environment: DOCKER_USERNAME: from_secret: docker_username diff --git a/.woodpecker/tests.yml b/.woodpecker/tests.yml index ec1e054..fa79918 100644 --- a/.woodpecker/tests.yml +++ b/.woodpecker/tests.yml @@ -4,19 +4,19 @@ when: steps: - name: fmt - image: rust:1.98 + image: rust:1.99 commands: - rustup component add rustfmt - cargo fmt --all -- --check - name: clippy - image: rust:1.98 + image: rust:1.99 commands: - rustup component add clippy - cargo clippy --workspace --all-targets --all-features -- -D warnings - name: test - image: rust:1.98 + image: rust:1.99 commands: - cargo test --workspace --all-targets diff --git a/Cargo.lock b/Cargo.lock index cfc72d7..fe89039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,12 +189,75 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "apalis" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504d52557a16b7b202941660f339c9182910d498cac40f93d15f6b31e6bef290" +dependencies = [ + "apalis-core", + "futures", + "pin-project-lite", + "serde", + "thiserror", + "tower", + "tracing", + "tracing-futures", +] + +[[package]] +name = "apalis-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3948d2051795c769e6a44a46549879af39d5e8e4fb113e0011cf99f7cd21b657" +dependencies = [ + "futures", + "futures-timer", + "pin-project-lite", + "serde", + "serde_json", + "thiserror", + "tower", + "ulid", +] + +[[package]] +name = "apalis-redis" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871367e3dfff2accda824ccc6e4f46f8f155c865065a450c466a7f2835541d55" +dependencies = [ + "apalis-core", + "chrono", + "futures", + "log", + "redis", + "serde", + "thiserror", + "tokio", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "axum" version = "0.8.9" @@ -247,6 +310,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -381,6 +453,32 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -621,6 +719,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.34" @@ -637,6 +750,17 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.34" @@ -666,12 +790,19 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -929,6 +1060,30 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.3.0" @@ -1281,12 +1436,40 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "objc2" version = "0.6.4" @@ -1715,6 +1898,31 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redis" +version = "0.32.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" +dependencies = [ + "arc-swap", + "backon", + "bytes", + "cfg-if", + "combine", + "futures-channel", + "futures-util", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2", + "tokio", + "tokio-util", + "url", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2149,6 +2357,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2210,9 +2424,11 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "steep-server" -version = "1.2.0" +version = "1.3.0" dependencies = [ "anyhow", + "apalis", + "apalis-redis", "axum", "devcontainer-rs", "dotenvy", @@ -2220,6 +2436,7 @@ dependencies = [ "hex", "metrics", "metrics-exporter-prometheus", + "redis", "reqwest", "ring", "sentry", @@ -2445,6 +2662,7 @@ dependencies = [ "pin-project-lite", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -2530,6 +2748,15 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "tracing", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -2565,6 +2792,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand", + "web-time", +] + [[package]] name = "uname" version = "0.1.1" @@ -2777,6 +3014,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-root-certs" version = "1.0.9" @@ -2808,6 +3055,41 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -2823,6 +3105,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index aeaa9f3..ee4b5e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ hex = "0.4" bytes = "1.1" metrics = "0.24" metrics-exporter-prometheus = { version = "0.18", default-features = false, features = ["http-listener"] } +apalis = { version = "0.7", features = ["catch-panic", "limit"] } +apalis-redis = "0.7" +redis = { version = "0.32", features = ["tokio-comp", "connection-manager"] } [profile.release] debug = 1 diff --git a/README.md b/README.md index 2a00232..7a35eaf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Steep — Let your code steep](banniere_readme.png) +![Steep — Let your code steep](public/banniere_readme.png) # Steep @@ -38,6 +38,7 @@ Steep reads its configuration from environment variables (a `.env` file is suppo | `GITEA_URL` | Base URL of your Gitea instance | | `GITEA_TOKEN` | Gitea API token | | `GITEA_TIMEOUT` | Gitea API request timeout in seconds | +| `REDIS_URL` | Redis connection URL used for the durable review queue (e.g. `redis://127.0.0.1:6379`) | | `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. | | `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking | | `RUST_LOG` | *(optional)* Log level, defaults to `info` | @@ -128,8 +129,9 @@ Set `METRICS_BIND_ADDR` (e.g. `0.0.0.0:9100`) to enable it. The metrics are then |---|---|---| | `steep_webhooks_received_total` | counter | Total webhooks received (label: `event_type`) | | `steep_webhooks_duplicate_total` | counter | Webhooks rejected as duplicates (label: `event_type`) | -| `steep_webhooks_channel_full_total` | counter | Webhooks dropped because the bot channel was full (label: `event_type`) | +| `steep_webhooks_enqueue_failed_total` | counter | Webhooks dropped because enqueueing to Redis failed (label: `event_type`) | | `steep_bot_tasks_active` | gauge | Bot tasks currently in progress | +| `steep_bot_tasks_skipped_total` | counter | Reviews skipped because they were already done (label: `event_type`) | | `steep_bot_tasks_completed_total` | counter | Bot tasks completed successfully (label: `event_type`) | | `steep_bot_tasks_failed_total` | counter | Bot tasks that failed (label: `event_type`) | | `steep_openrouter_cost_cents_total` | counter | Total OpenRouter cost in cents (divide by 100 for USD) | diff --git a/crates/steep-server/Cargo.toml b/crates/steep-server/Cargo.toml index 4165ef4..b6a466a 100644 --- a/crates/steep-server/Cargo.toml +++ b/crates/steep-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "steep-server" -version = "1.2.0" +version = "1.3.0" edition = "2024" [dependencies] @@ -25,4 +25,7 @@ hex = { workspace = true } metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } devcontainer-rs = { path = "../devcontainer-rs" } -tempfile = "3" \ No newline at end of file +tempfile = "3" +apalis = { workspace = true } +apalis-redis = { workspace = true } +redis = { workspace = true } diff --git a/crates/steep-server/src/api.rs b/crates/steep-server/src/api.rs index 64ca4f2..844c4ba 100644 --- a/crates/steep-server/src/api.rs +++ b/crates/steep-server/src/api.rs @@ -14,7 +14,10 @@ use tracing::{info, instrument}; use tokio_util::sync::CancellationToken; +use apalis::prelude::Storage; + use crate::consts::{GITEA_EVENT_TYPE_HEADER_NAME, GITEA_SIG_HEADER_NAME, MAX_WEBHOOK_BODY_SIZE}; +use crate::dedup::{Dedup, WEBHOOK_TTL}; use crate::errors::AppError; use crate::gitea::WebhookType; use crate::metrics; @@ -61,15 +64,22 @@ async fn webhook( metrics::webhook_received(event_type); let event_id = wb.event_id(); - if app_state.bot.check_and_mark(event_id).await { + let webhook_key = Dedup::webhook_key(event_id); + if !app_state + .dedup + .claim(&webhook_key, WEBHOOK_TTL) + .await + .map_err(anyhow::Error::from)? + { metrics::webhook_duplicate(event_type); return Err(AppError::AlreadyProcessedErr); } - if app_state.bot_tx.try_send(wb).is_err() { - app_state.bot.unmark(event_id).await; - metrics::webhook_channel_full(event_type); - return Err(AppError::ChannelFullErr); + let mut storage = app_state.storage.clone(); + if let Err(err) = storage.push(wb).await { + app_state.dedup.release(&webhook_key).await.ok(); + metrics::webhook_enqueue_failed(event_type); + return Err(AppError::EnqueueErr(anyhow::Error::from(err))); } Ok((StatusCode::CREATED, "Task started")) @@ -109,7 +119,7 @@ where }); }); - let webhook = parse_webhook(&type_header, &app_state.bot.name(), &body_bytes)?; + let webhook = parse_webhook(&type_header, &app_state.bot_name, &body_bytes)?; Ok(WebhookExtract(webhook)) } } diff --git a/crates/steep-server/src/bot.rs b/crates/steep-server/src/bot.rs index 596e1c8..fdb8c1c 100644 --- a/crates/steep-server/src/bot.rs +++ b/crates/steep-server/src/bot.rs @@ -1,12 +1,13 @@ use crate::{ + dedup::{Dedup, REVIEW_TTL}, gitea::{GiteaAPI, WebhookType}, metrics, open_router::OpenRouterClient, sandbox::SandboxConfig, }; +use apalis::prelude::{Data, Error, Monitor, WorkerBuilder, WorkerBuilderExt, WorkerFactoryFn}; +use apalis_redis::RedisStorage; use serde::{Deserialize, Deserializer}; -use std::{collections::HashSet, sync::Arc}; -use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; use tracing::{error, info, instrument}; @@ -120,87 +121,64 @@ where #[derive(Clone)] pub struct Bot { - bot_name: String, gitea_api: GiteaAPI, open_router_client: OpenRouterClient, max_concurrent: usize, open_router_model: String, sandbox: SandboxConfig, - actions_handled: Arc>>, + dedup: Dedup, } impl Bot { pub fn new( - bot_name: String, gitea_api: GiteaAPI, open_router_client: OpenRouterClient, max_concurrent: usize, open_router_model: String, sandbox: SandboxConfig, + dedup: Dedup, ) -> Self { Self { - bot_name, gitea_api, open_router_client, max_concurrent, open_router_model, sandbox, - actions_handled: Arc::new(Mutex::new(HashSet::new())), + dedup, } } - pub fn name(&self) -> String { - self.bot_name.clone() - } - + /// Consomme la file Redis jusqu'à l'annulation de `shutdown`. + /// + /// Les jobs en cours sont laissés se terminer, et ceux qui n'ont pas encore + /// été acquittés restent dans Redis : ils seront repris au prochain démarrage. pub async fn start( &self, - mut rx: tokio::sync::mpsc::Receiver, + storage: RedisStorage, shutdown: CancellationToken, ) -> anyhow::Result<()> { info!("Bot started"); - let sem = Arc::new(tokio::sync::Semaphore::new(self.max_concurrent)); - let mut tasks = tokio::task::JoinSet::new(); + let worker = WorkerBuilder::new("steep-review") + .concurrency(self.max_concurrent) + .data(self.clone()) + .backend(storage) + .build_fn(handle_review); - loop { - let wb = tokio::select! { - biased; - _ = shutdown.cancelled() => break, - msg = rx.recv() => match msg { - Some(wb) => wb, - None => break, - }, - }; - - while let Some(res) = tasks.try_join_next() { - if let Err(e) = res { - error!("Task panicked: {e}"); - } - } - - info!(queued = rx.len(), active = tasks.len(), "Webhook received"); - let permit = sem.clone().acquire_owned().await?; - let self_clone = self.clone(); - metrics::increment_task_active(); - - tasks.spawn(async move { - self_clone.exec(wb).await; - drop(permit); - metrics::decrement_task_active(); - }); - } - - tasks.join_all().await; + Monitor::new() + .register(worker) + .run_with_signal(async move { + shutdown.cancelled().await; + Ok(()) + }) + .await?; info!("Bot shutting down complete"); Ok(()) } #[instrument(skip(self, webhook), fields(repo, pr))] - pub async fn exec(&self, webhook: WebhookType) { - let event_type_str = webhook.event_type_str(); - + pub async fn exec(&self, webhook: WebhookType) -> anyhow::Result<()> { match &webhook { WebhookType::Review(p) => { tracing::Span::current().record("repo", &p.repository.full_name); @@ -210,111 +188,55 @@ impl Bot { let tools = crate::sandbox::tools::for_webhook(&webhook); - let exec_result = match webhook { - WebhookType::Review(review_payload) => crate::bot_actions::review::exec_review( - &self.gitea_api, - &self.open_router_client, - &self.open_router_model, - &self.sandbox, - tools, - review_payload, - ), - } - .await; - - match exec_result { - Ok(_) => { - metrics::task_completed(event_type_str); - info!("Task completed"); - } - Err(err) => { - metrics::task_failed(event_type_str); - error!(%err, "Task error"); - sentry_anyhow::capture_anyhow(&err); + match webhook { + WebhookType::Review(review_payload) => { + crate::bot_actions::review::exec_review( + &self.gitea_api, + &self.open_router_client, + &self.open_router_model, + &self.sandbox, + tools, + review_payload, + ) + .await } } } - - pub async fn check_and_mark(&self, event_id: u64) -> bool { - let mut action_handled_lock = self.actions_handled.lock().await; - - !action_handled_lock.insert(event_id) - } - - pub async fn unmark(&self, event_id: u64) { - let mut action_handled_lock = self.actions_handled.lock().await; - - action_handled_lock.remove(&event_id); - } } -#[cfg(test)] -mod tests { - use super::*; +async fn handle_review(webhook: WebhookType, bot: Data) -> Result<(), Error> { + let event_type = webhook.event_type_str(); + let review_key = Dedup::review_key(webhook.event_id()); - fn make_actions_handled() -> Arc>> { - Arc::new(Mutex::new(HashSet::new())) + match bot.dedup.is_marked(&review_key).await { + Ok(true) => { + metrics::task_skipped(event_type); + info!("Review already done, skipping"); + return Ok(()); + } + Ok(false) => {} + Err(err) => error!(%err, "Dedup lookup failed, reviewing anyway"), } - async fn check_and_mark(actions_handled: &Arc>>, event_id: u64) -> bool { - let mut lock = actions_handled.lock().await; - !lock.insert(event_id) + metrics::increment_task_active(); + let result = bot.exec(webhook).await; + metrics::decrement_task_active(); + + match result { + Ok(()) => { + metrics::task_completed(event_type); + info!("Task completed"); + + if let Err(err) = bot.dedup.mark(&review_key, REVIEW_TTL).await { + error!(%err, "Failed to mark review as done"); + } + } + Err(err) => { + metrics::task_failed(event_type); + error!(%err, "Task error"); + sentry_anyhow::capture_anyhow(&err); + } } - async fn unmark(actions_handled: &Arc>>, event_id: u64) { - let mut lock = actions_handled.lock().await; - lock.remove(&event_id); - } - - #[tokio::test] - async fn test_check_and_mark_first_call_returns_false() { - let actions_handled = make_actions_handled(); - assert!(!check_and_mark(&actions_handled, 1).await); - } - - #[tokio::test] - async fn test_check_and_mark_second_call_returns_true() { - let actions_handled = make_actions_handled(); - check_and_mark(&actions_handled, 1).await; - assert!(check_and_mark(&actions_handled, 1).await); - } - - #[tokio::test] - async fn test_check_and_mark_different_ids_are_independent() { - let actions_handled = make_actions_handled(); - check_and_mark(&actions_handled, 1).await; - assert!(!check_and_mark(&actions_handled, 2).await); - } - - #[tokio::test] - async fn test_unmark_allows_reprocessing() { - let actions_handled = make_actions_handled(); - check_and_mark(&actions_handled, 1).await; - unmark(&actions_handled, 1).await; - assert!(!check_and_mark(&actions_handled, 1).await); - } - - #[tokio::test] - async fn test_unmark_nonexistent_id_is_noop() { - let actions_handled = make_actions_handled(); - unmark(&actions_handled, 99).await; - assert!(!check_and_mark(&actions_handled, 99).await); - } - - #[tokio::test] - async fn test_check_and_mark_concurrent_same_id() { - let actions_handled = make_actions_handled(); - let actions_handled2 = Arc::clone(&actions_handled); - - let t1 = tokio::spawn(async move { check_and_mark(&actions_handled, 42).await }); - let t2 = tokio::spawn(async move { check_and_mark(&actions_handled2, 42).await }); - - let (r1, r2) = tokio::join!(t1, t2); - let results = [r1.unwrap(), r2.unwrap()]; - - // exactement un seul des deux doit retourner false (non traité) - assert_eq!(results.iter().filter(|&&r| !r).count(), 1); - // l'autre doit retourner true (déjà traité) - assert_eq!(results.iter().filter(|&&r| r).count(), 1); - } + Ok(()) } diff --git a/crates/steep-server/src/dedup.rs b/crates/steep-server/src/dedup.rs new file mode 100644 index 0000000..b414859 --- /dev/null +++ b/crates/steep-server/src/dedup.rs @@ -0,0 +1,120 @@ +use std::time::Duration; + +use redis::aio::ConnectionManager; + +pub const WEBHOOK_TTL: Duration = Duration::from_secs(24 * 60 * 60); +pub const REVIEW_TTL: Duration = Duration::from_secs(7 * 24 * 60 * 60); + +#[derive(Clone)] +pub struct Dedup { + conn: ConnectionManager, +} + +impl Dedup { + pub fn new(conn: ConnectionManager) -> Self { + Self { conn } + } + + pub fn webhook_key(event_id: u64) -> String { + format!("steep:webhook:{event_id}") + } + + pub fn review_key(event_id: u64) -> String { + format!("steep:review:{event_id}") + } + + pub async fn claim(&self, key: &str, ttl: Duration) -> redis::RedisResult { + let set: Option = redis::cmd("SET") + .arg(key) + .arg(1) + .arg("NX") + .arg("EX") + .arg(ttl.as_secs()) + .query_async(&mut self.conn.clone()) + .await?; + + Ok(set.is_some()) + } + + pub async fn mark(&self, key: &str, ttl: Duration) -> redis::RedisResult<()> { + let _: String = redis::cmd("SET") + .arg(key) + .arg(1) + .arg("EX") + .arg(ttl.as_secs()) + .query_async(&mut self.conn.clone()) + .await?; + + Ok(()) + } + + pub async fn is_marked(&self, key: &str) -> redis::RedisResult { + let exists: i64 = redis::cmd("EXISTS") + .arg(key) + .query_async(&mut self.conn.clone()) + .await?; + + Ok(exists > 0) + } + + pub async fn release(&self, key: &str) -> redis::RedisResult<()> { + let _: i64 = redis::cmd("DEL") + .arg(key) + .query_async(&mut self.conn.clone()) + .await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn webhook_key_is_namespaced() { + assert_eq!(Dedup::webhook_key(42), "steep:webhook:42"); + } + + #[test] + fn review_key_is_namespaced() { + assert_eq!(Dedup::review_key(42), "steep:review:42"); + } + + #[test] + fn the_two_keys_for_the_same_event_differ() { + assert_ne!(Dedup::webhook_key(7), Dedup::review_key(7)); + } + + async fn redis_dedup() -> Dedup { + let url = std::env::var("REDIS_URL").expect("REDIS_URL must be set"); + Dedup::new(apalis_redis::connect(url).await.expect("connect to redis")) + } + + #[tokio::test] + #[ignore = "requires a running Redis (REDIS_URL)"] + async fn claim_then_claim_is_a_duplicate() { + let dedup = redis_dedup().await; + let key = "steep:test:claim"; + dedup.release(key).await.unwrap(); + + assert!(dedup.claim(key, Duration::from_secs(30)).await.unwrap()); + assert!(!dedup.claim(key, Duration::from_secs(30)).await.unwrap()); + + dedup.release(key).await.unwrap(); + } + + #[tokio::test] + #[ignore = "requires a running Redis (REDIS_URL)"] + async fn mark_then_is_marked() { + let dedup = redis_dedup().await; + let key = "steep:test:mark"; + dedup.release(key).await.unwrap(); + + assert!(!dedup.is_marked(key).await.unwrap()); + dedup.mark(key, Duration::from_secs(30)).await.unwrap(); + assert!(dedup.is_marked(key).await.unwrap()); + + dedup.release(key).await.unwrap(); + } +} diff --git a/crates/steep-server/src/env.rs b/crates/steep-server/src/env.rs index a7752de..771c490 100644 --- a/crates/steep-server/src/env.rs +++ b/crates/steep-server/src/env.rs @@ -11,6 +11,7 @@ pub struct EnvConfig { pub gitea_url: String, pub gitea_token: String, pub gitea_timeout: u64, + pub redis_url: String, pub metrics_bind_addr: Option, pub sandbox_max_iterations: usize, } @@ -25,6 +26,7 @@ pub fn load_config() -> anyhow::Result { let gitea_url = try_get_env("GITEA_URL")?; let gitea_token = try_get_env("GITEA_TOKEN")?; let gitea_timeout = try_get_env("GITEA_TIMEOUT")?.parse()?; + let redis_url = try_get_env("REDIS_URL")?; let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok(); let sandbox_max_iterations = std::env::var("SANDBOX_MAX_ITERATIONS") .ok() @@ -41,6 +43,7 @@ pub fn load_config() -> anyhow::Result { gitea_url, gitea_token, gitea_timeout, + redis_url, metrics_bind_addr, sandbox_max_iterations, }) diff --git a/crates/steep-server/src/errors.rs b/crates/steep-server/src/errors.rs index 589854b..b06bda2 100644 --- a/crates/steep-server/src/errors.rs +++ b/crates/steep-server/src/errors.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use axum::response::IntoResponse; use reqwest::StatusCode; @@ -22,8 +21,8 @@ pub enum AppError { #[error("WebHook have bad action")] InvalidActionErr, - #[error("Channel full")] - ChannelFullErr, + #[error("Could not enqueue task")] + EnqueueErr(#[source] anyhow::Error), #[error("Already processed")] AlreadyProcessedErr, @@ -62,11 +61,11 @@ impl IntoResponse for AppError { "WebHook sig header is invalid".to_string(), ), AppError::AlreadyProcessedErr => (StatusCode::OK, "Already processed".to_string()), - AppError::ChannelFullErr => { - sentry_anyhow::capture_anyhow(&anyhow!("Max concurrent tasks reached")); + AppError::EnqueueErr(err) => { + sentry_anyhow::capture_anyhow(&err); ( StatusCode::SERVICE_UNAVAILABLE, - "Max concurrent tasks reached".to_string(), + "Could not enqueue task".to_string(), ) } AppError::Other(err) => { diff --git a/crates/steep-server/src/gitea.rs b/crates/steep-server/src/gitea.rs index 7dbbfcc..e3e3112 100644 --- a/crates/steep-server/src/gitea.rs +++ b/crates/steep-server/src/gitea.rs @@ -1,7 +1,7 @@ use std::time::Duration; use futures_util::stream::TryStreamExt; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use tokio::io::AsyncReadExt; use tokio_util::io::StreamReader; @@ -281,7 +281,7 @@ async fn read_capped(response: reqwest::Response) -> anyhow::Result { Ok(String::from_utf8_lossy(&buf).into_owned()) } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub enum WebhookType { Review(ReviewPayload), } @@ -300,7 +300,7 @@ impl WebhookType { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] pub struct ReviewPayload { pub action: String, pub pull_request: PullRequest, @@ -308,13 +308,13 @@ pub struct ReviewPayload { pub comment: Comment, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] pub struct PullRequest { pub number: u64, pub title: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] pub struct Comment { pub id: u64, pub body: String, @@ -325,7 +325,7 @@ pub struct User { pub login: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] pub struct Repository { pub full_name: String, pub clone_url: String, diff --git a/crates/steep-server/src/main.rs b/crates/steep-server/src/main.rs index b18de22..3558457 100644 --- a/crates/steep-server/src/main.rs +++ b/crates/steep-server/src/main.rs @@ -2,6 +2,7 @@ use std::time::Duration; use crate::{ bot::Bot, + dedup::Dedup, gitea::{GiteaAPI, WebhookType}, open_router::OpenRouterClient, sandbox::SandboxConfig, @@ -18,6 +19,7 @@ mod api; mod bot; mod bot_actions; mod consts; +mod dedup; mod env; mod errors; mod gitea; @@ -99,19 +101,24 @@ async fn run() -> anyhow::Result<()> { ); } + let redis_conn = apalis_redis::connect(config.redis_url.clone()).await?; + let storage: apalis_redis::RedisStorage = + apalis_redis::RedisStorage::new(redis_conn.clone()); + let dedup = Dedup::new(redis_conn); + let bot = Bot::new( - gitea_user.login, gitea_api, open_router_client, config.bot_max_concurrent, config.open_router_model.clone(), sandbox, + dedup.clone(), ); - let (tx, rx) = tokio::sync::mpsc::channel::(config.bot_max_concurrent * 2); let app_state = AppState { - bot_tx: tx, - bot: bot.clone(), + storage: storage.clone(), + bot_name: gitea_user.login.clone(), + dedup, config, }; @@ -136,7 +143,7 @@ async fn run() -> anyhow::Result<()> { tokio::select! { result = async { tokio::try_join!( - bot.start(rx, shutdown.clone()), + bot.start(storage, shutdown.clone()), api::start(app_state, shutdown.clone()), signal ) diff --git a/crates/steep-server/src/metrics.rs b/crates/steep-server/src/metrics.rs index e23c36e..ff1771c 100644 --- a/crates/steep-server/src/metrics.rs +++ b/crates/steep-server/src/metrics.rs @@ -10,8 +10,8 @@ pub fn webhook_duplicate(event_type: &str) { counter!("steep_webhooks_duplicate_total", "event_type" => event_type.to_string()).increment(1); } -pub fn webhook_channel_full(event_type: &str) { - counter!("steep_webhooks_channel_full_total", "event_type" => event_type.to_string()) +pub fn webhook_enqueue_failed(event_type: &str) { + counter!("steep_webhooks_enqueue_failed_total", "event_type" => event_type.to_string()) .increment(1); } @@ -23,6 +23,10 @@ pub fn decrement_task_active() { gauge!("steep_bot_tasks_active").decrement(1.0); } +pub fn task_skipped(event_type: &str) { + counter!("steep_bot_tasks_skipped_total", "event_type" => event_type.to_string()).increment(1); +} + pub fn task_completed(event_type: &str) { counter!("steep_bot_tasks_completed_total", "event_type" => event_type.to_string()) .increment(1); @@ -48,15 +52,20 @@ pub fn describe() { "Webhooks rejected as duplicates" ); describe_counter!( - "steep_webhooks_channel_full_total", + "steep_webhooks_enqueue_failed_total", Unit::Count, - "Webhooks dropped because the bot channel was full" + "Webhooks dropped because enqueueing to Redis failed" ); describe_gauge!( "steep_bot_tasks_active", Unit::Count, "Bot tasks currently in progress" ); + describe_counter!( + "steep_bot_tasks_skipped_total", + Unit::Count, + "Reviews skipped because they were already done" + ); describe_counter!( "steep_bot_tasks_completed_total", Unit::Count, diff --git a/crates/steep-server/src/state.rs b/crates/steep-server/src/state.rs index 7ef7b47..a6857d7 100644 --- a/crates/steep-server/src/state.rs +++ b/crates/steep-server/src/state.rs @@ -1,8 +1,11 @@ -use crate::{bot::Bot, env::EnvConfig, gitea::WebhookType}; +use apalis_redis::RedisStorage; + +use crate::{dedup::Dedup, env::EnvConfig, gitea::WebhookType}; #[derive(Clone)] pub struct AppState { - pub bot_tx: tokio::sync::mpsc::Sender, - pub bot: Bot, + pub storage: RedisStorage, + pub bot_name: String, + pub dedup: Dedup, pub config: EnvConfig, } diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1b3cfe5750c77a4dd21c5703bf6dbc502659fa GIT binary patch literal 152163 zcmV(#K;*xPP)g1`^|t~{rX?j-w~$m>~9fq6?R*dU4

Ywf#%Xk=G0-_=JJ-_-hU zBayWk{NOPO?liNf&c4FvZT)HkFrCw`Qje))EL zY51jWx3Y=%QK44mZk9szaekGKI|tNAv3+szDpOT{XNj+FxOZ9~XoTPSyZ&pJc3*lB zyH{6*Y4?X7Xop96I?4&~@ZLa|gKFBPsx@w-`52#9J6gYTDIO~nz3$!cj_*$XD462? zy`DLCeK+VsKZgEBd0cN@KM*^>m@3zND#@iUHSiq7*4RU~p1NgdHXM-zX=$%-35ctX z4pw=MRcJ3C=h0?*3rwv&lKfIc+cW>4u1w}_QYzaa8e{e?wOOp*RL zLq{k8K3C@zCpY5@OPo-i{yV@RM6sU3BhXSi?3b%jnHQN z18&^qJ8FE+1&d@DbjKO6dm~~nd(0&0`Fbfa^hgpG1$W1ag|+60T_^^c%&*#N^Uk;z=1PWIDjwFSuG5Z zx`>mfnd|*$*IB@28FOko4|v#~DU0-NR_uB(;+d+J2(6$`qA5DP44c=2NH6$O{V| z)sTq0BrX^~K70{6lM0-Qa14g--n+Q{;KhfS&}J~n{2Y+qWy3_}4m~7{Z7Z-y9R7(rQN*fRTx!gS4t=^M!2%TcNrfEQ2M2EUoV3M#zq9%g&4Hvf&Y() z0W}V5Gyp7Y;kNbDUVIBSee*g`1v6k^C(9l-c?B*Rz#Rmdq%meYlIIwoKPyT3~ihkLQ zbQ>0TcX!f2k$kKMBM4`$XiO9S79OUFR1SbgOj1EOr^gJo^C3WV;oaF8r9kJf#gd&8~|ATA-yhjfounm^F< z+N0+4PVdEFfbqRp5cLbtG}-=29LfpSp`nrZhs^P&#Lck9Gz$zs5{GslKAEVFkQwt{ z5W&v)_O=8c4;||ZVyhvtm0`^c=*)W@_@K64`#kOLyz*i5luH^V?Bxho5%NxuQHBU< zdCpy|8+TI7fvxNpW;br^%3=v~39fT~0;eu`%`&gIhrqN#EFE4S2o92|aQw=P%4jh| zs{zQ*Jud^BeWYfcXL>%&uel|5ql3l7lIR0u@nlB{!Yc(-WBwkJHHYlyk9)0YF4!zq zucwnXKqAT#GIqebApBfYLRSO1yPk_o6uAvulyf^Uc6;H2$&)!qBOpg$X`5?Iwk=k! zXbd!5Q2zv^Oy|bVu>Y2G@6jn8}(Otvp$l(^E;JH`XPM0S%YD0ExH$q|JcP^&S z26M8vJ{rhLk5YgwGk#()CBbj`1=n|&#IZBGlI*6pkp2_QheWE6{v);loqyJs(aSHNgl#-MUeOI&l4oq`MoCc3P3UN5K* z5V2Q&uz6$_$3cUMkp#x0?7!eh!yM(Xc=P_-56c{fpjAMN)^ai~XhW_nMqcS4*c5xI z5*?=X-1rkW(AaB_WwrlNzD@OMl!X`x)|rYg!5a2RIJo%-WJs4rPM}drsLSbFJ^w@q z){2-;!wp`@=si{OZa+bnE%P+oH6Cnc}wSLO@j#+MZA#tJZ*FeO9)iItK{jeIddb88lj5{vsUR5Q2-p=3+YhM@FS?oK1?i z`o}#1s+~i1PC#KTW-UD$TIGtu$X6em=blW&=*EJml*H|#9L$Q)Fo-Eyz>|zTrMbru1 zlTJ7g6|L)sPYrHv4C3j_lpxS_w`SpCWX}v$DC&1v^?VhzM4QN>S7@`*rPfh$FfW{B zozrS!a-h1u6>Mj^avG&ml`s)!K3oNt0}UU9ANsn0KmRhW>5L*~?4=E|&@vdc|2D90 zpi`L*+E@p+1zPn!!%9n{tCsmI&zNa;#vb6{43Z2G5U*s8AnUjb@2DE(I4*kJkI8|N ze%l+6rB|MMmnhFvM@$V37JIZ2|3u~r2K$^ZWzG2jcffoiVGYR)jdpif)4(lOopDpB z19swj84;Nlp|x`Kjwe3b{Y%8B#fP&<+@;e}cw_wg5#6txM7Q zv#oZ>(nPh@K{1mU&K8$fWO`U~O5$k&!4($3f>wJ_??7sbkw*|{{|GkzbP_{~ny(Yc%3n{7WzONCJcl26?E z2fel=eH){irOBSBQr%hn72MNcf@87Ddkez%&T|7Y7DqkyBGr0sqSL;4pM2WF7fE#x z^g3+1!;DWNEHa0wF!c@|R7oO(Q=BmR;wdkCtt4=oTh&b{U<)+%laU43-JF1isbzME zwY%ermF4(q^eIPV!VuKSZB3vR-84Vs4~ch;##PP@Np+ayHW=Sf-@AWCPqDBy@9AsV zbn;asfq$|wlPU}a8a^&k$lrXhTmxZXtR=zFG%I4*}wuR%o1anQd2-v}dS;guB*omwWa zV+mBvd94P!WQML3|FnQw<`;8ZR=j7P+nE}VffOO-J-NJaMl>FbH3yK2a8mdw<=% zHOPR`#J^f!1H6iaMy{G6I4&1Y9TqG3nk^#~1I<0KMuHT8{|z#5-AtC5E)`qD6Hp(l zl$h0WPRp6)o}aLdMyJN!mM`J%JD5K_acsfo-0B?P`d$ zUgn6|%lixfcL$7&>XVz2z?kRGDWvVkj+o4~!MXLrAnsz$R#dG0I|;0|e+qLohe69T`(hgL3~NIHW5hdK|B zCimRla$K*NZBn0A<8d0ZiC#N_lO;Pp1mZ{LW42yIsAYBS(|&z+r!=RVH{cEAZV$h0 z5qq;79$p`Z#abvOgPLoUbxH|U?#hIa)T2r{Nv;oC2SA?05+N-JJLC^K`dZh+WfT0D z%!d&yc30kpAdapXyPizFcN*(C9X+A3E#OT8d31R?dT;fkGcb z0dVT2L<=F9#u&BgLuu7q3qGTfLvtpOf>;ff5^u=>owIaHbf09aCr%iEoP-!8ub_rrG9Zf*UOwqH`p`Ps85ikXqWkNo4QLA?Dz8QfW&G239%m?~Nd`b>INRg~t;R5#5cL(pV{CNAZi^ z*Y3Q{FllLzVf`wMo$C09d>+3!MJ;StJUG4xP7e%7@Qe`Ts7)`(!+()3I3}ugdo}h- z@7Ey{7*tY7rAp0ee(HHO0-iQe85L8Pj(Lc$#Gu5dhbypkqRqb62bj2_6Va^&WH}v!uLfKK@EF?fv1ChBS5RVz@`gY9}`K*l+aJNdIKl!;fL8-?WM!~s>!A{yGMY%jhmvqwP;jl+A z+IfdAIrZA<5YlNiBKOR}`Sw$qiu!Li5k_b+f`-3!B07d$FPfRj&259-0Ati00xgxX zh28M%=-MLz!kQMW0o|MteN4?qhYN>gMy&v@LzuwqMq6YlN9{Av@uBs_?bIIF6|ab% zPQ5a-p0cdI(HjQa8^YYusE?WP_649Y#0kKdq44&|xMRs!;$c$%X&>*@^Zat&|o1QOlK#~HE;SX|+0PXSDwI>V% zE~c?TI<-oA2q!&L4nm~4R>LD@55x< z8u-{kGC;dR41*Q+BF-uI7stsPH+U4ul`EwUA()6)57)xtr1FRe0G(({h1Y*qtQv9? z51#@%ySgF|mYUVn$TD=c(gwKIj}=jP%G-D6F|3ht>Mi&Q*l;v-IanCx6?g>p*A%od3YaC3zC$0zSE9sIDkr`-a5Bki%b?vY`f_`EELX+sZ*~Xu9d>4Vez@k zTY43CI1JoN#O3k3J&LLx`huPrJvEz!a!9g$1{pEKsuiZP5;5eMe zRjra&ZM4nh3!a}+Sq5?J-Tm`aA`3_IMd-{@YLTgpx9c*U|HIWx(=hl*@n&6V!{rcUchWBu#h^Z}IfSi%yPnRp#6 zK-v_Y4*?U18pEzInafs-v>a0bVvmXi-O^c?YhZmIybKt6;31T-eOW%#&(gbAa!Lxt z-Z?7DAZkOyzL4Fv&dJ@7y>iuy%^Dk(US=V!ai?~x&bp0PFLZFcOF}K+M%k>lQQJ`V zl4tszy4okyAdsKntOk~DjJ5@;dJMgGennA*!x_t!lMl|4>U3RwoE>RkdJw35gEVG* z>Zq&a1^H1}T5~BPA314pTo?@A$a4Mm=I#A7P+}gmWJbv6u_F8UeR&iS&5{Bc_z^04 zd(uCZ@wU3>B>!OwoJ_IdI3a=W$JSZF*6?>~2gl&UuRb*1TWsGLhoC%;pO`93Hc~)u zdYSBll#TlHXboL{cW68*8ME>RA;67udsnZj4QN#r>p!@X>DyB8uWj#3mB~HXr(U<3 zu}KJIyY!aX7&!GO#By}onaeZa6zy`_yr~<+D{7Ry<&G5ND_=yev0mtIYtx#$FlnVf znKkA9pYU;d*S3fzN$_W=A|F2)pK+`oLOwKP)4M=Oh*SCk1n4CA{@*8bSJt%FMX)5| zG3mF0)^=w!jRt>3Ucn(v@dR0qZ%=M7A@awgPGFfhF&h5kv>Ty5t zKBnZ$vZJ0C*Ko=J6`!^d!i6e5pVB(MRAQrxR8VAQr}!~$YPO`vgTV@B4rIS@gF_(4 zFuh_Bwte`_ESZR5%Io|I~RYestw6&r2OdT?!85G~A3U=U?ploOp-a9C*7$}K` z`>(Gk1dfx92VzaXq`=a`IAjEzqG$MevbF@mfj|;?tLfI7hz&r&YJa@%72Si=%WB)E zaLO215;#Y=Kr=&p;n33qKLQ;pzDDcwO@;E!14JgcNke2eBi=V;7}z;iqkCwL=*H&s z%!7%_@%$+%gHIYJNIh3uYebmC4**X<67Yts;7;QT-n+oWi^4(m{&2lT9= z8)`1df_FR+TJuy}p3CB`mY_`b^@xtss;Z6(uyhQ2bTl3@cNECkevG6kq(7*qS71XCaCg zRq*>bh)W$0(P44U!)70q?{?huh%7|s#^+dVD7#z;mfF&vfU`eA2Kc6=>`6+%Mjj{~ z434-v!PLw$y?X%(c`^iul~rtx!|FqKA{Tx2aASQnn7m%~w%iU_fJ|9C-IjjW9Df{< z`c4VPI7!+A;k&ZH9UG0-9Ueb>=()3pqG|mp(bLcOA}*36y-PI^Q5A{dh?e9vTQMbd z%Jf_N>XpIdV3&w0Ha0pa_*HB|A3mDbx2cNO7ZUQO$S5r&Rigp9Kg>(vFJr;m>6|yG zY?guTbI4t_cD`_Shr=@-Yzk_pL^>K+Rr7I+RD@>M?dRa;)Cm}m59=G{6i)n7iSg}f z%N}*rawqO(1D;(6(W;eL8FkfeVMUB)Ztc~|rqX?e^q&b96`}0!Ye6}d9k#5m;BJB& zWRpw@@&{4!a+A&MhW5?Glm??UJeej z))t^`m%1H_`T}Qp9r_6`d9&^8c-W(0%rl$IgyU;dNT7S;nC?u)`6G-Bj1i`((Pf6T zNf#e(OXGCr4s%X!P$u}(V7h1~Fu%2;o#9Yui z5!gjo52K)y%4>S212ukCA3A@?L8EWz+h1tBNUEdxO{`M8%pI@6jEBThxkshe_ZcGB!ecGO7YS#gDd+?KfD!%*+VDz zj8m?3hzXDrX)0xbYd|)i|HPB0P@(9UOEUA#ZG;B?zy^X`#!dDUSc4Y zVg0@)EFp^!ot`rq60m(F$PJgZ$1b1V6=w-tI5Mu%x(V8jSMHpMj~+j_ffZ^smMRTt z!eRaBI+cxnFkhI_K5FfQ9zbT+zws_ca(y@?RKT$3c)CB<2G@Q4XRk9#j;%G1D* z{$67a(1#T12;=)HU2hl{I!z9Qal#I{by;%P7?oO40-;m4E@2`tCgiUp-Aa)H+l-Ku z;C&QC$Q|@8y1batQ(_u&f!RrmIwdu;!WA+#SzFpdAHoNxj!12f>^p;maFdB+Sk1r| zX@#wv7Zqb<1nlP_vZ*vJ$n)fZL=)F!3JBEO+2Sc4lzo5hR8`f$&PS*sYeJXeh&W#D6ETvw_HWu#EQvEl~ zxL?Nf1yR1lu^$pHla*=a(4F}!I6t-7v3IZRpQ?Us#~$Dy@L`d^6 zcXB=syf-~YX4$!g%4x(=wz74$$1p|y;oyF$Zp zj2+M3sR)nIWshhGF=aIH#j-NQ_IAvM5R+P$!$M#p*pk`^eUT3CkeeP^_@M7dqLX;d zFFQ67oEp4)&%9y^!-(MvBKYaFhqmlQOv}(i$o8VP14hGKCPdYHB|^pdS8m27{N$HP z@6?E0BAOY>iw>K?4CaA4EQ!~{J*;E|EVHYf`1!Q|Xfc~{|5InyW^~)udGN&rIpPpX zB8U>@S~!pwiGo<60Ex>|Mf+~V3_tXAUZ@Xj&^svD6iA?EZo+U-fuYu3S_%XA1Q8Ps zZs)<-x)=LnTn!mkPZ_-KyxI*PFQv+WZHH?)7 z1f<~y$JJ6l(;6@xf9TCvIZ_dtWuVE^TK3DU+3qWRbs(b+^MHN*nVKWwO4E+CRl4DF zzgSlY4w}`~j3L_f-!=--$9(X!!qP~ho{W`VX*i(?&X6uIYc-lRpy4C}Xzks<5 zH7T!}GPD+APAkrL^3P;-JXnz+1T^IWy}l!IxmF~}8t_ocqUi^E-bq_sra8qrQ&(%< zZ8kV#b6IOhk{V-EFjO_R^f1Yb_sjta#-T5Ojt*@p5$VyYO`nTy?~cfK2lc$#x1uF@ ztfg1oWtgQFKXAhOYsUA)p1vxGC>qhp8{3X1H4<(d_{O|~O8tZlzECfmTYI&;!|Ufi z9{?9)*6q~-)JLV7rh4P%nJ9q{+Ei>4P)Sd_UbC#+_$aq1qc zLG*Po7Wg0%bU%v3mWi+XgeDhOao!PBhpmYBF>>q>!>CIuwYbOa^!N66+AT0YtQ@Bwl|s{@Wm z;Gb6%T+du-?~YalG0}IBT2V2MJU-A7f3Rg)^699$w44iA)I&aHJ3`h320T=1AjK1u2O{}AVaL(?7_QDL-Y=COpxzz$D zCqv^SpH82O#N<{iM;^*TSK{jI60G`OTRkK1isd&#kMaLS41&x8De5fm11kJPi`a_(yFtsgO`h^k+QiT zIhf^6Z!JB+7%nHmQ^Oo9x2H(>q3|+!EWM6p;6^mK5lPE1nsKZNPX8;S zmQBD*)QdG<$IFD{&^cEO&p44Jf?2VYt|wHIeVrXjjcADH4Rj>U5yYN8_FR^MQ#EKI z>}4^7h`TjzIq}G+S$HMR56lhl9$jumdnTiXi)^zm z<(_9SWTniT7-}L3nxz*_O3(`CCEUa5s}8qTHW;6{kX-?sLVN~020|$?$&Ebku6mn8 zhn3V}7wh4Z{RES=`a8gCC-0s$Of3aXRl^R(opNbU*=#k)4;q~MC8HBeIdp{WofB+K z!dl4=ydMwjZc*nnPSlyKH^{;w4hnb~>a{z=)~0wgvLulc(9|tEjJ6#`C7}9gB@;Zh zk|FYuk^%Z?saCLX=y?$k-k-|s1uH*(n2KPt;e2=y2*dP20eKiu+;{dm9^S#@rZR!r zmuu0uG8DDl#t{#6xvU$(!WH{CnE3;y@NINY`vGz9!RiI$$`A)I8BszAPVsIn*Kwyp z*u3V&%e9sbHTn?bY`jd{Qw$UN@8+H#(aW zO|zh+Z3p`R=x!p(^e@qh(&s~P)|$EH{?ExcO;E^6shXKeyW6y@%cp8r5wueS(Pw3< zVx9qx=>P^&DQ`MEizM`OHw0_y&4YQu50(-KZSdYwk>4%9XG=-G>RF!?NJq%)IOaSz z?m%^oQM=bXnq0X{c-5WwLSX?Hei+&eWRB2ZN`VTTq(_n&45{0A(cf^zrjM-4S8l{8 zcpp@eDrQiky+vR!0N62Y3|k$Z4Z<+w7Yf!5PH1psF zhV(>h`qlc=NIkK&ekHT8Hh&@g`DG>La0zt8Q74AR}6n zN=3{jv&=9|Lc-~__tf@BmQfRw2MwD&Ks*Zrc+wx@R zTJe+c_fGm+u>>%+j%UT{E~)OuRCK*znhj%XU^fS06^1=+=oy}2kbFZyVvnPfb5kH0 zj7kGZ-X)wWhJZxv@CwIfX#=ffsX5eTyz*MpK@J<=D+*N*s?LPlC`?|n{^=7|05hz@ zGi@U?mN!ax3_Sh9OD1ylD3*VLu@2xNPHMbEOBRK!szN@H&kFP{K(ddKt&1ye%DC7a zdJReqT#OUM8_XS$ls)t{ck^k%p>x5T_w2rOF~Hl)bvVvzpVl{v3CC5X523tY~Qk)|ZqpN<|3GbKFjj9AM5llXFmX{iv z{8sGUqp!mS`}t48608v$Aav(Q1WqA#W?7+?*tFSXa%_+IpzR%WDxGTHlDgUP zUq2zfkLE#*cpWyE+#VN;9a6`sNWiA=HWJR|4BTsI&(=>twA0tX zJ`Fca?I>@?CNm*3_8!#QsX8d=45uini5s;I$;rud94c#eoAtcn0W1y7YkzDvfkEzj zQ%peOJMkNN=xLq*oku?C-9~dYyt?9p) zZCxktRx-$NekAW%QAbpTA=spujw?j7H`4FDM;kpeBJHjy2F-G$U;!ZZy4QPaAe}Z{ zrEcE8Jq=tQ7|LfrExcq6?g)|gLzo3iQqQh-ZK!L53$g{Q_zMi3|U zL|^;}GUhvz`GfFEIz%w5%;$rgZh>6VOwI{{tCSRfE|eyF5h{o1s`OWyNRU3D`d7Fy z_D>jU4=A|*jkHdLVtl-3U|*5O*>Sc~jU3J^HIDu|0kEuV7FsfEPJ+APjXDaHz+!*E zk?(}d>hPJz=4^8V(W2K$P0q_9DMfKaBvstaB-2w5PLYXSfW6xfn$?C1+>iIodwV?! zPsO94>u8c0ceZ61C-p?A<8VJL*#xj23J9R}t>73ytxNFPuI)W{xWrg`#`_!G3Dmh~ z3Ndv*2l1+=OufvOaKrHvb_2wEU8C*uX!QX(j03pO@q$QkIc|rkqq-F*&&LjXSSy@9 zWYbDP)6g)P*g_KB=abRrLd=+LT8%^6&4>bj28v6#5B;inoUSH!<==QTN_OX2f&{fs z{lqo%YkQ!>&|8ptPGF<|8rr)wDe`{BxQxXuy248agED0>C#R^HoiFGTLp)%tz{88N zk3r|9A1x4o=DuqeRud`U8HH9_G$(3@I4!AjYh|-SkN|f->E02w>dA5-tKRKSK-IVS zp>+~#-YvW8wR249999*=xkoN-ZQlo!?9`axLxf_pKfsrC*T}1YotA)mH}~w#J9wOW z)%*{-w%bq=MKF6eGfXd1Lvitmih;Drqv}-bI9lyU$EaI4N$07C*=`0o&Jwvvx5L&X zJ*cy#9-*0ARG|IlCEp{_FeOo^HGM)W01`J?bgDHMS@}H8z{@k$7;uxi0uw^c{%stI zb_kVl1#Z;PZmt$5t(k-{>P{VGZ;M3pNWA)?#N!b9shFzEurQ*h-^jo7B74xCE6vc= z-~}25b#>EmsylXz=3r!j1RSG^rtu6Lw& zI-Uv*-50=N1Ggs*Ou6o!BG2OiE zB;{=|X=&;wBX_0df6`=S)G8Y4q(jZ*k>Cxp$e|CJ@&X3?=wh;djCk$3IWBbC*y)iB zkU5gvLso&36Zs;j;H85;4qvFnZ}tnV=#KyD^lZ7C7!8i$f<*!~qcASQBIw`-5mx*7 z#F;X+I8LP@?BY2M4dCC-t!@J8z?jJY9Tt?rj0Buo9+qwneF=FNh+VePC%9uGMbW4@ zxz`R$wGYAEl&JTX^-U9m|Hqh{8}4PkdKYphX@fM@Qi-rYQ&v&uWXz?GU=gjI^D`@I z7@MrHW9nDYJejlUb2JU49@n}8XA7;Unj#|c)|S7 zIcEqS^K9Yz3OA9uSjyb7aJeHf`k^=k^_gnXeU#VW$%1BOiAuYGs2pQmo_GPBA zw;Jyui00SYsKO^ONGVa+x z`k*JcYJdi@E;~UX!p?KwA)~-Ws5W{Gf_x6YWikZD7WW%?Evi;+2pA-)Ic~$9YuN{CV4>dD@ zqlWu_XRsEl_6k5ch1uLDh6+c5#qY8~$~DfzE9BL=TNZNldMFaiKC2a$4mvSq=sB{p zMF&LrH0^1t0lJdTT42FpwCF){BPc$R*oOGMbOwgCG=S>cd zKtd2hTS|)pK~TJ8e%*fVMCGtqrzJ<3y4pP!v!+ADKvkd}PvEd8t827Foz{N3j252a z77dpRUk8hEaYctUN6pD|*|Z=DU553sqTI(Z)Jq_2M8EsRpci+C7VHmh6Ch!r zB11h%U^SM7i?i8h=iePI>JuPVLhmW`~zK%rg@iz0xzSY+Y7_1d51rF>Z$J3Bp3{#M6=`Os} zFgW-TS_QxA;3==R!X3?l_s#+}-YC|8HbwGe7;A=!lMD1|#TW+-{~odusC_D1%#ISQ zYuPHxxxr#=uozYbzYLvtc5%I&EimVK44N5aPSE+qR#8|#UhH&Xv+#j6Bo6Iko7>eA zacMcX@AJud>rO1o{lINlx00iL&92eIvCFLxJVp^M6>C&o{tnjK!CEhW5U#ln#{J5* zg`hSW7A1p1_QRBL?dfHhNJcfLoll8=68X!=A*3Fi{Wp+-m(Ba2k5SbH0D) zXfsn5Q40YOufqiZbnYoavMilw(9xCRt@hPy;H83~B@h%0GlwPgbbq9cu5SV>Y0|?G zzmm}Q;%^v59tz}Mra86>$hb$2mi~0Gu+eCxUp9Mj?El+a;#iWk@tv;+9lMW^$E0fC0*njn}N8Sux`3@}kDhT{pZ zx-VvwU__s6Ftvm2QW)xR<^Oj1B$fRfG1AY-O3;bMmvYt3=J%H zbCAk+1_U7#eA0jv+xa7^U7IDsbE@d|R;tyx0gHuKj15~@Py%ifr z48m#JG-Ib<^61y9pAM{GE=*Oh z(w=;qWV5Jmp2=dGgMKCa;M_!WTX?i+ql~L_PVj++P!Sas$qAq&aG^@nj8+d_I5H01 zDEZhIWEeHkfemdDb>+%Vtw5$YHPoqV)TWb3&r@#Sz<9v`K~=}pa5uDX?LF#ACD)xc z7*NID#(+*3S?gKeDKR}EO%%D~F!-@&!Hk3n3q&Tg#DmJVg18pTJO1jlPXFku-(b4q zRDW=fC4~Z&eP!?0HSvHMC}T(Sz{42m;MW$P+!jDZ4cTr}x5G`!erHFv30DL?70Nmdb zeH?<^foe4!NCjXS-(?mZfgKKF(1}sV6trxshm`Wrmd7WrXE+dGK=T);HpPPy@4V0D zd$>GwD#N-WL*ML|Y^}D%n|n!_3;|AqO-Wl480Z?hznSE7R&c z9+`c+=DH5;xyE1HC7Q!0#8`p$Y`2=WH4wgEo4Sg9+Xd6j3-YPw@a=G~@$$_=c#Xv} z8SG=Knn1po5#K2r)Rh_TDvOUllcHVvEe(ugz^7~wUh~P5f|KjH1zR9_tqZ3V4+7QQ z+XVt}Ly>2mNDk-7w98f7bnEY9$H};G zAF)6bn%++k>EX@zzKwi=TJ|zzURMUhAa33CblOEuGEbTCQk9JC8|OZImnFb9Z{EKB zu+4ChUUUq`rfYo(qTIDEx5JD1x4hw}p9qZ^ISpkRc0}zO>)2i<*E98dg?^w}y*mLP zU6maKLH}?*)QcEmYWRE8j`_M1Noo+f5W5oBX6tn4#n7c`+^VkLbF%`52EmT~E&BZE zfhBs3AJ^N?#Pl~@dfR@g;}Q>jIW#L9AWrgQNZ{O~88AWzy{=SnmoOmH>3VF8Q%AY( z!9b`HEbFxmhwIghU+i+FIhS{Q?H-1DSE-; zqMENN=y|P>85o8s?SC`~_y0V>dy)8oj}EM)dDC;c2dhf6*scX0X0Fp!Ik_@)%~3jT zvXpAZi_&(OdR6GbS|}w0JDBQ97I5#!A6B1Mb&=;j?RRF|u!om6K5>siiaU2{d&2V+ z;bi@pStU2X8WeHNyT*Cgy57bssh8f(+~N}7AmT-EbhP?;3>s8&U_Al3>b^{oeYULC z|1qdpea~od7(ztH`h$o>;vSf~F;O?zR|ar>2t>%n6N4;mH(Z9J-aDJTzO@_eTkHf) z>z4%ds`-IP!5BtKJ;KCZzRRT<*seHk(7--n6mE`{O=9rZI;ccEzsWF6ZI#MGl&pee zo>@wP?FI=4qzLTuI`Gu9VZ#Sa4YHD=ZP_sy5r>!W#cunJh+>0E@#9aE>;R2y8=x}y z!cHPgqT}w?J^#GlEb3p%{yfv~^!|PrUk-@{H_WfTYqac zYehaaw08Gtvpu`^k^Xw8h~kY}xdTXO7~KJ#Lv>^)zyXH^!PJpZvxkdhaHs%MK&`)5TMp&%cljZ> zuw~X`-O(c@{KU&PK_PJ6E$38h=VnHUp5ip_Wqw^NE=bwNZZZ z+Sd(<&T80k;hnyBa$<02``1jLNp4S;Bm4B!NNJ7nlbZ4yNiC{tU(2e=%q3);3+)Ft zZqiK~d7@5sES`ZBd=(F~^xpQk8$Av{gf`{E4q%E_mC0$JJC}|@9Xb@v^+oca!Ozpb z?z?JSPAbi-hCqzfv`oW_EQy^gkv)a|@k>Kzz=G5|ax;yHf;6EhQy8otM<9EgK2~ed z@|q@HJHD?Em8QT>(dUV>C-N8|JdofNy=2FNL1&CR>$x0B9(d_GRU)`#Ygt_dbuUbp z0)*T^*9U1Zz$X!4wlQR{Xk^4~Frchu@{o!N+wCV3wMUr+U2xd{C>X>$>q0v@{Qy05 zov&Dc`*db0bjfia4@_SABCfYZhn-!B)VnpcgrcU_f8R!*+#KD`z?hW|0C`+AFt9E( z9eZU~TPgJd!kFX?8mEzo;W-LfY!m0AaVG2zZk%)_^c8Mlc>xGsR2ms1pB=LJH-z7& zm<@J5h~zmoY@-L!AtR8^3QFRcT-ri8AsC+wtaF8-9K@dX&KnuvzWO3ik!ukHh<|%` z_&5x&in^)mx__R4=N{3{*x?5HIr^u5DwwoEa4&U=ov>K3ivX;QOx>)<{0Rm}uwLTC z2rqx`K_&UZApn$E|K*=12Xz&&?a;1i>|iE*hQ9BZxZGSaaezJWwuQ!o$ZBu;D&%get4e2 z!LPLcfC{)ik(GbEqK9aRWHqIvJGD3niDlPv0@oyp*c{suMjzm ziVe`s>=vQQs8y(JyS2ouhqPCbYqd(epYzqzVKhaH_9g3qYA$KSn#ZK}M;@}pxbat= z1mz}fvHoY+O`Yzi8x5`Y4v?H72ke9b8 z5;vlr4w2Z@HFy;z$f+0H_D-z32#cY%oo~Kxn`n@wlX23-c}?C0Z$B78Mds*ow#+vc zZ60`TYsj6y3xV}L-F0AlC8zozlELRx!0XcUD6@+Y`-_}-H8X%-QF(R#W0E?c<8Zdp z!JV~1Y?Df-ezq0B4*6`%S=iH_;V3G88D^;tB301`$9fe$z2bA+|I66{?G^V<7#n<~Mwy%T~Y2R-?TOQFou|$)6%_yiz@rxZaZV`La}FXl=K| zxw+vnTeyKG{Hq|47T(`V5|n)qFdgfir+UyH`Q16$Jz;8 zgog((y{~UXDfR)|okZ%6LF!V3rU6|t2YGCT(DC%-s>sd{-g#)F`+wvmJdfd2y3y){ z{uOhL4-760TxdjD&Z5CfMs@LKu>3<)56GAqsA1%XZRo1Mx2CsOZ(-6_FIv7w+&hrf zkQuMrFg=g@*F|#oEvl2!-G=DL`VYuJ2?tcGDGdr9{~@0NG&Xu1Cqa8cBW8A-i=RF; zF$}n0zT~24(apjex%JUo(pjoWx3EENp8$_yH%$K*Td^SY6M{ z6m%n95xee04nlMTkQ>}o@Sd<~WgVE-j1EacAD!v|mhg4bRUx*3Da9`7+foG}EQj?_ zT3mFdU@_Cy4f5oBLSa|!1-&oUP5>JQH&F9q(4JgR8+J_-q;lc;BZ&)cz&xccL8V@U zA@^Pi4ys#bB734k>~JXsIF1Tlpi|ZaRtGJWnst?s5iPi%#-27#Cx^`#6U!nER8j1k099?0{7$ z+76tjo>_l_KVRZldvWMy@(Bh?oTtJ06|vYblGhql4-Df7fx)P$U@k8B3%K8I3U$ zG5B~l;Fghd z2_HCFM2Bjj@i8Ldpkad@IP?a?;V}Jr@J{dNh`m6=15xs%3e? zb6E#(7l^suY1{{I6Y09SRry5xY7261d~m4G$6p!D5>IOGOx*gU<5L$3V(HMvEQNS5%5LS0+994Z3dp&jcmFrfL013x)}HJlx4=1s9~8IU$`P z`74NORlmVOfPJxw^hKFet9<9^`*&^6ZG*WvY#yWDYz7cFmnHMLH1>Pqt7&MF-Nuf? z(9OVMyl20%B4C%h8Ni3RAI5BPg??FJ;}2L6z8w>f81A{%NwzTW4^3{xH+Ch5tfwUk zZ2~^4R{%Fn3AVw%?UCKi&vCZ2B?(l>1qB~1RT#~!kOq?(8DC8{tu*a$k@w&bF7Rsm ze&CoFi)=txCIE(jf~%N~$D??rV|{_lBg2Kzq0NJ>YDXkOedVO_VXrXT^)w?9&oBRP z&zZdHe!Ktw8^7KBcC(x7p^u0m^vS&Xd}hkbikN!sllz10sL*Jk^nN5MELB+$bb78G zfk-AC5N|x_tPeTbecvT*Y#$Cy$vmut@&JVN)$5JJpnwEpc(EJQ!*Dv0Be4eEt$ApmM~+JUfv9S-EFACdyo6DLi^ zJGF-oawn9E7V@-vn1yF)4sw_1`UbZa7FyfrAnRPMWw%KZ5w66G#lKtq zy8(KBaS`?|3~p~gs66+>_oxNuE$$G_IIMi39V=sDd2-OH~Uo7 z)qEQ*lSl92Hi|^no}LC8O)Ah2u|PAeE+UhlOTtFYY)_iDJuVI`ibY7D2LPzSAE|>d z5`%+|`8YX4TOC5o$>9udH%eOWzgf$|e`Ugv@((jr-xn>%^SkGF&oAizX1Ax?(+BTA zy?yWY-Wzv+eizT5y?giByZG#0K-c5lE7pZK@_RPg4}+Pwj_%A~#q4A|sfv&v*(yye zD!UX7IpOM=ITxkx0vNF0%hTyg{&GpKY5v54CGmyuM1AM*g_A7FS zgGd-vwS+Fifg{*<{|{V?dN4S0!90d~a}%i@y@MrcFruFhv7sN)&^p}ldg1YMpN$jx6JzlZU90MzXpe~6ZtDeE~U ztnADr9>p9u95zAYK2q2G=C^o$e)su1d(Xf0vtRxAm%sJVFMac)pZoepKlil{KmO92 z_g{qn-KU>@@`E4$-S@u#{XhK6zxmgH@xwp;+jl?u?B(S*IVZgB49f6A;lXIG5o&$D zQuHI!$m5w*CgyWJ!}->H7Mau(T<7F{AYwlRL36Q=h}qxi)E|t~j257hO9#rM|AEA$ z0_`)zR+ZQhC+hbZRHq*K`|QJ@*#Lf__uOUhyrhV(?a1#;c}rso%Pjl& zsh!@r^&YqbyW+&gO8n-kic)J9c6`#&hWMYtVvj`f~C^!m4 zR*4MP?8|s)M&tRvHsvv4>EU+@STCaJb`_W$ht>(F0#87zeE2vcacBb5JZ^q_dGq%C z_GiBIt>60gFaGX7{?@O5`-5+O_4f316~6iH&F#(8?afQc+Y4(x|NQTN^fy2F>wozt zfAYWo!Jqw~fAr(;{hhxvdwNr8mY#bZ*nhePGY6JADGsdo=(ZA@H;HWf6advKfugg+ z+NpvujzGN?f|VxW3*7_3wFI3*m|ZThFY8bbVck3RSd4<6qFW5UcG$Y5ocmg3eX!TV zEl(edLG}~XEm$V94qG<7O-eQ+B@$p>XKW!weJCz5#>H@KaVK%4tqJPf+Un8v1Fw!) zM9avxeJQ+{@0NcDygTlk^Qu9e#3`OoaTs2KbQtlxXlL?9n`^0tWr57|(dkO4HV-vKSZ6&-cNV8^J)_;xqTfOw$G!A5(S6^4_IJMXJOB0Xefzh6 z)!%>r-RIBoE}mbE_j8Wu6)L(Vrn>HAe<^sU{n&=w^I zm({z%c>>UOWYq)PO@6d|DP zFR(}#)NMmX?(yR}7T0@w=Je+6Tigidh7R17a`^yB>OCL>34d@|+~c3M{5F?*Uo`@w zO!koOdNXr{j8b!l_q0do|D*Xx15AR4DR$t18A;={&5*_2@4+8?S zk~`3OfZ>B881zJ*&le`cF>ik0d(rLW%>3X02oNnu`ZE82jyGR^^Q-^GZ~uq?Z20p%QSN1|Znrld{oL37(eM1qmp=aT_y6qsKmF0) z*Z$c+PWfy-Cnh?tp3GSwEI-E;He_Vq0^@$%votVtkPt58gIb=b;Z82O952W2np+ms zXV>qvf&2asrKt6Oje^>$W#utH>H$35a}^)5t{p4N9?@WWV^JfXpHu-Vq>%3X8COcS z17JVtXhR0nXgq;(@_Dd*)XD3h*=aKt-)ihG52>SIAeT@%#5)l3&xoKrn@+D)%H=P| zmEqCpa4*?&g#2i&UsvcaYo{6L!$f8?*eC2PyuhaBPSBRa;K7K`(TuI&Uf?v@tLnjh z1v19&)C{L{QzaB!X$nsG)5mYW`!D{Z-~R7@|NXCi^!!;Yr||OD^XA0f`tK%dYFYh@ zhq(FecDu#x>FMpy{l>Sy{+)0B^&kJ$PyXhImz%hP1wJ6=L;sC^ZCZ67dQ^t=qSUeP zG%e^naxuXp&Kg+TY+d&^dX$xRleB1d5$$60?sL$VN@E>NS`$E#QIpPkReU#N7Irb;LiZvIBw49e@XJ z;ESA(7Fc%K(TteWBz?c`dvDEp?K&^T+bF~)))hZ+2Orv+l57wS2fj{{g|DJqMa}wf z@WS4F{Qh_U```by|LULG)002fk`Y|Po50w8LR>GryDbEBj}YD5Z_jr7=-Xfa#xH&A zFaPk*Kl!U4W$3OSMB^u$$`;3Ykljtk+%aFn1{2?S1oqbz5gTcQT0;Ze1x{7Y0n30@ z)z=gxPb3lrsr(b;c`9V1%Uvy2<;qTZDh<=RW%Q7rypq|IZ(P`h%a`y!auY8=~Q)P#REB^&Yt2 z8Npt#l=v{LZbQrC#|;zwyA?xsHFoGgE%(v8V1iiV2{Ym=>m$cJn6>at?^#jbr{UBk z{%!6AzoPc`m2k}bT=-k44iS?p*C{+e)puJzw!9y`dH;hCJeuEFjkF-YE?ky4xVcE2 zSda&DIM^N9xVS6d1xD`$|$V)V23@V4xt5V?p9*Kums65L;hix`ro75gh&%&%DnDLb$jg06dySHeu}HnvyXQ_u#g$F?CV7VD>09Jy)Fq9kkxN1D z$r|r^9Na*-GyyZkE{+s#B;;5ji+KeX63N+Ido~XB;|YuWDdSW^*b3Doi8V{7#n2u1 zE>LcE><$|`B`~TBFfBNXdb+^WSJ)nYJ6b??W;iO284g0k0*zWj*)JDtM=HSIxg zG#V}3g651N(X16+G2np{{cgSBAa~Aownq`K5~q^_%T9?lPQrl3PKbfaTiJN3kY(I6 zk`VTD${%;VPZA_~hey{FT{NK}$yLTpElcpb5-;fT?fPIhxx)kw&-i`EQS_UezyHk- z|AYVGKmFFPeaAkJI)}Z6zzqSZPYTM@mwoR~J&o&(hZ%F)a?P(Ura7j%O^nk0zy%NC=R38Yu^}Zf%HkD20r!ZO%rd303f>Fqb*Ct{N4| z{UEQ1)|vM4BB~~52l;9?KKM)ax|@d7AMaTY+)S6DgFidjcOn(4?P>Ag>|Olyrg(@t zay&|XN&a;cK^vEzY+n<>Eb{Iv?%$$ysscI^=*W`V?a6ps*579m4v}%QHp`?wDua>A z*^w?1Pai}p9(9HbT?|s0L{+A2;iAkBbJsFwA~FH*cfiA0l})hnzup-YTn~%Ts1Hi{ zgV1blfuH8@L$Gjc#;@7>Fa5LM_|EVC>hovM1?FR0)>t?HTnBD!D+avgS?Rh$Blfi@ z7jy9#3-^zI;ak7)&;O}?@P<1Z#}HE!Kk4Sc0C*PR!=*ZWsXh9t9xiB>k#{VM20Iqb z6_%`RXNk!^TQloPK4a&O549usa2vGfa*)6=2`(m05`R3#H_tr{5?}xjF7-b!2qq5g z=#(27Elh%`%6z~;_buxM{T)AnMPB~7t;9B}rS1!25h`VZ3-1#(gC!FR@4Mi2J} z`mK|D5c2id8^P2~CZUmc80ooO`1-tTyBdRgsL)iFWb+9e%U`W~hr+}uLosYBl)wl+ z;1^GzM4kuirP-ZI%G1TDG^+6maui8blM^{q z7xO5ez!fA0kApvR>(|mcF*q`5>wL~nU2zbZ4TCLYjG)2X;;HZWSa4gOxBWqTzU2vy za34A#Bgmue;NwH0%wmEJq zX*g`?(x@;o;CJ%uXJ>UQf+{58U_GrC+k%fJOK4?5hQT=S7xiLhiCj~-Nxu0ux6mKy zzB>7MalcgxUV!FvK9Nv-qj1Ft)iM=h@e2|&DGbDQ^OA4thi`xHeA7L7Rt4$(o2VKu zC_CnqF;g+zx!ig8j>?n#jR}Lv-TH3`<#fOR$|{*wkmBu0BkcL-f9oH8^_PC;-ShK{ zKTu^u2jeJtP~~1{7K&Co2E_WFGu+;O^((*f`@iKMJnhak-w!-C8b6IABiPRO3%ICV zAZRj*P~htu97PFhp=@$mEZz}$)rZA#i}>VO9FD4calpw+kL!M;F#KNxNpfv1ar9#- z1ELPDHXCSkgt{DSHW6t>ISKY*8l&2T_DxMg$jA&ZE1_pSO9jcqYhtlu#Hdh0UeQ{j z75N4&6@X2yYHwhaxSx+cZoP2pp?JzLb{-1s9=hp`J5-~l8QIu_jv!+6mdvG&MA=Ip zP)mwUpZuo}<4#iqkzhS~T{v(~Pi*(@6(Z4W^A;t>nJ&NQ8AAxNcQbGS<1iJ)?)4jo zhi}7-LF)4D%kRej`S!t^U-<4nxxIaQj^}sRWiQ3sA;McO#9>Afq;fP4HEJ)7-|voD zM}vO*w}0hJKmXD3{22|-5Jk%{a)rq;PD zHBBchP#p+y>~yMKHsTYw>v(P>jR>-Y#5GrR5+UxB>vd@X32qQ6HbQGgLMx0WBWMa; zx@3_jxlPdjBx!pJCnFZ2a~k+_v*ZbdFH7b=lpMZVJZqhu61olM1I;xMM9OKw!LM+I z{iDlfI2Vpo6qIybyyADbZVNd}Db!VZGjBk3XcyM^%}_kJ&-FD{*ZfaiJGgasUq=WM88Q}dXafh+PK$W{{Q1j2^U*hd z;}?JW?z5ZSk9CWcuaXC4hMAtY9@VJ;3~-(F3>m)r2+Hc7FXrFp!XN+qSHJmdzwo0! z{QXd6R~RiE6J|r8zrbDARrCrT|wdrt41g{5U>&>uO2dRT|wX z1+yE7EdwlJK9F3*qGAq<7P0mOEK_`HvVUT)nud*Mc<@6C*=lc_3*R#CP}2yE28xV@ z&9+o*h9Jk(^a-G&q$709{#m ztKT}UuomV-wgA6!$$=Vrv`z_4>~@sURfKBs;njLT{Sd*!Y7~!HGllz+4+~R!Ed_Hs zdza|p3XVB%kp&hiY=t0mj_pW{`hTZGje=AMUU8Zgk%_y1o}c66Z-4W{Z++$Y`T1o! z5YNwR`8Hvf`yiu%$K`HYpK-kF_UYJGGBD2fqA5Fw`1Ha1KmYIkvcG@ZjIK$>(%z_a zEZQP#)xq&|&L%m16uwXZrDfelO{F_;w~TJ)d&U4A$=(7vqS}!}r}T{Cqu0J`(VRg{ zy^I`U=;l6p>kuRH1DKAkDU!3*z76&-F|uZ3sbw8W9vLzG``V{*sVd|UO4N;Da*#{j zWYE~s=7|!}F3qYTjE>GNosu52iPZqBNh;t{V|RR5fzuHMxYyydbHaPDK)G}h%IsA_73@^%oofxD4+i)4;HX&eyGGrFG1W;hK8$(A zJ~pdupV{#BfAX{b-tEQm8=&>7iV$Zo6+&xJGmK43qDqM0MST4q|Ll8T`QWopev++j zkqky}S##c=7MvZ@ixSPuL?ri`NXpEPW;U)bBO-_{IWF9`lW-Zom59172Xr6$AXO;7 zU~W%z;k0TWY0066NSja(XZGeAScJ)L5t|}z*=jdrNM^JrUWcps5Hygh>I_q0Uc`XW6}-b|x&VOb(O?0ilm` z@)Ge`Td&ZrX+Jtrs5J_$O-V=`P}WR#JyGcnt6w{%TPXF0Qg+QyuGR~yEP#3lpm*r% zy-js>7SNZ@%`OZCy-Z6+p0c>dAvoE_Lq-Z9u+2*4Y`it*KBHAl(~) zP=cw$i>dw8qf@?Tr->)B2=ZZZw@%tA1KM37ZCEipvEIGf!PQ zUFT5g7t^EI5d|i4PxrT_)sD6Y;y5OZX?Ra$n7#Mqw_o|$uifu;zm;iQm>AMOwun3% zX3}22knM*6rF*;Yq_}y%S@`+g^V0|Kf9dOA`9b`-$8F}!wX6{O&0)H6jsDFbU##oYH08;gmN_z^+djcy^ z5N+!y;xj$dqwW75+7VAMI9Y8n>~KS)YdzVW zEwg%U6BS~4SgU`ikabL_ABCdv-&-6fZw-grZKzuw1AyT!vU4o;E#=HFI@@!!)!c4= zXMcDR{+@#!xXvF}1nmQ+e2i?wFq_ex@d`)h)Q2S8qckeprs) zGdNsxB&wWp1c=fi<+$UT!B~`>Z;Kh;^vO*V3m>^?fNocm+{s22e(9cO;C`tkpLa*0v}TN5yZU!_3La{q>vDg=>HYr!HlMy(er41H067z7HRV+%n}|b#ivwhjp_s z>qjXC)1BBSq)`l64|C_Jn{#+YO(Nd_vL-ngupTX#1oeH-^dv+gNllsen?o0pPd|Xk z(y>$Sf}~-O*++vyP9kJBEGj<)ao+mtZ$5bMy|?e*{PxCgx0_?WfU6}k+s8X-wVr`4 zK1Caw^rNH#XgujDcT1kbQt{}81W%y2u)4B0Xe9)+#p=*sH#JX6p#&n1lKndQR`ou zg>1^+-A71DKu7zT$gcPOWN$By?f4dY{U<@C)5=66AY!&>+q-XK1c0yl>1f75lxphe zD=APric!hVbH$`X4FmCbKo>IM5R!|%c4THE0$cZ@IhDW@`0G|SsaoslNXlAvCg!w} zWRGF80?{~V_-t#>< z4$wRYJX|B6(OIWXrsODs^W@t7{@{rF?UMfH_8eCLs3ES#Niat)6CLW7nv!5e7ROnM z)CN1~y#_UMQ9Tfw*N8Zip4keadX!X!p%!ufPOf1X+2~k?_((_Q-6uW}#+t}$$&^d? z2j>z?Hh9@U!4*%2~;!eg9E<)-%K zB5Kf|E)jag-Oy*%;uUudh7UdjvG4rIU|;jj4R{^(=Ml^@#A|m7IUA$dA+AQFL)v{7 z&SD122QXYOj$gk7M3^rh&;vNTte=DQMG7sq41L>xXKmskW0SSmzFezWX9$;{K zD7cOsFoTId8()4UkX*MD>04vTm8?$ghUN7XxWz{ufXbgAaG25KJj1}>d=E^yCG&TK#p-Lu}kSP2jf$h$Nq z@LK7G&T5$X4L8mRlJo60y_v)}?<6ojdvtRaS#^j)J4@F9I|`aed~yMVD10^K&MAM? z060MSB4CGflZ|a{4w_?fJu{TH`mV9NzB_?dHwd0r9F=SX9YQ7{An)Hs^N;@qgxUrR zHoslMBUuEvTQUEp2*rV#-khMGQ!)9O_I>QU@`(8zT-1s>Lo9zjnC?6SWVIOFn>9%c z;1RLw)UFlabvpmeHfv*L40&!o!7UDT?y6x}YXySpe5x%1X#Epf+Pmk!|GS@5Li_8k z`tU~>P629R6SmTFkdAf*%LdQ@)|$Yq{3{qRz}AV8_dEol_|)JyuMsp%R>8UZD6UZ^ zY?GJEPNxjGVb^|(k+8@`1tACT2T264d~&o@In=XXUm#z{dnH!JWRGPNoX{>~8Q(qq zqU2xeS1R6^Jk3SNg2ibxs(OjFY*!UEG$YV6q5&7TGfAZ-kKlsG$7Gz`~LIGxNJ_-<; z2!LUh#|-S$j({@>Z)R`&_U85`-o5+ehd;j0KPViSGY=TH!eeYjUs{)aY6BjtEX|!=N9fjPCi0MQ#K;)yuwR zU{wFxyEC0&ID}^@H&h+tKoffu30}A4pRz{#SPt#OMg9_(@sJpFP3l)o0RCQ%hvLV6n+euPdtfYK1g3EBm!=E6HlTLMpozD zr=S1uFTd~Z&v1GsfH^lvr?I^bf%}{>(Kn{FXWkf5AmO?a@+Y5s`sv^O;F=eh_KiLJv7djeKL?w`C7)J3m(n?|DRB>a z8_SE@K3ZL|r9+9SZ8kDs*k>*zj!cE~=@l_$mM{ziZeUYwDWte$y2>pHJIvxcD0xyU zpnVB&G#j~@E7@t0=FxIBNtSNFF9r3Zky2|RnPKP?S}t6n|L19itPbEz-la{UrE4UMF%At(i^E7}H+rD61A=lKaMu@V z249sE!q)UCvO*cHiFCKa1ZK(OdT9?(rM73m)u#ZC5V`9(=(AdS|&rg zAP*NO9*v7bXQpbyh5`>ZQ3n0&cv7zA2FL0pfZs?~;QuqN-lAqoNg$(k>gLUFfBOf2 z{`rqS-EKQ7WO|w8JQ-+v(mK_r!g`KjJu}NQCijbI$X{B%*_C!`fAt4{`tFm@{dOy$ zd1BtcW7pnEw{>Kkj$ai$0yEl9+iK(LB0m9tx~S-+ux{wAy4bYV=s|ev^|9!Bya&#v zU=BoKrsfsm=!-)Jc?U*2x%i+Xj@8U(ra9SkJF9%fjOi#jhqr9G_Ue$d3-xqIm&O1+mNdy5Gl1N(QUvF5;K4f`qpVVOvJRk}XTJWJzObB+ZOG zBaP;EG;?Oo`S$MAx8Jq8pQpRu{T=zWHD`bCWvyPldUZdmyVqK{ou)rgy?x`QFMRkj zcDmDX4qd7sW?-RFU8^jaR+@G_BuSd3d$AP4Hd-5N znFVR4*cI7Wfc6S?p-qhv0H4GpHcaZ*f<esYwpUh4q@a4DfoHLBu+5kjBujR9(hwEnv@}DdP9l_aFK*YS!d95r0aFB7a;O2o zNyaHAO;Dgnv8_D|GQp%9PE>>vy=tM;+#OkQzJChGdZfmX1md?9<~KCKMYunt;Z~+PtY}S5Tu~K#yC1FYcH}l*_-WNeCcz)`rEr_ zZXVdZkDR?bQp-o}wyca=lOndp_9L(Yyip$txrp~R^}@40ni*xqdAb0m1XUoJk7=9igu#vlLmV{jZr+3!e(&ku z{pC-d*hV*J|L+sAQJy7wydIvu;K7?qynJIY2^|u@Xn)z-mp}Q)@BRAk%HH<-y;!n+NA9Rce2UdhK{yR|PMesmr;(#snin?Y-x~#bD=F92QFxZllpEaKk zxEB6hBh3;!MAEcuu}yo~?LYr3pZLnBA3eFSJ*4C-0K3T*t^A#q3D%sdfp;lp4z9(P zgroh7&%N-;fAOKyXI?yzur~}+tZ+M<#nnga_f|fKPgG%lmTC?xTW)e$FaTZ}?CL)L zD@waTN$DM?-z0;Egi#DNKxxtt|Lpc`NUp!N=)aogT4v=uyAkQOSq@X7N&QMsPOck`{u2iU%&bKH-EET+Dx4? z%~^me#R@bBjp3D9kd4^@1!&Xy zrXolOSf>G7(i9p>0Tdf5g4`6bth>^0C2gA`nDoxbNd>*_(Y@u@!2nbnLBF)}s18YG z)xFwhp^h;5%tfyQh3do$yrig~!aY@lPdIqHG8W6oHT7^w5~NV?7@RBwYlPRmOtN}Z zTq`%4?{O5DN0(MD5AxDrnLZ~sSDtUNcBo3Z;QYysyeSuSN_Z2+N8MDw)e8cAIm}}A z^6Xch)(hKLe*5cpo5A7@guDsXdoOs;84_*FDlhfe{#-1-7u%>j{;@B7?Be$lST|ji_3rT=Q+ILZ~KIjKcx%a6El5)mBf>Jc&i4FJf0l@^xr!QQs}_Y!&SH z(n5cyK)Qly-JkBh`uQ*4_42C^{J~etVSmsiCa~<&fOf=9rF%4{ybYTC4IjTM+q0jW z{Z~Kp*oXg@fAjn!Pj7V2y%ak_v~qvt0##}vbyVI#34UmYt&F(k>c(zw6Pho>`J&ns zmrV0ACY0q9hF*Dj?N5F*BcUn@Y>_kDysqlnYB$woU!kODl{LmrYD<)+p_iN2RulJE zHU@?lx(NYNH>*v$8@eE=+^O&n2EdZ_1kS-g09zjhZmr3psO6Wq-b7RcWiE4CHNR+vwZ+Qz{gi(cs zOLh0s?I#|3^y+=r?*FD&t8O;3Y!t2GfwAZZ9B%U#JlvvBNglMUS90Lk!21v&KQDag+Mf|kP{1nE?91ipd$d6iV zyqBFQnk=5t7(tZa@g)=yOk4DBR&FK>OkstR{ITHMIPRhme0V_*)(GZj>Jgh zayootKssoDXk#pV(D(okxxAQ@Bi0deD*V6S*-p1OdGbWu+zJV{J>O1*G+43LgNk8P_`cR zt5OBjqh_dv@@K3@R!8^bX9o3o{^tHruz|Hs0Ff(hHXMM*l)-xBVn};tb~`}VJ3Xva zsZ%u(5F;p`I>5c^wg!C0spdxe&oEfm(n9n!0B*aITP@BUhzga=X|FwqIMH-MOW^j5 zD>V7(5{E5RL(?=rj$vR(oG19pnjOo^E<~dQsC)QlBOA8~mc22<0&KQ=>xCCS`M!_-#y|e$7ryY6 z;?%U$HThfv{%sTjYOcYVJP**0y?lSRBoF$eca;CNT=3j#i|{+;F?1FwfG>KiS)p4d z+EB0?noBj)I~m)FI~RNTi7N1Oj)2=KM}sTBU~lmEQi`AS^ibfqeIix81U7tfeb{c zRHy^0(ZZ>C{9Vf4gmKlBSLLRV_*g9&ntF2X@@NllUpNKM3FCt&^ zfG>h^1d8@^#PXG9d#$kHycS(9X$BBsKnxN1@mTY0HRU-GT3*f3Cbf0~CgdTEv66bO zZM=eFPxqM>syyP+Wjm>@Ja1&X4s>>r87mXRDUl?tuh=HHd`Z9IAWf_&)E44mkZEH@ zVvy&0q__g#sfeT+bYeP=Kt$1lw_1-=$pn(u7y%f4ui=A%b$chNRuuBR+QE#&pnhvADQT= zCxu?zRitvoAKw z|76J$@;d@_+ow*FN!OeQ7UUVs#nQ(6os(*P!A`i?l8* z2jE)jsv_d$6wWEHbwjyeK%Pn`Vv2jzl2*ha#JLZq?EFGXXbfk$L9s5=r_LwT`XrDo zgxSH#urhU6Ra5PB2ScA(FZY5hSW6nbQ=6^0i6W#iEf%%6D5d(Roy`F5j=H37-NU3r z5E#0Ow@TxLQzZlRh$Px*Qr#HWfo423*%wrFK=rgaqa{oKu`i`CKZ`*EvHow>`|G$yLBtSNY6=i!dySzR5 z16N;h@rm8f|9AiU7k>3qr#EitUJfasLMI)e2v)xbsKO-VMjc(`ARJXv1Egd^I~Noq zcC9K*g}v=*v^i7^P7-Y~Nm%JF$6m&i!~9WGU*;cREz^;t&gLO?R-GSMU)|4W zC9C01L2h|7xz1O=#WPAKCTnWMjT?;~aE&fdeuxilD?z4_=XUUQ&_W!@cKf}ZBAhl$(;A7e1N+G#JM@(8qeeH8Pt%amQHanZdBV8DwgOnzL^IkB@-LSN2$(F zL|Em3(1XFdek)5HjFd59O)pqyyd@2L=7DG2=kruTEv(_4WCm%;h|R&~PoYSj=p*@w zEs{4pgxBw~$t)Y&GFomP+y3O6QGGH3*-WO(w+@Z%Rz$QILxtK_3R=SWZ}l;)k`Z&*}5ZQCS=&3aDl!hUnlg?nCe_x0`d?UF&iv;AK7 zhushdSUlbB_Pd>$T-a~L_PgEgQ1XUi8j9^u82QCRWcbtYhbKe~Tsjr5+s?%$)%r1j zm4%&iE}nLaLZ&%5pCieVM8aJ!P3HDtby~unJ~liEJ_JHpGtf7gPQ#HqIzUcnWh{IuJZrF3p46)wFeu-eN?2)KEFfHNC{nh1E_ZHUA=jj- zvob0rjKwfs{R#l2F?t$12JH`f_UsQkcW#yxpsj9FrF+?Xi1c7M_WS+RNLnpaXOFPI;Sr_u%1EQXdpU7wAlv+}%C$S2i%6q?o!yP)D2BF>#I zEMWX%It&0Yq^+!$N89L?{aUTelJZFJj3*CknrMW}*DQU2X@v!O`u=ST08Go z3bR(t#CuoI7u3&M8Ky{4nK~2!jWGu+ZjquA?jEjl-5`tb5&pmg+FY%aP-jB(qnIWG zGlLTyEzEvxl?*zD;BoF&EXU?;O)w=LtQY!R?GPMOy^isl|{}mu5@Dp=_k1fy4~*To+m%W4M!& zpaIm>4&EE8xDzP|HGZO>&B!tW8gLz>3|$wvc%n$o01ctplN=arA3*PT-o3FRKI%-A zY*rN9`u#hWp)u;`Ij>T&1P}`Jil+%uwDk54JQZC9AwoB)@}tSuCy^}N}Q@EWzl&d({b^rmkETZ4S&NJz`8&DwK*Jv z#6%6Umk8o!6BaMl=#%IuV}u5HG|jhC=TuTQ6tKJFRT~?Eh1KvfGB{Lx{@foK@G^Og zMg=^LR<{vW$zWBF3;PrC3DSZ7r40om@KV}d(jE)P9M(_NKzz3#6lC8!k!zZ&Ji?AL~GVlOn||D7T%B(OI#}?7g*yz0am74LYiYZ z5YwZ+aMY;Yk;=o&itG!wod{!qg!?0jr-1{vr20Tw2GqLp+s40D2oBK0ni9*BG#iqw zs*2R`IjJTqW@>)lyn+Yr6*;zT!HGUkK5XBz!&+R5rE& zFtlX&?-IvjmK0% zAPJC{8B+exIiP~N4>%(|WU5XD*KMthCqahcGfr!l7#2KW*i~mq|C?l+*K4@7OY@*| zGIhny&xytH!hL`>yf7+91n;ZW2aKXKx)cnNff*0Jg{qF(uH`Afsi8cYB2 zben|8W=Kq&$BQwpaO;TuaY$qZbzsg|!YkebB%T~)cZO4yOFkL99g2anG=!t49D|xg zUfcXO#Zz5HLPIlOu!1Rq%RUbK{T`BNA#!WdXepDihuq=Z_gHAAxd%vxD*1m>C(-s+ z<(Y)Vuwc12LKAw@u(BBoCHA`qQUu|$5Z%&h<&}&46Xj{r8wApQFM;xXlQxy+<*vRW z9t!!t6<$nA8TOSH%o*q0!kC8j$e`&y8ZIkQYg(G!jl zX~-;C2A2sqeb}UvIXKA%ldCrixRGF-HJs`BRpA7&X4xoW`sxquJi?1kI~=2+sTK@l z6K24TD;g7GF>q7J1qd-&X*X5hJ!B+@HyZfFEn03`mNQU~DWeWy$NWZfOS=@=+;>lBl4NTy(H zQs6YVSv6vlrjv5QTyA`uc-Od-jp1ZAt3|zNq8D@04{cVm9!&(C4Yl^wl%}srD9DADL!I~A&c)vEOMh`wU_F4dvb8=n`~^Hfjz%9qdgB~ zt{d8HmN@|B3s@u2&Y&rGCXOLct7*$Imn)@15ZPfP*w(ZQ#8mtS=2P-FXqdG_A1eSO z6{dG!ROrWlaiTZ0D%JIo`Xt^#W?{&5ltW%-Mp~eYG}7jD zthIrTM&&M&20+V7DA=MYX=m_vmrL-PUjq%|_9 z()YG<xagay>`YnEk$c!d%nt#t*n z98Fh#XOb0~X#NWWV~#Fz?5t$@&uUCLGA%GF^?CeXt&U<|JEcSHe)E-kdv7u26(Q z7Vbqj#;--(gvUjES$${ihceKDoj7gERDl8F zo)llcnQZaog1Zsu)D5ZcN=IFKg}htP6$$mE40?i28FUew%4Jyr5(-mJ5BDz$mTlBW zmw6`?ZbjclN!OU~o6ydOP7ylihz}XN6Ht`W^7Zq>*8=84aaLQI&*IDg8(G3hOY*$zGDAOpDcWk2#vODFdD; zp3Kh7ppK+oVL{cFR|^Rd!8W>`6u7gW3(^RMS5@npPL!RjBtM~Yo%f1u;)Z5H^(fj= zge4r{Oq<`Cb}(fbcD4r01mKAe&w-G2Ic)yyuZ?8iReM#C;|79tlq4mq^iRz_WA_Kt z<>?B;$~g+NB|){*py{vx22Y0TXsu~kZw*lS*3`aUk4t@6ltN8u;lGqu{?2I>WO}&xD)VZf6P@pOjydttyt>LGPy*RjVFtPt!A*JR*%~7}gTYqv zD`g7175)NiEo)z#FpbsHtOjc>B9dW4>=6UKh&D+W;Hd%i?l3h0MXiseXz^?wKu5Wv9=o#F?K z=;xO#mpMeerbuU~qr~DA=3d0R#=b9|p<1%4+w+j-v!0&8Zst^+B$ir%Mu-BC7QJZ& z#xVXZvz2NDsMCTt&Bbvk>C;rE78t?zrWJ0!$C7d}3PcxcXq`QDWrKg(Iz(P-u!u?c z!xL*6vIv2J*lMh6z$I-iEgTa;uFX792^&y5iFmN4 z5IVr9HAJI}>wN{SC?#nYhHijOYpO|bZ0TR%WT0Od1u;QSOpZ# zIF}5?+i}(1v@B@E^Uief(;CQ4HKBuJhI89yS)!n6O*uzil7yJgB@F75;Akj~GUmd| z7t4YzHyOE+dj}ks9%2EjLR!%b^>eA6ZQ(*M{eb2eYHb<|tYcan1oG2WQ3x8Ta9FO_Ev%I{Pf*fx?h8;sCAwsG^1)>OPUg*aQz? zvp5468l>K=jH}XHBX;V0Js9AkoE?%cjP{Qo(;pR^>3IRwO^wa;3~X2+6IyE78v1Ms z(6m84oXEWO{iKNnC{4_o+?*qz24>$@6o_)$B8O=rHeK->c*AYlJs4vK9Ii%7895Xo zvu2qQWKga~lKgdrqI1RGaEp8Z&SB88(?(^frS@yZANioVSk*}5L!qTQ)kT>?#n0@p z#7c^odMUAEL0BNeJd8PVyekciu{IT>l|h}l6s?BwXtH?N-!?YIokU@45%?>KCj*S6 zL#2u0TE`R6>O--k;fTG;tAtf|P#brFog-HER`{vauGH9bMB!T)*Hi8&Oh&Te zTBj%#E6Am5SoE7@U|V!jw8(WIN^Zi4Rf?I*rb&Y&TX;l*dA3>GX!IVERA0|hXRSB_ zg~)!3jB@g8*-&LEvxY{v;jS`W8?hP2^=D;xR^bTo$KWv(Wv#ai{&t&e>8(U#K?qS=f>nb3qI8H&c8F_& zTdSBdww>p+aZm$w@&fXB)X3!~77o={<9Rw>3ZIXlbrUm#I<VlwAgx=w!WI z#I=vgHHYDFt1DL$X-W?1u9tAl8Y&+*zs)x&u1%0`&M?Siu3I6zWn==3ghNyQ$;3x9 zWJsZ+-`8CUa0@N$qPjlQW%i#p1W(A$bF_gm1db zCo4+w&Agi&Qv?a=G^`?sOPev-ZfRpt`-L{|2^`hRk?XD(Fi*X^&{yh##ubbPO(43o zl%=)odKIElsIR=&t-Fz#{}+Ne7J`Ap$EbWVDyF{WzM;+6YJxx+Ce>*vfln=bib;H$ zaI44C*y?8tpv(-Ll8t-+krC5`a{R+3svScY4VDsM4V|o6J1Q%kVeGPGqUm!nhu(<4nU&LI$b4-1L+U3Kd8-(-)j=Hco5*a(!~=ojY&(lp!ZdxLc8 zRU}yjHYSYT(>J&f+%!YsscP=>KtN@0nId^ANhRhwu+;oTg8xR(h!%JBmu zLdQEkHO=;q=tPrX=|z#8yvYnFO%CvEfG%o`&PTDEifEd{&L)wrG)sBqymET(SR;|k zFVwI6jb?K;*0J;1sIgY#=C%CP>hF7;f&lK_KCu& zAevNvo@Mww9}t95W5|2n(ZHS=cxu6yLL>1g(3awqg9s1MfPVP!Fr$-m@IVlv=s-h8 zTkYoD=qwQkJ!zY$`VNX?GrU>XXf+DVD@>R^4XREG=QXrRW7NX1JDc?HF3K@TxZr97DpN;?&6 z+LEWy{yB6HLZus8m!PzsrO}bc(lD?>6$080bXQGF!i)+6oh1#;DkeTzHl?WWP{Yo* zV*~DRD{0-K6tJqo_b~3T8;yOAiFtwUB?|N}j7K<6bLa1%`Q_nhnIHlE4v1hyMBpLT|8Tk?A+!u;gCqpk37w>ReI7&R zKSo1gvNZrrd6u`rEx|ofjODWRqH@xCUv>`>8;xMOEd@mZ+nGc|T`p1?iY3x3tRRi| z3|d(a$R!?;zuFMKMicm8qdbDudnNBchlS7$oNwpe(Z-lcQh9Jl5wW@NUu)PlV}@4V z1eGx*(o%Coa6^u+(xiZvx66iL(+_B%GgqQf=l5d!0vHo*O3l06W(!#I#+nn$7j0pj zXNHlSNeFFLR;PMAoI!6l3~@}QP}<0fJ5hR6oWI3*a@fXL5TQkG_Ob0+3xqALB&CZR-{Pdo;gm%p- z_JOTTv|tf&$a1Dya+a{rx6rMb5Yz@^PXJ?zE%>~=1TH2kmDh%k@7H{HLGjho zTmDj)BhbCM@;DNLU1?5F0nB@}Pza1w9^)m%vs|0`p=H(V$$#AY82WIO!;G-DX#_4c zlD)hiHJZ0_>D)I&sn~J)o;F5%zN?9nk<)lY9jqzrrXux3Ngwvg_!rw9Y?>1YXNA?u zG8`f9A_Vh|EW$NuQqyA4a+vkV0-f$lrCN7OPIM+PDEZ(5GwkTK@P>JZ5(3js8LjW} z&v?>Ou?jwuBDz9oN@FN15thaT{%10S%U(-79m0$7&*gPUh^|;I^Ojf)sD=l@MQuYr zU3b8;xD_-EgElKVEe*C>uw+ULdvN}2Y48HW2bw*i?Y%hFd~{-NHfV;za08V%iCfLRLB`6G6b!fec+WC^@5uhmg+V!lvdSGzKsJcz^32mY=D!RyQ-oR zwR)auEfd~%^~Z*J4yqA~aRSQpvd@Q;pBE1{ zT(H}SVKigDk5jlcy5p7Zn3Fax&h6n99BS$AEmRc{`%#wCnT!|jZz#u7X zq>Q8oX~U5WdndPp$tXLMkSRFfX4f29;Wa84<6IZtNF1nc(cE@dhy?22LG!!Ja~2+}wmath^;_gExxNl^+FQYbq7BwH>!5 zc`gkF;z>)ITiB4S)?{iFNQTQbv&htpd8pBpZLY>YO5NQ+dn2}>If}90#C^58N34+6 zXPYv-U8k#4cwyQvMKIK>fT5b@}hTowGbLuj}=}lcfwTsz`WZ}X4xvZ%q%bB ztucH{0u9klU1?{dJALztQ7LJ@8BR2%W<%B_7L38QEKrmoC&5Ra*eGFJq}$Szs0r@p z@m$n2jx@!UIGRi#uH-g6sL_!LRAEJk$M3n0jo2_$5N)TH;3 zidJH;GW&&fX6mp}HYvhH8o%2+(ulUHZE*Uns+-}5ST>PQnGv}*O)aYMp{I6R3Vnn{3WCV0lD>vH7?@DQ=fZyOZ0|luc_EY-6*sx+ljZ*GlVS zG?{WfL8A(U^=m2}*&Rntv0}^ep<=L09HMg1dFzd(mX!tFwTO4(Vk1pfKbw|jP8l|d z1C7`7h*_Uu3*c`GSXg@Zh;zT2wV|kMPfefqFo4+Hq?zg}RTmB#=WT0eRcj}*4XE?Bo3RTwKndV@D$%&IxG8XQcPXg+703?&F{;dc$iKHHI{C zMKaK46xLMMdfueoG2*=s!Sm$Q-#_o6^1YVXIGRqtViZN}2K4AkW>GTX=*{qj^NBXv zRk%ejig_bN+J?v}R*%vPX|)v`B+{b@UQh!zKrt_qPf&!*4PJ&4hTKr7!eG-YO$Alk zuE1VIAxpg_q0RCy1TIzBxb1W^L7>Dt{8NJi?(c@Fm0~Gcl2D*4omA~blF}& z!Ns3;m$Xih#!utOz#NKb-z;shBJQWorxyX zgj>#>@&N*wsV+m7v<}a!{J>sabE>3TpzG2Z9CnnigMF4)mC_(6$D>d71j@ABk`8LWHjNDt=Kh4y6B^%xo}-7+o;U#-uPR38vyDq1Vv0vQc5u|IHNXU2Pc>21 z@;g}oz@C*3c6C<U!9c$fLe2dz!79a;%iF zmQ5E>z;+eiF4r;Zl}LN7oz#$mBJ)>g!Y}^(YmTj@gcGcX7-S-Q0vwk`@bbO1LfB zW`qv3U;EPhQP+oR*7^1GtP#m9@Mno)M^FmHwyE8X+Hd=N$|vuqZ6z3`MGcjrc~`ge zW~teT9|0%paBGt+yS6C=gF{3I{+zm(wqBP!Ts26;qfFh`6KkAOMiG2JN;^UJ0o4sm zZXpL@xKZJ)u3M;zes|8gQW^fRs-sHV zEp+*M;T3$)DhAhnOJXR870gfCFq|}6n&29$EvPh3snT$JA;?rIJg4CcHQ`XNqE2pF z&43&SLc<#?e*)75`ydTW*On`+_gSmuhg!n5E|zEnH8Q1JiV}Guq!pve;q&MRuuHBH za#F}XwuC6Gb!x~O^MY4yT^gfkPDA1Q_*d~TPDK4VJt$_io6~M3a6}? zGNHymxR970guuO^BL<;W8889wfsEr5lhsit140s3|!}n<_U3AWs9`T*PLlVdhtiB zD62DzREOr@8TaU#p|X{7K<`yF znV_j0@^Ho`gkU0UIX4uGayLYLwPn5%1CAnfx|9wywIt8WzvvkS>~9S)z)Oo(42saG zAF%-R+9*|(_GnD6cPf#K%1h(SizWY!N;;3FLI}8D9=ejkG&sec!`{^xzd_N1TW z6ozSHQr9o$amY~^$VaEWj<~1{Vj&iD1}tvXh7_G!^@#8HXk(=%ZuqkT^BBV!>+{l} zyfbxtlU2SZ)6?XASaoIKf<~jJ4+X4|u`zpN%)aJgG1^2+XYg>1pJ^y!hOjY8vQSk= zcDQRc{LyCoW-iabqmL=#67qF^P-6`+c7SA!-Y=@r{UVml57Nxwc?QpLR=lXTIq_56 zYLtA88!unb_ehzjv3z_7*twL2BKV4XtIFwKkNYP zWt8fziyV#jzwqxZ_I@JCdHPS3fM3YQ=kC**f_Oy>}iE2VNH2xnhzn>L^wqtgG0sZs0c=4 zy$D;2xyv)>+VPRMKoR<`%RDGylVN%c58pkc@tkBcm9iYCL4q~YZ7vQT9LDbOjNgoZ zYc-OqqSHb2$lXFUW6gGvGOfCxvMzF_q==27QYNHqPSL%UCLz)w`SokQSv42F0?b}1 zSS#YR+8$Sh;GCY|p>z{{o9tIHqU%IjLr_C!n^>u9(zREcr&|7=y*q%m zN;hk<@Xf?WhL0$pxAc2ey@%^Wqz-;{8vvi8X#afpJ-j6`+Epm#(oXa+qI`4m1JHN=G@iqgyZs!UB2VV5ee1-&x`3c!PVU zixya7w2{`we#w$-+ycn?R@yP2QMj!+Eu+$)}5qsg=Hy&ShCJ;>1CMajwdcjee_b6YGfIs zC#hD`3Q&G6OsQpbz#sB$O>Qqkl2MS{LixH1Z5S3lB$Ntv`cd)l_Eb`*z3D-7(f#Q_rwy5==D>%k`!vSu8J(y?~nFPv<{5ewj(y6QrA7 zkyTCxE!SmnF2X2QSC&kh$I){t^i6ruvr-y&w6*@*ZS=zKCXD)lHW zq|d~_W-r+Sj0jwJtXO@?Q8%KPr}Jg?w56g17y2vw$IObV(2^=jaYEm_bqcV1xdD%w zb3lnX$SO@Z&G}q&46%2;dVg~rU}3SuBeT-B#;u&R!ci6HXdNx0re;J*n!hXsR^8PM zI{>Ci)Brh!8KfVf{H{T>6e?#>w~{!*S%unFV?@j*8{XW+qMRf_5(mHVRYAjQ8F!&~ ztc>M^I#!izE;9a6h)H-wTGX6k2{|Ub(t57h`gbZq0+9(Hg_1d`ZG?0btVr7hEAFAj zPlO|Og(rU5N51hB`Z&5+cqCrGPg;^r;Q4nQ1JI2MFNy9Cvf!c%(O!FwwjKJCpcI^ zmx(dXPTJ_h9CT1;Nw+SH%EO8@dz!4w7pT^O!i#L)nHj&~Wg6a*pn}>!rM9HGvmgQ2#G*`Ws`us)WEv(2RuP~k<=Y}&GIWiI zMDhmGCrLyse}wL|8r9Tf%<^W=8n6QU z6Eq-tnWR6|ISkC1@)y~g?b!jT>BW56FaS165U}gioP1~`o;hm-D*fxm)NAFBCdJEd zFXg)#rzt|pGg0TNYHOE`)i(f&w$>Qq$k58a_>n{~mj?!gn2R>N;X+ecl>7}`+I(Yu zDGYIhdUdt*ygLr6O+&8O95d_3L^)DF=RN&OJT5N zKY+0&T40xk);>ZpX&rOr5*P+8NMlXd-N-$0-25cMW6nyx$C!ICT2)X=oLEq>i}4_; z7s007Wo5%l2*`#U0rK*(Lr~7Wy@RA9Wt7j|IO`Q z0a+%^KG?PFu^pIAg~i1t%|gZ45y?tGHc!~%86}2lS(yVCfT6w{fGbAIv*#%kz9I)*;#|9Ww z(co54v-}FS7MCy?*#NEP9g1UVJQ+P$&Jq@g2*oDTRc;aDP4kjd*&3kLx#}{#B9Lt| zBsTXnH4pOt;?6NDqOfuih%+TSnLk2gJ7Ii3yb;U#$ZZdNK)ZsRNzV5AiAWw<1@~HN zrvKDhgG{z7Ro`4GmCryC0-kXk(WnRW%+XY!R1Y_`j~hFUwkmIc#Ld?(t`%>agxfWq z@L!2dvL<9xIs!FzAY;DU@mCA^cpuVy+waI*DE%JLUcAo`V9MTqka9H3J6%T>x}dtHa`|^kVHi1-cXoQ!FQYtJ=$O3~0(Ib{0Om;Aq}Ey&2!8b-1iGSE7c;mDOW+c?7~Z92WoIJTuB(!%DgQgi_kxTG1X=THV~!<- zxp}|*zyuMaw2FSFme30SYvgHe^^}J(Y!I}pbPGw*e)}L7-*lfFSn=(-hr+baQa`5= zQUX#kvlvowX&kfalKi_$yLA=abPGDkshWXK*= zBf-Sxa-@r9$t3F%)lSezqf_FV&1nJk&iY(r5lD6Qn~!yg-m$!ErY-wrEgD5P3JhoF zb=5}amOTuja%nOClzLtj)>SceeoCaQFSq%cVZRIFjz4p5&)S=7m-&CwBDazQ#jTwW z0|Q%n%G%68h#dN0F;KIh2Q80IA|RH?$r?lpABUR}Ez-Tp92{Nc-Rzxhl#i6GPOz&u zh=DoP2+hk^0#BGgUF5^vh`@LV6U9cKlx<(Va21+@XDAJ&@Ul-;tC&;iz)>&BN@L4q zvkLHXSgO7IszmcFNW+kKIpCCwhlRE)wB@zcMoh}Ao!sEC2hCv{Dx?iH$EO@qB6~&` zj6pY0NOt!!Ry4x`2I~fo8hCM7uC!?Y`tb>^EL)#wFdh2O$BXUfwl!umkE5}l+>oMI zhQGM1mW6D%;C`jxNsx(SDqc_ZCwz*A2{|7+Ccqg9|X30@iJebiKBmEiNLx{Ngjx%d~eUP!SnP@ zOw@h&`=lB?fvGDq$=^V`9jQuna7bf4>Kia_`wWnTXFY9ZMK59B)bx>A{vm9#2GX;` zB(WIu{aPR*4hr!VV3-NbkFes#xf&co9eyy4jjt83 zMUZ5eDi~{pC$_7RLhC$i&LcxbSzt~Zkes~N>89CCGUMEr4J=GGwE8?wg-M#X_lE*d z_2u1w99xZ2jU1^8yH|LsfuA01tn*?4R-%oW7O;e!$p}D(5m=5d4vp>oZJ}u90S6vd zn5{T0fLqQ-pk#@nVM^{1953nXGvD)04S970d?Mtrp`C0kJ@Uwf zn>_c%&f9{-Ky%b{M!AuzEjDZf7;GQBH9sm~N6!S#xm2wCWWD8K536AWnx;tR$IV=& zuCK@hk|cJ+Q&6nV_3~uqhWoFcNnre%1)ywwCfa@0zY?_Y-Xav}?7leKEbQH!2OoNQ zQL!79*pK(n^!?m8+DlN?&EbWO^7b<$n_!su#jUP_z;eFaW=Of}XqTs{hk3l`kFbKQ zTwr-uUsHp{iJ+rCjNBpfaY*O7KS6GXr@Rr8SPT^uF z7SBIvoBXM@%Gt6?TdZBAe~i3v4AcPzY+EjtBDk5v6X%~G-zVb$9y(3f14RbcgTn>>rjska$IhlHs*>L~FFVs8nEjlkb(Aqa@8nC7a+R zPAK*T6Zp&?o|EAC82{a%9z6I!#P<8cb`CJ}w>c45c1o~$P~t()scyEq+0PSHB9yU* zV+Opi?D!A8D&=;9YSB45W+^`9v|zWAOpR^bpmU~4!#yu-zd!8#a0meG?V!TPkph$t z>Wkqlx@+=oe^$(Hx8LvVp#M!Z%z^jBG@usJZIOBBTrz?6$)Eu>w7;g5+(sPdAG6R= z1?oy@AaQ#j`eDU~TA#c4E&SXZUfG+Uo_4r&vxvlIb7uTDV+f7CF(7+VwT?JrtuU!? zbm`QuDX~{qpp_dEuzoz4bK1L6HLjG*F&K|rI$>p0=d&n9wLgb}C8I&;MttIwg9ZIC zuUZj7BWKk!iX6Inzu-RIcPpap5$fM^oGA0#tP` zw%_e{yVC{Wt8On}x_I@Tt1o-mz4u;w+0}clUb%Aho@>{xUcGYh;>EL3JZ1QF! zo6UB!-ELS}E}Q8bVx!ZC5rlBMe@xG<;U-4P(VnSqvWNj&KJa8w>K5~dp^Ra4jJ?;e8+d(efQnl?Us9dfDnaDn~lnTZ{PF>zUe1^;xE4R;){z-G?mTf zj4Nl?G}*9~KV_1-Ro%P9D(HGsaHT!(Ou zrS8VsKM6oJ@;Ps?+xUYQcc;)BKle}Rn|42RF0*Bs|pWZQwtj@Znh*4x%Y+*AejM^HoOBK^E4Qx3Y z?r2s`_WLuHzu#^*cU``8&pp@fyZ`=Izxq|LeeG*s|Jv6-@W2E2-h1udm)-lam%aSj zJ@?#w_uZE-Us@E|kz>i$9_8!+sUvx%%p~jePG`_76=mGF)maf#y>Q{=(xppm)C^Si zyfLid{`Tqa)~#D`;^HW4g)c;i$?EW2C3`J*@4U;3f;|7tZq$ut?{~Lu-+tlw7oNLu zjjpuHB?Q38A^5b8A^s&dj{Mh4U+O>PGT)BGb(j^-AV}ET~ zJS36_9&1(#TPvyKRLn5b?Kp@4F<4$*Fb&^OzST)c46 zy(MS^sKx!@?;(tytE5vi*PtBJJAPvdfT1M!FsCwi33Qy@y?$N5{imwC{qFe}Ubu1N z#?wzd^VpXk`|aQPo!|cW#~=Ru=O2CSv9EmP$^Cw}J-Kl4;ziZXez)K2PR^t&pt@RK ziQPCAQUf*e0hK&FmQ-JyY`k`$Qu=#JOG$OcR}7X2SRS@5%?hNu!ZM02gk90nTrrrP zSf2-kv=ssdg|HiBC2agqO^w#8*IrJjw}oC>;u*njG*Ht^ww9I!L!$-YIk;`9AKc7TfghU2Os>lH@@-JuYT3tci(;C;zgM*T7`H(I9@ZvBZ+HSYEZ{NOo^X9Y9J@@bz zzWB*ce)6|J@yXx%_$MBF^wAeycwxa9w%hH)IVBs&DTs6hD=n%9-sq7r8{_ng!H_Tj zF>1libV{25!I7stC$RKRoz|aQV0{P_D}J+{q*zW9Nrj`PpJTc6Xqet}?K-P5iAj@B zNOJP|h(?p5kJTneltFxA{)wg9OY33gV;MR(JU0=_-7*SLxp}utZ20N{eOROV#+JHC zr#k18*j7Cjjdm}~c7Gw<89KvtgLpXAO`O>A=zwWgU{^2)$-@D%R<~M)mH+|C^ zu3x{tJvrGPyvIXr?L5U}2@8x6w3#R@m!4^pF;gZRpet)%rR&u@Q)kVMsW-7>kYRUt zd4`g~rcsN|DVqRE8DTb*QcMd6yEu13mQ0yU*svA?X;7|SrLkFe*eJIFo;U&4h47o+ zL9qsYdF?Fg^3Hy)f1Zx7MQd9GkM~nFMk30Yxb62hU%2_`m%sek&ph<+fBnP1{_7up z=%LR({q)z)^#1llW^%y@)LU86*-&2RPqcTS8S%*J$vnx<;p)9^fg`E%Cdsy#S{?RJMS?}mK(dl|{wGgv7j zrzHWEL$1I^qV0R*QL)BlGx5A)<3pnuavVn&|T*?$kQ z_9a4o^8EA9Kk=2XeDtFq`_*6l)nEI-uYLaE&!67Dy*b&Qbw(&N$XYswa8Tq@xDv`N zS@Y&QI%KqHz&y>E?<9K=J=#W}1v0Ui>0|(miUOTsX@iQ4GdgXSF0TO8(c}cR267&j zzrz58p93g$EMwW$WI_=@bq}-J6`d=qO^nQJ{eG_dNLEgFpBKKlpv$_q}g=(}VY1y?SzTB8QBWvv-!= z%G6KhA|gq5+Zf?dwR$pxb1F}v^bTDX$1h9&-a%m(h== zs0kl*n3N3=cV||oZDfqQWIV9)z2mfuhKp`GzGs>CI)@(<3egUv@OwwE0M(=|TvApD&NJUkig3(D(PdZyGf$9aRsDwn8k%w$!HZ^L~Y zw0i_cxhz>tO|7Bef?mCLUDm=J-5isaaiKQU;;L#AB8s0xaat&Od)`^2a54S1(qS#o zmgf3J>cp?J0Vd7vdaPCbUl7d_F75}?G(JmS_OPa#SHJ4j?|%0W{mDP|C*S&(?|RwG z?mbMdw+qHMU#ryvvtZOr;;2uJta__c2q^jT&u{Ru45M5Ntr~!Ds1oaHn~*LB%JE1> zd`{FJWpAScP+D5!dS}fsn+e#ltVDpW6)jKi@S{boH!pL1+h*rN137ja}h(Ik$f)HoV4fWPUT(PUce24(UH^nnpeN(hkodX{@joK*jwNBmV55Gx+MG@H2zj%xsZZy$8pjWcf1ky zJO%?MPK>5#V~I?7H;A$U5McY*ep!i3Y8tOy zYGb757=UF!HU^;AF0&%fk`zW8y)RfR%jU#RM{_jkN;67jQDSSe7eS4MOij_H4B=|* z;L=v{BR2M`^3dB<-!xj`v#A;p?AQ?08aQ)7OIkF;Zq0g@%?5 zMLilDs}WqK9nwJi`~qtX0Q7w#eA3qV=o^HhaDpV4L3%wngkmzW5`4%SxLfm8A}!L; zkKCW%!(*-FN?8p@&Bq+Ld0ywTgZ&w(-|tTEyMF!M?|%1t{>-0#>)YOP?b1N9spEitVH>wNKxU;2fA@&EqwfA$NX{`6;VpYCR&=|16x#O!1uG8}1P zL2OlGsBPVzX<1F4uBy z0IG)V7{WIz=jf{(FePAZe0qJ6X6xIIljY2j{Bmf2ti&LA!2$9-oi7F;F&F%km?(=Y z4Uv<>2YX1;KRvyD&((Xr_g(M$3xDB1e%HIc=i0s3hU@hV^lyA;fF&8-=An2IxveVR zQRI>NyFpB_1QKM*u3J-#E_cqrqGF`1ji}w>G5Md?B4% z(Vm{3KJ?J<{?mW@&;ReA|M@R|>5(O%wPY6IGO1z!3UmXVj62N*cx3~CoTg<2f-&1D zU7_^Y*%#h-- zmqs`>H}d>}o@CB7PIF9sXyhc)I!-sv#isPHDdS}SsX^iO#>o%pGA#xLsMpjeENJZc zvK6tD?e=YNfBR4VmB0L#|H^;%<~M)m#Y-3DFj+oq#Iqfa{)JW*YV4W;fT7bv`*=@| zfO?axzj56=_J>slPPYa=Y8Ya2j$XU-i}Wv@qQr0ANsR?O%oE;LCLWc5JGv<21%3oR z)lA0ug@&%mYs_q0lH5T^JX(Mt zYcAL*rKqdOoI!*Q@EcdJUB^Idju<3b7zopecj!5115ol;5t1$-S79$Jp1hX-~axf{n>x~;SYcKrI&8$W`izD+_~35w~u|}hD(BVp{NMm zEA2l!#X3=r-?Anw-K^S>VXgBcjUekDrHC&J0;UD?W|HfZ`&wKv#0}tFUqT8{_d*##;vN5%9fQz5LUa zF&fGMZ-$V!lLjg4#M4}Qm9?ul0Ijf_-imL*)33x0!{X<%;8KY-B17PV(u#y?fQHpp zD3pe4@{^EccmFyw>IKk+a>k8~q~nhxRfxbw-?NAb7cz8H^V~2(`H3`Tz=V_F#mp=| zpS$nA`;YwLKm3k&yyLFB?)v=0U$}AOxy@#);9cs}`)HVCy&Qk(iar&g8G{$p&S(p4 z&AqJ@?j%!zFYD@Dz)7JQM%0oStdjD&B!AX5m*F01l0$*98z?kAy_!O-6V(qF8wW(@ zw~Z|;g}asX_|NphH?6YyK`M%5J+wrPMO$aOEumYh;50V;&Mt4TRgwEh*{KImfAHwP z?sc#Gi68&*Kl`KadHMC1=|(r|=Z(xJr^y$IqE1*{l$I^KZym0UIalX$%~+_q8S_Ie z`WpKv??0lffa%R|?5g1D`1@Ku?)JfI?G8fQpm(J{v|vU@LQ{#BMkgF{oQva<*D5`7 zwS(nOE^CT!zlJ%l?#*WYpPPW;##xYwE0K*$=_UsNWF?`Nj2tKj-%@+O-<_WBUb=Pb z*M9ATKl3v`^Zxh$+grC!H;1XfoO2knAu<+Ic^ zGjo#L1XAcVl+~g*z>afu%H%?lN(fojxp9^XKFgCKyF&xOQz7Qr1nPP2iS$HHh5>n8 zKKXo-v2y1Q^PZMGXbH>a@d)7{)k?1yP9UmiW@w~Rpa)7z)FZ{NOk`}8pT z-t8UV&7kC9<=k0Epv}0K@(i^G&d#>pL7LK^@wiR?=4)+%9;4mm&VixGoKR+=XmA2k zaPcv(WRk=@%uG`I;LYlu!f3jC8`w?_E8MX`ON`qyaC6#F8GA)A{NZM^IXOAGaN)wm z3l}b2I3ZTYQmr#KyG|RnK?@cEAAW9BB3ou3?w7-NOg8$}C!hS;pZ&)_`@j9{<4-)X zJvrI!c0M`D)6b-~u~Z@7w1L(hRQxE z-qAASmbDD_R^z?J4D-mkdhL3PG>}7i4H5RT3oHs=5yE;UE*fNvV-2MNnC@=KQbo7h z3IQulJ>m}yaUv!efNs-MbGMu6W@BOx+<*U%{YQW9$AA1kef{fSx8LtiE?hX-tgW(d zx`D`p9T0p2xFyGKLHf&>ynUwPZ{NK6;*(E4^_8!F^~tY2`PA2*dh)5Ko_g}BXP$lb z`4_(a;)^fcK0UR$FSH-?ayF6J?``Mjp-)pe2Ye_jlf5T=$0jn)Mt8b+{zxRZ z6{uuZIBD!?NSoXV8jK*zzH1vcK@HD1>%rTDOa`80K@*Hqm)saJL-7Kq^&-k#Uy#F5-W>j8G=oCy2 z#--EXV8#c}FsBcDrR{z%6V#owXR1v_U|Rik6I_7$SWaKvf9H3;`LF%tU;U9Ee)rX@ zR~&6+;4e+D&_u?AssqpNeJ%c3Q3nn8;!7_*^X#)vKmGLQKKJmaf9KPWJo?CEU;e!( z9)IHLr=NN5`5Q0Zd~v@!L;cIHZ>q7gr8?*jV`q`+2h+t8b-|1~?iNI&p&W4H96smZ zJIwCVN=Mbuk5N6QjcaUOF&i~K*yx8?Cg?2jQZy^P3InqQDyDKMxYVpL9M z^a= zlr6hX=)sY>U-oP`J>7lwvk(23o!^1?NwI%;>EeZV z|IoXC>ZktN+urt;jc)d~U*f%MpOUy3+3n%Vm;u>m%j9+Pk;K;zx7)m z``E`n@z`UJeC=yb-MI1mZoh{~x=x&4q!Z-Bkf)^9(8UneGEaobi>HZK#?#c|0m8L0 zY*f;m3-mpxI$M)i971j|jEd6=Pz?ZT17F7(!xD3+dTIKXf?FKF_ctwn?40hd9IcQ% z!B@fK{WdW4aq+GDca{UXQ{>CJ!ikAC}` z-t?{C{4Fnk`MoD6C;R<=Br@1W<~~9tFQQmH5Fm#ZKBE1;BzaygCdo)UxCQOVA}8ZmoH!Z3xEDU`N_ZfmtXgW*Iqa|SvGIeRqbEJ z19XrrXS?tByE71UaAO6C_H(z-E*6p(y^vTJ| z9K*udKbDh%p+beFu!Ed4D7QybQDM&7#oBL_NH(p(#UgDtr;@AgoGuORx!ImQH-9-Q zE~e3{b1(H$rQblim*k<9R6=bJ9z|#g(<>7BhP-@DWuRIjVn!BpsN!h_#^GS<5_C8& zV865d{`7Ra-CVqM`Thr9`KAZI{Vi|#u6Mrko!|b4zU{vIu3xxtVez@?@JDTgvu~*d z9vN1$z*A1o1pf5&^re?xdhdJxzrX!={)bQf&hH$+ljjf_H3A&NLl~w-j9#*Y)(PQb z2DyeTRCJ0xIDg{M=+h3At@7d8_$;+(Wfo;OA0vwDn|gu`R7B_4#}b<@6c5UjjwJ|8 zI|S5Lmx)8elv$z1cD6D8TY%*tcl$3t{(B$$*eCwYFTU@? zAO81W`r;$czwp9F>})>1Q8NGs>IaCM1$9Df4m~|sW57O|g{Vk3e_FdL{B1ros9Rxq zshm?|@;Ezktb&t=FW9z9E8HjsAfWS61CUw=O{fo8W?RKww^=p3Wr+)I!{rMj|9KSvSWtc+aS4JTDsqJ@%=&tQW^!ewX|IB9}df)rr z_iujjmwxx5&wl;uFKo7(#S3Kn9qtUPaD-&ip0I>Yq?rfc=Il*YjQQ#rWJT)Rvf@V8 zf^TH=EkHACd%$CPi98Rs?%=kyiw4QD?LVc!v3%!96>MKYbE}|`K|ibvUZ*$`t zIn0}rldWgm@glEd^e-{|MgDiY-Df`g(BJ;sf9GHRtM~40oFXu1wR?;fX;xmVkOlk! zmO2Q?${vd1{83hF88xGuKK6~6PG(U5nyE8RcF(#o@?XoaV>Sv&-FT53cV+;**DWh| znHzXGopZ{?20*&Q2GX2Z5{qX~E`M({@9rG=SX!LM?5Co3x_BpwEFpUmj$n{_Rd`OI z2p=Fk>K|tbp0#Cvdi&en_{P8S*Z=yT_!B>JvOU@A_Dn}_=OVMx$y0(USMuON+Ii}< zncaBq#z%hRH~;DX^UvP*zF&Ie(MLq|;>AmceYM8kDK(538QK%nyUPyD-wfU;t#cS? z6SVHcTk7^4<4q+@<-b*hvWG1VMssmgcTZ_7<-jrtbgX%zD5EmrqAq(jT>%afG zfAaIY<@CA*_bl$I#I0jm*%peu0GOlvZN-h{ZG1LKQV|+MI3{G?^9$-sl&%a!6 zEB9JMRc3|7VC6Sv728Xf?-K3>KqkHkS)i6ib?b9U3p~uPZXTtsUuYdi6|L#M7|EK@K-~Z`<@b5nKp&K`DoJ}covv^1mX>)FQ zcNK)e!vI4`MUuyDr9GIm6kb3|;i^Lb4+-Zl^F)bNZQHVRWjg_LitOZ!>PceAEXzvG zOwl*w-ecY8x7zUBH&W`63*z(BhuJYx`9C6@iY3!L#PA)X`k<#(WqY!{ef#$3AAa}) zANas;ee`4d{r>*@@4M@+yMPjjWM16gswaDL=5SrRcI}oL#ZIQdA%$&qYH%!8~~)5tBjQygft+v<5o9D6vaEGV|ZtpECzsWD5d+c z(idsAmYRRkz+x4lUX3q^iTi8zyH(!@Pi-v(9N4Ks%~%}6{KY(d}}s&c@PvE*P2*C6|XJ}xL!pe>6X8P zQ60vd!~Nb{CF)+~v~l_J<+r}&yHxEHAOFPd+o!`TrH)NKIL`Vk+EhU^wk46xIJv*?WLla$V{< z?2j4wr)e3KsCyALfUJ6%@-7?eV#s)K<=CWQCmex=zG@qotWRm1n(gPTOUQP| z({?};gh;dBs{p=h=Ljp2C{)+=>UjL*lTY1#`|VFW@#LmWn|AKpF*7q$bDaaf7RrEs zl3){qEa^HOaQ$`HO`7q2_us#|y4uA6NQ$|j`eHawwOv8u0Y7FWWGHeE`0G5Fp;rK+ zX+Rz)6Gh3&Zh)ex;Vl{*<@R%{063JMYQ)jw#U>WWr1nZ-?H*E29!3%12w5tQdukC6 zLk%F~8SESo1SZ0PiilYQNWvv2Wk1%k#I=ot5z);z-1y6%`Si8dU)3f2jH;2baEqK! z!nyw=uwwPq*IxVW-}&8t{J;H^FMjDyPM$j5ZHLk{lSoW3Hv1eB0mq`ZAqHh|NFPycKeA_rw{Dk zw`J>AX6`cm&9WF{P*4`ooU`xNxM$tm+%?x+v%I`=@4fd<#;ertT$Jakl7qPXka1+8 zMsdMM%J>Z4NjgO-K~!0Jx+`qP8Y^h|YxqZ*Xz-*GfR<#64|7DKw5BuM6g*;Fc(JAc z3X$!Ix*9uj>Oh5?1*VC32^lCsroVwA%cOTM;-XH1DbP1Ho_T@L45woHlS2qSS=OX8 zU4QMhzxpe`eB({m_o;JT&W{@DGAq)z{{r~tM|C4|B@BjTvFTGUN zqgE+2DICVGInJ@@8b3wIOZ>d2f*upG_X5Y}@a6oQ49>Un^rBBwO6kuu-)?F!`z945 zI^^aU;|CE7C@Po>(On7^NtqYuT9qMnXm|kjgy%qqxPX!CUZ}CymP6JBQ8YlBHN7=( z>eT7)-gVCd_dmFK%jP|ME}Wg66}56OXoB4{1A4b^-Q1DuuUkBG=Dz#ypL82VFu0RJ z8HdxO7mL#w*98-&7z)uH_F9w+P|d&U4i62lDVR-@tcH*r7UZ&4SI>g-Mymiw=Sd`D zvM7JA8to#7h$yeqi^<~d7d@rrjZ#z2ljfeWgKIa?NzZHpG zebtqp`lVlb+uPpKX{I&wUnydVE?>zY>Q_86^J}lY{<+V6?lZsqt9N|++buVq8a8iM-ocR($f{ICm zG;_jI8tc?{jnZ`nIip6{$M8QSvH(Cc?+ufX>Vtm-m&v1a1@w1=2xzo0SfxB z8=k0`m5q>8YX)=u(n~MjcKhvbym9Qnfqk1dZ*GqXtBM4H1LlgPgvr|5jO{Sb&CT6( z(~WPQIPt)J_vN43z+1t#rq`@{!a+g_vGZ@|c_7xU5KEFQZMi5PL+&Zy%(^&a%K0Xt zR8&tRxMEXHzB&bf)vMq}mf(!B5EWs_wMDDhQ}PfSEK@f3WZMTK>mD4C>Pi#YuQ95G z0CKr>oW)gZ?I#jyE;_LPmp}8VcfI=uTF_tj&_6Lsb`R(&)61tuWHO#~j-SUKd;A~% zqhI~kzxHddzy1buC6h_sdklQbtV5@chYA3TJpZwW#lq6y>I*0Ef-yU@yyH! zQrZ~@ij){|=S?up&CcF%!;zPc9)0ZlPvooiYO}+ zo`<@rr%yM$(XxgB-G_72^oxd69ozO#n?}I}exaUdUEY4h{L)J=-SO=^PM$t}>A`~= zH*VAm#MZg%$PfBtX%#83X!x!Kv# zXcTYp7R1Wh<;*PIF3QJ_9sBHm_>F(^PygA|&pgA-jWpB<6(|O;=A#A}Ky;#l9n9!r zasmpSLZmq_6nvaN=EK7WaAu?*;0u%WNWdrKQ;K$ClgRIcB0EzBnU{;vl(-vE2HK8^ z$Ha1e;bU+h40pQ>I&_@ea-NP+&pny_ZRaRcz*)bZXx7uohTj;P2QuIB7n5@*PH$`R z;OA1g%X(`_==AB+-@4=5FTU`?p+kqZZQtJ21>1h-Vm!D|XHRa~viY*h4&8VEeJ>w< zg{xXUp@aNc7U$9>qCM;_Wc#Jlc3@&LY4d|jiYVztkx&{^)z4@~TnbIiGIMY%At1Yw z{6K`hl_d>SS;T0>;Q*LTecaH5C?o4lyqjwiCbOQ=6cV4m5NWgXeZq_TP?=#HTBm$n z*FX6aKlyV%_p=)}Z0L^BKEF3~fo6DFchvWN2jEb|Iiu*~1Mn2sQD`xCPO1#T0)k-2PK@U)*K&EL0`|u#8 zQT~x4yZvj#CM#KEU0@-%1x<*R>cCB$t=ZmWKl-lazU<_LCoLz~R5-{*0~aPrCF$P- zsV>Jsqasw#G_Xm*82zY~@;lgYjJ-T&!N|H>D?_=T0_6`@9-rjqt^v+hDB3rQe~ zT_wAqhddwlW)u(Qp9Mu5#NC`+OE0~2_dWM*+qU)4rI*yBx{=0AD|f<2iX^q$ zS9i$jqKgh}*s$T--?{Vb*+twWE@5QO1XmkyDBBm=rie2x(gL{?hb+^EvE#@NlX<;Gh67HCS)eE+31-oq)Ib)-KAeh8;3EuT2QlAoX1|Etb8BdNGLcKpq6-*M-{ z{Q9e}zG`-Et~;z$#nrHCG#-=Vud4X6Lx)Z;p1$+WyQJT{G;27HW`omc1>J)atjJ8X zWO8YCw&Xt&3|Km})P;0aBrGb?;PcF8?255&JC~P`6R-1~dUkF-MWVn;e*X+Q&^+^L z3nG|`xj|;a4Dc}&#fXR@@5=GC1%=^I~n^Ud1*44{OLFT?C<@r|KO2F9_HTdLu3{LyJWm$OJjFzqAh^td zJj7NMoobHH>)+0jy6=zO$i5WjB)gKn47+^V*&`aL>dlc^H#s1xVSr(i#as|^&AiX; zq8eUWUcURDdzks^tFD@xn`@e~e94yAo%#njz1thHs;a|>FMIxl7asfmW4#xs!~m6E z1`m$*9O%|tLp-1+5n1z~OE9ZMMY?;uDC(Kvq^d(5s#d0vL|pO0QKZ#z)|tKek-uYoOrDP6oiY-6?sqvok;Y zGe7-PKlR^qM`l-flZRp-;)%w6=^gg3+mnI6_O(C#yMOQFPdxc#-5Ra=^~B>tg3Hf9 z)cylxH(l41Dg;Oq7~@OjS=NawsqQi|%C?>dcn3*?h*l_igSAFY_ zZ@v8TD``i<0yBAQl0HQJTv(5zpoTI^Yqm-tFPmvV5j!Z`^Rb00%Kz7n0ZpQFc#8#gEYvP+f z{DBYroxk(9HgDNdRdAqq&wNHXrFomQ?xbnH`n9kB;xB#X$)}!PW6Gu^`Ww-o^DhTG zXlbQanTN>3OjJjU5UbiRUTm~2_m0cx4IjE4d|u0?A+_jUH1a$oLU};@NCUY#%r;Xz z;K7dqU(dmc&c$g99kOlW!u*JE)X?x(b(@FUAHTuIdj)k$-ibqNm!tyPaj8U_haP%( zJf0l6;rh9`In^O?on+f*5!teJ%cf16zW%kZuda^USVCSO5SX(_C*!hLk7$qTjKyFC89{-@K?&cEN-s$Z?6E1AIjnwR_=M}T-{4*)G4J)n37 z?_l=)aMLv3_|tFv;wL`!#8c0}{`ke#py*OifY~ehMW;9al@ z`pCZZ?3w6#DD_#X%kDq3(586fSVbIczkx)h)zQ=fX9BC9LtX&<;-D4zNQtUus%n6K z4QavB6K{0Dh}cMndWNk_(dyVxh!jxD_zW`{HdSM1CGwE6G1Spk8U)EXogA>%fCnFX zSctB>?%H+h*0pv(f26%=?4-)?*VwF0y8Zk2y>aZ=0}no&kHrSV)mXuK@&Zihcsntd zsKy7N9?7G->eP5?0IQe`@-FMdU>8bIRuq`OdZ@8A)`Bz%5y=7|T!I?Le5^!au^rWq zs~O9ROp&;@P6D=i!m$>Y2KyGntnxRD%#NiaC+=LBU-z5m%+YPpuU z8`G5#`q>{!PHgg9X{nLrIGQZdEkME=4R)vx%TRrnOQ1SVq#Z?^#5iNH9I?V z=(5Z1yYK#^M_;bECKE6l8dGxZRB6xTCc%Ks5Yh$iImfDUXN4^TR0JOwX>g9MjbK{k z*g>!4vI2-?Ip9Mz+gz|9yLZqb=6Wau8dxKKhcaw>oNI+4ocVERH!Jqi-vs4zRD~za z&%@DX`g>CVJUPTJAfU+$d|d&BfmeYfrQ0yRId&5_KlM`zS9Z6DWqT%Xo?S(O>*kT} zKytq;J~LEO2{1#mEZ;kq;!)ZX5lR>WV_F%nKKQ^x8yDuUz4n^XsFp@DQxpLTN0#Ay z5uvSHw`|<7;r83Vb#`f)d;398d{QI=VPhjnPBOS89{pf(A{62&?q_5}oFTe-(>A1{ zfG4bR1LY35gZe6a1pp~kzDm?h4jDjkUG|)V{^0j#He1}3*O|4w5Gs16>=r;O3}IcS zu;-*Fg&E_Hu_jHkXZP+;{^BoOcKA@05?9q~n1+5LJ^&4%oyaik-DCRQ zkW#&hQNiF)uN0?wzz;>BO(DU6M~q9a7*$IFwAp7N&C=4+-FM%!d-v|kuQ=SV42ZO+ zIqobYiGBO`z5d$k4?OSyl>_dD%u47^#TK)?lw!M#K2pw4>Vim*ZysDO{pBh0%Bvbn zbVqNT!b;5-(UE6-?uC&j_3Yew9;n7NlgLkT66d?2tntH^n=c^D!pegkIm{e{7X_OM zci7htQy_`kWK2pUBr~JgpZ%Gi`O*LCqqDQK`D}OF5mpFBx(sBI*Is??6TkGSuYT=o z9V_Qfv>TX|^X&t0n!ehTN$h8@)vZKh_iwKxpw>Wb#N(ff5l!b<=a{(??%&YAX?DEv z=!_JOirUsUY(VH0^Euzi%hue6k$AR?=T>*se}_Fxu3+;(poU<2 z=jmYQgM%LaU9DZh*;RP3jG}G41F~Qa63osdOw+Sz61LabvrG3q@ZjOg4qtNVK^`Cu zx(QO`jzwft*9Q*lzx$qhUw-v9>b;rvm}BS+^r?82JdIg%a;QC#rWy1ip(M?5O*GbT z85N|bdo&;Va}hZ0bz)nS2GqRQSpeCN%|}*qCWc~vMXn)i#-2PvFf|ySV^DyC58D!; z_uA9}Q9-6X4TF9NQASzGG?NS!Ic^>K?~lQU9HDH%nMmROw*vm#H85 zlN5KT8HY|3^ucOZ0s+8h%kSE06j7V4lv1Ui%{ufD>fSf*c z^07xByXEFvcI>=Bk=twh1a{l4;)uE0wQDCa-*(&Wt1ByM8L}AQ#X3jSBF7?@7_wlp zxgum(j?GwRN1J0C*2~=3jGum9>xNP(jBwueq8M3yacYzqN2vW$vVa(#_t*q9q}70G z^6J;by9y}mT5u)C@l%c!w7@ptgv3_{9MCY)OOqRs`StVv;P3y!Ew|iE?XkZO{@Vt1 zfTd|)G|JLXJ)&KE-E-f#KH2n~$kLnatd~kfuGr_8Q;!)=v0>M`gcu{s2;sTe^68J z+eKm63sK`SW%EGpgzHBnyU*ha!jwt|88|;!Najy`HOVKiebd6(vVaztP>5c8?X{yv zkKX#$w`}V64PCRMO=grz^XjguwF54^@WO{4die3jA8#jVgX6+v`M7LduRxm=*%gnl zqI1`1?<`O#)QpTLqrM~FU8vP|pIzvF^d1p>t9!Tvftl^YDPYuR{1rj3*iCHtX|Ed1 z@i0Qc6v8Sg@+UcXhV*d+M_RXOPwdnMjt7K5nVN<^`WHU>H~z-|Fu#7j?(%-vhTYB8 zVgIV)Z+`Q(Pks6`ufOp|zZ9`s<~VFlW)K3ioO7IqDK^`~SY;kj&SYd_9TW;Kh4Dp8 zW-Cfe_)EuOLXVP`d}y;tp#a83IEss(JK?77vuFpEYYU<$Mc5!22CCFYS6N0I#K^4r zZgTIe42b3f?7d2nH=f8)Tx$XKlG)m*bWwdxl8$Z3J^4Zpk|SMdPDY~&JjcpIZ$Zvk z4YbJWi6@_|M>B7G>#fP0s!|17UU8{fL?)BT?Ci{@jT^r9wXdIDTIEI#?7K9rL^oJw~krSqz#Cw7)6b2}NUrq(PgGzgzlGNw^_&yJTZK&nxw!g#L8XY&`+O zfd=OZb1+EkYca&f7yzzwW|K)k1S5D(E0Hm3i=DGhNSh_W7DnUDkFGAKuDibf{m1s~ z*>m~jm!}m2EmN3Ng;~u{5viDW?%MU@i!VO-z(f5B@=PXNY#ReC%34OmVvArbocD$! zJd5Wa7`my9jDh>zPoWahbwLE!$=*E4tG-R$_8CI9zQq&?Y= zPoFyd>%aclJMa8%@+ViVn=f;JrefAbsjs$ePH=+QV_2hl9(0|>FLv`Q1f*t|kQiCYuTIxYd`tpyD!{5 znKTNiWMXE65NXH|ppJa;Drfkaq!b2`wVjCZ2E9;P_>yln3 zH4W|Ev**Wu{IATeo9Q-u?qfCF>%UAzpy-;V*)+|Uzxvhx_xFCU{iOoqxAPiFYqL#+W>4|U@t zic4lsrQB2GW=4MsPNdw7!fu@=64nqyKw=Kk(W$|R5K1IvN8k`}p#nmjOQ(>X)jR?6 zluonG9L3wo=kPMkcsHY=egsZzc-wBZIg1z3glIH{h42R_PG~OI+AjCiqGMJVw3-2 zDmx)%Vn-@lLJZMC7i!K$WtS|i3h2S7rz-1)daZ+E4TtwUuVEX!zZ72xic-{?tg)g; z>w_&~!h_2P7+8->OvuCxMfVS!MQpln`P$%vScJG_P>L(*TO{sAs@Bo~0Du5VL_t)p z*f}0FEapj7VZe{V#tUhvpmk1FRWH2w;`VLZZ@A$|Rp)af2N|A|fi6NjF4*xWU;h6V z&n#9|ovd^6GclxzX_&C14uPaH{>E$x4Yl$-YBx?rWN9w}EI&)|G(J<6Ul<{T9j}D2 zOQW)T37#PIbYR-8*ZnDDHSlG*r5K~B2q;KQBgsw!!PfixBhvd6k#5Kv%` za@~*RA`p zAN#R`2M-Fy=LT}P>=!Tj(>H+@A1_~amD zrmbSZ^C`&+W&KidPbJ3F4541JvF20`D^MV$K?XQ&IWXvqe8ey}@THJUCgTft@A~Ku z|H#bjsH)aLLlmw^7E9CGl_XVFz468y|K`_z{rK@?eOxNZrS;A$}H^^6Sq}a z5Kes-Cujy=USxa2i!}Dv>ciQ7*+q{9L~cegB?|^^xLp8U0<%02?NPqLp3U5&qW%N4 zQW^luudsarD4I3h3&+|yX(X~yXqZwGz7$d3d&mkPF&IZ(2&lOWOPmMfK!UgiI+qu? zn0xWT&|rfi4Z^p5cMX%098IP1448t96W-d6R=5l*ARb6*&o5XH59_#mmKaGqh@IqR zg8TW@|K0|2-~F!lTz$>eO_wU*lY{zkmDA@7_hNQh+^w1QA{d+;CRf z9GH%IzDy0|i1k8-siqnqCh6GfFvCe# z#+L(m$#iaP6Z8<+R8d*TJA4=>zRUlTqEnb{l0k?Bg-EBy)38Yu$z{v(=@+Z;8Nk`p z`9*{ZkwI+qQ;eseg?`4d7P(3efYj8vr!bwK}w+%aS^2BHV!*9I$%BvnD zD?|mhLtWQ%b8{d2*vEG6+|jMj)Mm%DiNVMUi9X<~i*+k@37A)UTT89DO%HRV_8Hev zOe@?r8&rze0W3|f5Ico-SgV^w@@!9%Ifnw1;$qW`rJ1C| zNUdx~PmQKTP*GObB@+h>2`Vkf?FV1b6s?k|2eAdR*j|VbL|7PVQ&|luS*c^v>PIl^ zPl0|9g{@fV-T2ntH`)%pFFswDOzKX4k0}sW*zCXsXHcMZZ5WaYY;B-RA0Xv9e(?#( zqH`@Vl9UOe=2D4G3PeOgRn0K)x=;SOTvyoiDSAkuji?D~^}!mV{_b6O{r(^R(Re&j z^ULIwb{(y_t~;BauDs&%x4-SJ(j=e;`*e}*A!MHEt@aAsa@=Yx^}b1x6GVM7w*DBA z4Opjy!dOY{NUp0ElV_3~390}~1xbe#@*-55KQb#l56^n;w-;ia91=9a4Kqs%F+o1& z{JzWs0n>4@hI-pd=xw*Y_1bH%DSQ45seq1PEe2RwUH$y$fB$=rK1%%(Lzxmob3#t> zDL(9>KW3rXR6Rb2@G2#Mk-#4JiOqhCqDiDW1mY&Ay5?DG6Ha*On`$Ca0D-Jy1_d~n zKN6~c4WdauNkYau=bI;h+{+l{AxVD_7s7!**bhH12Cj5wFXseFOrmoRJqBk~E?Sw8 zVktRQ6h|#$W+jKiMtc-&NubCmgeDOKkFv?z)_9>6V$G>DXa3`V`rJ!Lk23RQGHE6v zpy74oqpxMsH1i7!ANk0K7B(z2{kDx34={yhR&KvbA!Gc_HMj2yJ8ClKr{#xq1B%0( z%b{7;nWwBRKV?nwJ?A?`tye%?8$+P7g;EeQaus=iRaB%nIIpW?>_MbQQl%zsh~3)- zT8UbGWX6T4b1f3{!ovJVe&i$Dwr}fCeC!r#$1Kn$mbK@+fA7&p|I6n;zxH@h@qzV_ zLtn#q&Jj-cH$Q+0DpU8r^!f5OSX?-aOz1q<8{#n4E_hsQ9>r8=6ZG5M5?6s?5%roNqrqDehv;44kf zBpqSd!q5`JV%H!6#9%b-|RGzE+5awenp+V`KT zDR575T@tJe;{c%wCJl7ex)L79{9#;>VOJg@IjjasepIF0R97h&8U-GE$70homtJzo zd*1!73QoWpP{J z|ICsNOf_l+&1++W6Sl%1lhkB`K(NHFSRhC^nwDsWdeb#R*AZvk^t{%EO+xTlY(3*B zm7+XKA{uAY2TZPr`N_!+ttxc3p}$NnvW+fh{8$>Hp&f?xlwSdqwT}eq7L?403@U*( z#0nugJ!C(SjW<9{2%0)|oJ|xx{nHCLQ5Axf#bvQ>o(O9YzWLnee&?BIo?{+$;;2FP zb6~lus!?6<-Lv;y?|xTz$V_{7hh))%G1AZ!m}WakE-8se7N8IKdg*ILvnAQg+ICP_CNl9Ub6Xn-WM~p0BMu~k4Cn!}wpIMI z^NQq3OIVDu;knkUx4-@E2M+A3+GSrQ`F%jGcj;&kJoMm~zx?H962U7{=#rfu%0y3H z=R~P4F4|5P0(;lwCkfNE+zv?Im5Vw8GxJf%^UTwq`|aOdUS8_HugUZQ3am*wZY_MG_x;d& z_gr{kw~T;29ULBq=0)Yl^vkTEo42{NM-P zKN^jys_t|EM;MUOszN4SSzZ0om%jAuv(L6WeO8F1GblsQR>p{MH=Qh=5hI}KBBXL; zOyWvkcs4REgNi}JPQ!;FkAzwDO;LcY&{njj0*}0uft9@8qR)dV^v?wp#w!XM)gYJN z6)S;V(i?d=t^#p+f{Rjq%B`kBy~^Gy#9}=I21679N)aS!99%UIcjORMs=e5-5xY1V zl<`9$t7|F;_eyPjZn&@oS3y&$#FwqC6vQK;UkIPoQ&@mlE#tJi?@q=`%S)gC{O>*a zB8Ltgy7`uynn~kBix?)=0EvvDJxx{T#6MAT+&FC%DUs#+;p9Qb z$&fmMS|nwx7!9Pn@a;2|VP1%;;XPGFuOmCCBN#Bp-QhB=)QdeAmLz68j6M@Xrt`G# zbgGSAFh)~cv~Ci)@`@|2zWOR6sq3oW{4-kzuJ&=e@6U73KKI9e{3Yn}KqFZTS=NoU z(L5oH`l)1HR@v^UpEmgn1aYY-$?cF_PMRK#<8PDqg11~H}5>n7Fmz3iqvGniZ zLu)Cv57u@4gYS9wwryMc)71Jz=Eau1%)ah$lT={D>i#ww{Nf3QIIFERskCX|?nxnD34>)>Wu0&0-MP zEP-`KPfTAy+6xHuV5uz}3G6mQyPOKhtj#u$&KHyqYGt)_g-e$ibq;G%D2RdWXYCOy zOL;|P(llGQY<}z8ZXJzg8fnO`_6ka?>-zZd<6ro~msVEBVqBTo%Po*rVI&8ezxt~< z7x2LIl);!u&0sCrMOWF=Rq{b299ZIsBaN7e+FSktzBA1o2rBFJKhh5WGO|72Qn3ev zgy8T3t%R?WT3hKlA|NY+jSy{SgsV(bNPMEQMC2^Rh%SH&%~)rgVv|fCQc>9tK-Qfy zC?fNmc2P9+TNVid?Gqy;R2pmH3mq8B8M9$}qiPkPXo1|shU&V0@s20wB|&JHz_t=(F|d6Y2~ufVgv5i79i)z{ncdN@EsNMOd#x_mGYfHz0du z@KQv_|c`q^Ct1-~tZ^ zxNS=Ao0C+1F-7Q*CuaYXDVp75K8`4vjSyuB7!+lf5LV51GJ#b)GfRfB(KIjDW*pf* zev2IwiheA z2rC32&Y~$E3F`KORwG-U6{Dgd`fGiLtpwoayLWx}k?%d)qO`{JHR~2MGeqR(n{V2? zZ*S8yR#<}2a}ri^XVYyvuj0#DRbF-YtYR)XAY`*VNmEweP@VvXJ~JzIoB`_q!tDSZ z=cYc`l|yMFTVKVH*dS0tBTTo;%P?@v)Ie)Z;NEJ*a(n3Os!sjn24jt9nl20!BfG) zRbVPpicFJbKrv(inw!`DE9{^#yE41K0;+(mmMM~Xcn_$P_nB;-@r&$vF!}JH8AZSS=>xA~Dn@7L-i=c7ko{_dUMU0q$xkCBvD`eyYM;ttOp*uVeEE3TM~$Jvw!+G)evV5ua( zm>`1CGYZL}91-P9KMtZgR>JT7_(@hrj4MXOX_g>697vh11H4}uQpXF-kOZ7g69nlm z06i27X(3{@pogSo@f!xURA4rn*j*+-zx=QK?)&d-($=N3bJwoxue-KssM{kPz;(&> zD6MM=f6u-5z3}2oRXt(^OWOD{B;%;Nk}abAqn{z#ILzcmC`49j0)_H^nqqWIa1Dp4 zVjKa(s$rj@LnzeqH!)miURCH}Nc=Od8E_9dP6j_jK=6!Eqih+}0;svBbTVj$OPC=N zzg3U=Aoc|)R^s6ScmW+N62`VF7blt5MDMMTwSCeq1O7bF0RwoI3$OT4w38nOF*zaK zgO8?}eB+znJbChDi{$ho=A;T$p=|=ljW^y%+?^QHh{=WbJuAY$PeY=j2L{N&Dyae+ zA0(jK=OoVyNq@y|_a5a$kj9iwma}#cRf>}4@#3J!5zgHH^oR@Zvg@9|1Eoh4km5dq zlTF!oyi)k@&Pd}xvBke5X4tsdDV_xJ0_DcX^O(9(x%a9BqCq?+Bc*<0oa;! zj%~Lw) z4h#Mn9BxAxgJ00n4s@*{wRpreC{;mYn7rm_{0Q_>=Fs*-RoC}F_`uUoKht5m?BB?~ zK~u__5Fonw=9?B47ABLiI2~;~hn>Vt-)lKiWE5&(EZV4c*{0s%$E`p0jgvZ@Kj?>(;HGM$6>=Z}SKE`R8A__rCk; zdPKcH(KnM~SrOFWh11k#ureOC>pA~T7HQGa8M|4VOtRVOss%F%pr1tDk_R&NAfOj!{v0( z3(>y4d-w0#*EEyTd^ZjW?l<*_XE%@PEu+!KnRE%W!-OjYSQ$>5CWCQrL+fGWD%Rm;Q2NfA)lwTYWtmOM z=n-SBcMWHCvE|G>`nczq3o|4Wy%1NTV4P=%RzrPP9Zd0U z%Oi*~83`qDScRy>UopiZbCSlUt{~RUN z8(buVz_Ehr-F}u9&$I)$?6FFT2uJ~eZX$5;5(lmT$)sW3&6JA^np=-`+LnHeU2nhR zTmSuk_fs1-Z1Cv=qI*$Q)nqc+xN+m>i^I^DnarFi({`lyVskUv zvVOsj7IybydB1*K^Idsf}!sEPz_xuDB7|zkmPUy?g7L zA^!*Ik{G?|k9|LLX7Ss1e0yaLv5g z?|OxnT5EdiAwaVDL+4$MeRVC~6IO`Z1TAt*SX?hfJqG=iLKYwb>jso|7+sY?fn6fr z7b>PU|4h;wL)1ofQ6gq09uR?^L<;z7y{&*?b-P3%PALkfqd=1X6<5`hk3as{V~^i* z%gt_5+rD$N)|z$e)*U%=v?Ec!P8pw0(B%L)Y z6`g%^@y(as=yvQcGpF}F!D$*ov-Rl8D=y!-X=6(MVeuAM+C_saW~p9y;l-z(dYbye zIj^H)nUXpyq#z4ODWg=W2h)bxMbD0D!89dIShIq#MkA(~LvkxKSvTa8AfJ+SlD$l+ zEN##D8c^YI>`uwhT7uQ%y$)pHe8c4*ym@x}CZ<|eyAAw0pFC$@P1E+~%xfnWUpq-$R{uNX(#!h!`Sk}cK8V7l zvnKpHAnpvku{`zElP6D}sH&RHDax*vVIl4IwJP)hoF#w|hs&}>GdtHonSO{u$Qavx ze7ru(@h66UkghBlbmcA&0DXJMY@W<0FgnFtWVGTa#uBrYXDy^8!qn<9${uUt0ZrJk zz3Tv4Rbm00>O#m15Y)1&B7gGtL%)Ib^|7vn?9vMMj+WG!^WZeJA{Yg}3~tV$v1+IT z48XBt7j#Z+&wE_bTN4jF@Zj;|Z=wPS3M{4@@7}fR!V52a?D3~sRNwTa>osg$thrg9 zEFL>mQ&mx2^$@m;6EsZJ5Z9|z@#uns```Z4yLVrA(F4DG$BC!ksHE;UuI*a_SRQ(y z)RW?|fCL#-hM`~$8AKK7Bvrc^Uc6S22v=4XSF}YhxG=$10QA?3gH3ny4j5Gz(PyrO zXv<|6yz_5=Fp4D(S*5oml6SM~|(ZSt^wg<=is(yVk0W zn>OyyN~leq6Hk6iS)u^R`ICTPBi z_u_FP$xg2Q3C7Qd>cp|xXM;;w$5YdFz&~mt1m5 zT~(~$D@&$-UMSko+1F0ZG}VafS}NRTqtya!il{WrgsOV>@Vl@3!N2{{y>CB6>nf(Y z4OR9yT~HqCp*bJK*L&>p(MH6IiPSU|ML$0*ZS{c%9EJx+Yj~vb`SMLjEzpA5U-mi1 zH@MRP6|ZT{3$D8Goj>!#`);{{s?plBRnyR<$;-Y3ws~kBKr(4gymU;)t7>H!6$&IG zRMN<{?c4V5+tVfUo}*9g1ItTGk3aUrnvs~dY4xh2?cD~hqnWx?rAzGM%gj|>)n{Hm z$z!Rx)`A=r@kC@yGLZ@IxMc6U{>LA_^u5=}3_{G*CX#p{&wZV@9MA-F(7h)}Wl$v- zelCYVl*UVGeA#XgN;hCvBNL!P{**k4+}k6SkZiqt=MVhc4_|QA0hy$8e89*^h`ChB z#FLfr$(N7Ogc?z)qgnE&C14~?vuo$BZCkfa`u*KfaS{CQns?&F$yZ)^t*%Ef2;x#e zIrTFN3%K!!bKBh(D-vuO1xnpc6DEzq%NRrqV?jOZ%s-6)pxvsGEL>{WealP`?J*tf zxrDr}f`^PVF4#2;(WurD=;?A9>}gR2lFFo8sE{M>**B|tMw_8Gq4OrN#wG4Dk0(ws2OU(MCX1Qi`#78(IoBF zkYp2M|IJ)mSy_7Y`;YeUzug(AX|R^!)aV2PG4J2MZ*F#`KU-L@%Fgh={daWc^;6^J z)e;`e+KN&fvc1{u`a@Ss-96X<_e!zX+(`OK&oF$V63^T?f@NTh zr6Q&JdE8|zWo0a!@gbwjM^SvpP@^HK#vvAjSywiR`o0x&4ByG0<+zxcw5H{Y!5ilg<3 zOlh$yb>`s|x$mY0@M@&I@(O>P&5wW_C~ znT3U0|N0N@f7{{7WDJTZPkXT=Wv@s(Dok%Pu5kiE&vTXz7*Xx3RjaS^Yoe|>_ zKu4znBFG2BL}CmhcnA`K$l!^v+%)_Mw*^2PvYVgdjwORmv)RW+97)b~70**HrfYK$ zZ1*N9RD)81nF4M=Z$|bEtF>=rw<(?0>?AcL@|;1Fa8s}X$+YTbWu)g-;=&lrzz!0u zL{&Kmlrv9}i3HpgxK>e9PDpR=2|)>Q+mppaD&Wf5i}tVpMI?}-t>wYbKmWq&>X>^{ zG^zr8yLBsw(9Rt@7B*~b&-!bmX>dgPvF;-;)ykRW)y3r!brC{B_M)bfWYqb3CX+@+ zo91u$%kNk?u&o(4r1qP#w2Jx`QA+gge&xKkBn!&!nP_+k(kru8Ri>d9V;G|dVXp}- z*fcocA;Lppm{sVom$U7G&NFoRM~>{i`OpmJiipsH;FrlbBGP zC9Lp!riQ3m2BZ>x5c@!pIjUm&YUwF^&d+PsN~>|FVqek#p2#h5h(st_+M)bMfJf8_ zltB&>4j9xd;dX>D$4h@wi!5t_S4T;Kpp#?(scsO?!QOe(F!Rf=ynO1^Y352bzEgbc ziP~)3uwm1tjg!e(y4|D+bbWe`TZ3kNc6sT!w>dz0@qPR&^UP;EJ;9 z&b7D4NKYo?(P*@L*UolRp%Ndk(g0#Rj+Lc#}K zPqoctdtW4_9%gHD#b(cdVil`iSpUlBp_9X{&fPQq*W6tcKIxZX`v?Ps-c>NYnL^}t zdn*jh4m)_KabVR!F_O}L)_~*j>(Rzd8{3qS1{9N*yfj%^ zU3~L&K988(p6ZfQKD#rg+N_|4#;Y_raO>fHZ#~?QRH#pNFN*f1Y19>j@b)pa8Ij8_ z(R|fK!b_cM0yuKxufYKutUZ#&OGyZ{qk=m~INC6K_=890wr!Yfp+Ml&Kz*0abdF6YHBSraTrRfs#iUGS)DlDzMYn_! zdI8&wRaS_erC>o8TdVKBr2Ss?#Y!Fooy~JcIk1%vW-!i&_lKBIbF~t@*%~LyiiDEO zVb-7)CMoF~l4CqeXBUsXalAKDyFse-PPtO75NV{Y>aAP1azwrSaO{e-&Inw(BCM)B$8&zJSfgA2+ zfUx#=-Q3*z`Ss`U)}~AbuK4WPvnNix$;P>Wv$q$^h_9jBO#auL2Cl3w9IymlJQW#= z4Yh}A#9%iu4}q{Fkk=MP3kS9u8qn=Dowr;vRc2AFT{RDG8q^$he+oJs^uMbDW8v@X z32#QLlu?aY;Yij9S8&_JqA{13qo$Ln_V7T*l8)uz)PGV?!K%}!aMRJ0000SENniHp zOJxj7Ah$@>?`H*pL~qs}T3J~+e*AdG^hBg0z(K5dg2YkM zS0A7#)K1yPiu6p8vR>Gl5mad#nthXw^q_nADuG8*23K}?BsT=&9t|7uF!V%9a>&#w zmyC|1q+oSSp}U-td-Hhyp@&ZW32*UwNR>bA@e|I7FRWI-Lg5@ ziKWydImocGxJ>=YBWVkw(s#nRTkc#%Tz5vuY<=)uSIq6)*h!X9{y=xjB=Skm0~Dc< zvrY07DFIED4vqNo3I@uUiKCr5kkm{D29pHi&21>Yl1a1u@P#|BJJ6)1x_Pm!14+@u zXoqO=#2J~0VA{NhF`PR(E^OE^J3E^hkzt_#!xD&Yj4Q6rES_0BdzQ-@5;7zvoD5>A zCO$tzO2U4}NCw8UP1x-7^Gj+j2q-qLO#}2uYltu%5q8t`mqC++B0@s79qa` zR>#fBQzvsuj2Zkg-y5l#HR)V2FDxu@)tdky5z=hymT$F$Us*ic?FI(vgSjHnR8?s- z-3bI2T(o=74F`p(byjO+hlIspg8Mf0hEdr{_A3kmRa@-Z7*vGA69fW#BbxeRN!x!8DVr5x)c(HEL+ts}Y3hYO*t?j}N!hG)XYAObK5UV4{C!OL_GX z%V*zxTw>OUoIZUzFZvluA6*-*rc+eahJ}UEXol0)kf<}HAh+d9AN$^)(R&mI#fx>3o;01l`OQA0ElwNg%OV5Cpw z<$BY`O|!GJWw}70XE|d5VqRHYT^+Bgfim+Bei6UHP?oh|V{54+tTEnn(>Rb>Xrhyl zE#Gv{-m;@cYLTaMKIu6tx)h6-E$}y$LNddY{)$VO>CtFldJsg9a$wNbiOZ$I>x>)5 zOKP2BAR)C%fK6}UQ(EI78BIi10$~TNO*5z%N*J5PDxeGoq3=Q_T+hb95S5ghd(7bz z-Ha$!2AZ#P$io=y&P#xjWxWx|%Ocg7l))cFDQW zo#eDo+p}Cz?=V?Ns1hZ?hg3YuwE>l5C~TTlev*`z)q6iS@4H~z!JRU0@+z)g>rh#{ z3qf^-Mf%P*?J|N!Z_|bdq)T{ePk&#SpP!i-wX49xp2A!0H5pHuNrThUzsHG2o-xkl z@eb=W*Hnd2kcWTP8H`1Ci~DenM!0INT=u=sI>14jVD^M)FWVghFivIVCiNOldu7Iu zQRe+$_Gh80OdcX4o$KZrr1oe;Hw=tUocE&%~FiW-=FzRL^*8F<8#AXTIKPNHt- z5gFhVoj3rYgu&JiURtlhDv8u0lwJ8rCR$!u*2lp{<#sz^w%7dn`BA$y0Ipvko1u}? z1<`Mryt{=`EVh8Yx_0AA;f3v+wqLxfppjKC;L~7Sl zh02mbCt^MOLk12}nj;60x}1|{6-D>WIhJsqG6e_huo{;{t|G(45L@(=VaoYGQ*EW7 z(;AnM1R}pX`3U(;$E&zM*pq?5RNjEjBuoJ2u7g>Fu1h{LSuT)c9pqOus#g&$dgFMLnKY%?m}ki({Ov2&Sd z#GChStE*AB6Ewq_?&Q_GYDHO5xF(|~6Y7EhBwl#+eQSMuqfyN+^kRav7=8{rk|(sj zgJ0OMO0q=(F|7#G*sIWvY@n=YSRwyi?7ldlA$rO{$a2teM5xzcF+?{j1#SE(;~p__ zTv-aalcSqKOoDOkes;1|n+31h=|Ew`%zB}5SRkGs1PT=Xz%(5$4Tc4G{ZwQ&B{)gR zPVPdvqW>e~jTr(1@Uyy!K{i&@bDafJ^nxmPA`%J%c1mI^2Im%gH<*<6<@ysED5$*S z&J5XNUhIzmE;|ym8%|bboMZe5E=xfaIro)hIh|_7J0sl`z|I|vpvP7WZ1B+rH@WskB1G%66 z^iP7fopG-8^S7aOTQ}6}XDlyr$=||0Pr?1s__Lb|2{($`9wK*fgj1s^9OK|E1Tv=` z#n{5#RQ`oT3u&6k?7I1d?Hfs?s@D47pb{o?E~c+4FTB<*?$0oBJWU`Ta5O{gk!G zGKB(Idjxw^zV(|FEeFkUrIw~az+K8AXiyy!90zFHmhL3yY2RWJ%iVn_IDKLhICICx zOX3+zKF?b0m9Zh7WIUiI3~->{8^WBc$yzQDzE(&#c!e937JY(!2G;2fhMZC)WiqC{ zBgz@#3~q7CBU1JJ+BD)KZ#DTXMgj7rAc1XZitJrw&UMZKj;BlYZPX8pS4bK~MDNV9=EPni&gqy&bnhs7Ct zru?2DVI#H+hcs+bD9Nd$MQ9K))JjM6zR<-7;v%AwzCOm>a~D`t8o8#35( zA<(1}5yGT(avsMdhw_d(WnLo<(r7XgfJA4_Dx$JsPO|jPF-0kLv+a}sD?wjeeko{+ zQf0=fB1ZpF@YUSqGQfR+%J!h2%(>_?Q={ZZy0@EEPt0c(SVva(+s+3W#1C_sM zh*xlnD*;emFlmjBhjgISnu@uu5@_oGN`H#G%1Yuq4C##bG!WUqF?7>?mVUpEXkMF$ za6SP&^)UxIMQ|aqXTN-xfvnPDf7T!|^=3dH^;VeoBos}-&aEox_ug#C_6FM>W7Gtf zd=FFUid$9b{B>D<7vU z0Aj_AmxfG*CdyhztC9n06}Un4InGkBM*lOLF4z;HZO5D9`vud}*b0oIL_4Gz9%0?b zWE`e0N+VRw)T6msB%m1}1*Bk9b0M0n$Y3!-!Cyt!HOj@RRYBd3oQ5wYw@z&xS#-G= z<{4;B&VP~q)Q_S@71O%cv>lL%(#MaxJ)g8e3QB@fLbHeqFdnH?7qJVd5lvTLmklK*Y zWs@dkdxiZMGCER!Af@FZ5)7SKX;kaZOV zraIS_MC6IH7j1P`ICNA@$8|yV-A`!s5CR4^edZk7WlSFe`;a zVd$N@bu!uE3jshQctN!=yialFC?xRZ(y$!Vu)aqUq7kzdG(a{WPIz1#q`%l7#Kc9q zPKx1R^-kjNPG*Riz1jcQ+kPcQs_NhdV>F$G8ezb*Qe1H4L^4?3{Rgph1ma|_VI;oJ zHDo89EVxon`@EL?nsOJEW{Y`P4`TBF8t{(87;3q^c(e!%W5QZ0AM)KK{nDL;6j%yqE);XT2@dphJ$o=oMefuT9G12 zcG_ur6)`mcRi-EdL%M>Iv+gS`D`ehkv*g;w}isUtx{}LqIthTR<{d%%{0sPy3uHUW_Dq2G&_>ca1dEt zT3tT9w7R%LXB%qP^Z*fzHFHtQO#QE2W7rWL+GF@l`Zv(yYH^ZYC{RX&KvDHli9@MW zrXml@KuiG$IbI2TP6HpRa}tcfx#(V=GU!6gq$x8#4|R|GL_58^dDXB8r_2u_RZQyRGw->}Lq&Kh0#7>PPR;|Nx^&ApY7{fMR6zoNxLxdFA%_kw(sLVOmlh2Yj9?c z)oWp2(`K*?XXce~#We0rGgy<~Or(-W29{d+r%a*(1<%J71gYOGV>dLOjJe`<+vc|% z*m1$7ySH3?!NSgs>$hx}+pvB#H{!Z#cX4Z$7njezxw!bosZ%cIUy=Zt6mMdxte0lrNfFMDp!V+!QD+CHetU&MvK$dvGGe;q>ZvDgwRl@ zt2k-vc&=#8uTZgxS14VKC?MrBh)J?h0g--&8(T{Xq+xr?2hy=9l$^T~_NIS2H#I>> zD*iimSTOMt9s!mW&$DOPh$^#4REH>96$9SnR2@je;^5JP8H=OCEBF3u~C2K@F<5$OSG? zcZtn=M1b|MnH8Nk;v8`&TT-c53_8iz$bd%>=4o=M}JY^Bx6ey%7f(WMJ_J3KSLUitnvN)JfmWiuE z!5kje?ykkQ56a?guc~P#)%wu|S6sON?T2^YaB$rP8=31a*Kn=P<3{m8)2_cugCJ$qNJ|sgZWnf8GNM{be?{5hfzkw2?=m=S z-5x%Av8164gUzM=i@EgVRUqSCwUHapPdP4VLYS8FYv9nEdsxO4la-Pc}v)n8nE{jnFH`LpjG zz4zIb<4aXlwOKDsxR@Yt00b$9=eV5zprc4ZRE0`7YAVDPT{xxPeW+dkJ(tqqEc+A3 z3{EP$`6l;qdlgoe96of2tk|EXg5Gz;V!1bHyt0x?)RT#gwi94$wYEYH(8xRbNl}jd zuS(lX5q-rYWHVef{LfP`EjQ$(@hhy>n9a~bCiaGjkmzJXXjrKbGR6W#!2(|T@=l!h zSEv|pWDGV)3<(T<`mqRBmvZfb!Vf)s@AG&GZt%qK^^NGj*Sip|Olm5tDY(y_OAB6m-$WPv%^;R}vi8s1{D%EEAHML~ zi(Y@?#izdV(2L)FdiCT=pT{U=9MG%)XF!<0G;Utst3V;D2aLO?xX&eeG26}x(4i=V zjEO=xcX8RnA^~(UKM!6*B0tb)Xue7kdqz9;^O)FlvShHg2hay+#<3=f>mw-~oH?A~ zZIE9|$LALXJWZqYK4YY&Hw(le0tW~3xnBy7ULkGpj`vb)eZmY>>;2Xe@B0E3ifw8V%^X&n0Bhmi z?#RoQ%p3M^zVahC9(vDpym8%lJdugi?Z!&UYf)q^7Q(9GIp?s|7Q!-KySHxhrc2*{ zWZx~9KK_-5pZw|ri!Yz5xfa1ZMbQXOx%`~|Dp`gJ*|3OqW>KFQlUPNV%Q}u}iM*^9 zNE-|vzn7<9;w|O7=$k2K7?}z)_Kx260P2Y}>vql`dhhj@fAGeIU0aze8B--q8^KuwvF^+%7kabU?2ny`<~ zX{qw6ZTQzDUV&wWPCwCdNs-wt;L~_sfAJARt*MJ#L5UbmE$|J4^wSjR%dR?K9w2Lo z2NUEa^e<%~lI1KF?~?XHBo#rLfymOz^Dx737?umes;Nn9EgB(&(V&QfWQ<6tv$Zem zB}qJ`CxlR{tLfj%8BTuf^t-^^fs$F(o0cz7)S7`^tzsevu$f%3HiCmjK|&v>nblC( zJQSdmt~R|?(kugTfTYT|K>F*tIP*NuRr_zf_}ahpwrz*^NmDoDrf%BpmGW@+arX}A z7aZ3ifp(oX3t9%H^NF*nYW?Gg;HY6RxO91K1s1g7P8lS0aUYy-T$n@Uq2U`*G4pQ`NKU4!-l6t%r9#{0DbF z@s$V1$5)E&;SCr^F~MmF6v0qU&-NKRvK7=yycPdlDnZL2|5;1}{iE(cH8Ui!4oI)| zk^;qf8L@U#|Mnmm&H#%UH~*wkThfjtuvm07JF<%kFmuBG1ah@(L08MdI)~>=~wBOB|$fZh9Pa9_*@PfF4K=Lr%+wX-FMS zO&{a9fqDkvS@JnDef`25hhA( zj+bqAoi!#Yk)wH!k&!Cm>Y_qf*OGAV_8zK)_Ddpz!Kb;ytqPdkI(Orb|G=RS92ql> zS0>6ABY7yZjByuu* z)lJRvD>Xx{<&DW?8jx-~TCtgUG8o`zI3Ch&b5(XxHFP)+nT=|&Tgm{*vWX_PQ6hxK zojq(XMG10n17@BucDy)60a~|BSig7EJAV3um;B(hlcti@Nk8_@+Gtn8xZ4!wNT|sL zd)SF4dN16l>O?=M({kID1I zWVUcFqy4?#u@Y5f;{3CB^s+uNJx5tH7 zR8<7~H#Y)IlqGb&#VY!fqr~0sFg|Va(Ap?B(oCcok7?^=d*1#xfB3>%E|ru|+G_s_ zSuW&qAK1{;^J1YWTLB_qX?ia7<1Nr_GEiFzm)KKm+{IT*7;hA;r+j>EFqtT6h z2MB-yNN@)z&4~OCNzEuUBKfsOwx#g=;t5Ci2mdSn!4VTNmcj}-uLyr*YDnYPCD*6Rh6I2s`DCi2=;sL-gD|yWo2c4a;dCZDyHN+c{n4B znTwfb6v{#%8>8?mRBR;?7#;`PKpzltC#sV-*it1_bAXps(yYdllqG<_sGW#wQB_BK|-prIHu@$kOhc{JAg+ zhhd7tC~a~^tkez~*0<`zIj)hv^&>Nlq+Q2sbVAbkQP&RbN{OUYWDUTDsg~KvYTLvS z#dE{3Z;Y$iUxFyqaiowRjDNVK*tVBf#73srqt25}p^TQf*;-KgUm)SNgF8R>&;Q}U zCm)zj+nyF(k1!FZVNsd?ALx%UpbW$}*ILKtr%4h%N zcklSZ!_(QEx;D{~GZrVGwj8HGo%|-HjR^%&j@9H^TV%YZ{oKid=yTq2+rBUUi?81D z#9h+FQ}Yl7BhbwV-B|M(g(HM_b0nhc-HNR$d26Sl!M}bEi3-8&@3WoNxex+7sn+vK zCqPkTFf3B(hl16tl8H-bMt*`!hs6kA?=2{{(zvdoHGog$aVEiODtsrD%1)*_Yn&}r z^5m(&DhSA|_@Jna511=vFIc>9?*?ytff{}I9@=-6g}ltyRQt@?UE*6VicS!T-5!$A z5Sfx*Y?iA+IHIb&BgNSTt`tVlgFwhzJ_gqgooseZKJ~{>-TCED^q!bb(%Hl~u_>;@ zS)8z%10s395kgYQLN8I@K?A{)oCb4XF}Qoa`0%Ix>o14B%M?OxI91s85U>ZYh9|!D#e09}(XDpYwewEqUCim% z73dt8Dv^bV@K^zHLQ9YMWSgQVeXCuB>VY^6hi$aH|K8ia^v}P#`@Ve(9ngUC_H7%A zA}Q2ykync`v#c>%tKH&OVovxLd;Ex4p=#S8AV~4Kf22|tjA1SbxtyIq6~GqmabxUM zusfhU&Kt6IfZl|v;PP^HuyR^Vs}{m`RLJB%)6jfXQehMSGt1cARraqc#n^V!5yIIF zco=BRg3t%xqy^BX#a#0k&XyZ!cO4tfN0e=)vLZtnK|h*+nzYME>g7$qJa^xeC;_`q6KErke~Bx*H6%?cH;JoVDYL(kEi_&Y`ALZ^|Ffqjdsi4UH%(x$SkVexw_jCEy41meW)!E4=l~=0agtE= z64@E6)Q`m8jFpB!(E<%Jft_PKQ;Z%^_*7s3W*T$TBc5f&3F1d(3@pYDz`)7k5CvWE zCkkw#0R9;#XsR?K0*H*m!lo01WHwkpiI!eqDc8yv@GZ=PGfWa%N4 zcfyz((Ir_aSLk9l@I4bMcf=Dy;9pNjZhLjaH9k`XW5^PlW*f3j22!5|KEL&eyC47d zmw9C+3@0!ruxyX0(jhX~Yn@QrNh^#0v|*M(WOuPrB^AX((z$lxR8~%bmS%bBiQoVH zZBN|Ob~C}?U_yny%9A0tFpktiQ6;p1ypRl>Ma$8q7NPrGgx@Q3WCqMO*kIQQ4@Zv^ z@!p3HJpRXDmfg$!(Y29?Bv3pRT-a96g|V%y*SRQf?Qg2K$~E=RM&29h(U2HCC~*N*FTHTu!2b+ z#{>z`2)R`5talaqL;;>HFF{KToP^LXMMPr1B8*!k#O|^h0Ha#TS&Tvo!ih7<5S!^> z%zpU(hF~YLoD_VmBB@M*ithpR{J1S|Zgzt~t6TA#)|1zFm}jhRgqbZ}H+A*0o$GLH z7+h5?`Xx$_qM}CbU~P+bd+~p3kcEd;$*BWwgw0$OqM)C|cv{d~3^3r&`q<(1{-r7&Thbi71ib!oD;wz9HgDJ(V7h|lRA zAjc3kr@hk2kQSaSuRi|!U%2J5J9YcFRjui{amto}nv463DfY&WiDwS~NevUn8!9rZ z2K-38R$wnBQ*YE$t2RhP4dAgYtq5-_iF(RegZ5m8D#vLk}045pGB|}xvo#gRyy4NQvgAyeCLHzc=+*B0;LsBQ1Z00U!Wu6wZZ$3_ao})DPOx!i{z*io> z>r0Pxb8aYm=|Kt!X?1}s^IPv3^Yh5 zalWtFCmKP+`g~ln%%&UF-dfTCML5kt22aF7MJ}w^9#YvHGvNj# z|JwL>W&}e$apWS9frTa+<~sDZ?!I^5C;#ae!t$h>w#i&%*h`fX|5Jptk7zUt=2+(0 z*7WErhu-_?Yo}j3vU%oeJL`r!Ltm44fES?&D?3+jdFa;rfBTVpzx3$Z!M(G#rC7UC zC0h!T=swRr?%Q=Dyfk_28=pP?{NZaKo*xoXQhu5M29XMyE%iGgwqvq}SSBp${ec>K z=|_$= z8=FUe^TDBKUqAKQk@YiIx?v&+CXSo1)U51W*?0fJ2fzI2Jx@J))9w3bZ7V|+QM)>H z%Rv6L6YitJcYo%=yPkgdgYUl5gr*Z)7DR!U19(0r9Ot|;F;(fonZBtxc5P8sg#>1a zHwM%48i&}nzwuuw4GiDUEOm5QmbQidRpUF;ZelH0UcnA5x0vZ>_K}%#Ii{DzC01p^ zZ4>PH7;utO(b+8YV$qkj#=Rn)WDa zOnq5%AC+^;4wu?sly=ott4vkhAzZ(NY=W679Tk6~&gP_JIU$bFO}aAO6~tFa5=jn-C1iB-fzJ)i9(a z>h_;Fs#k9ieaZM_R1yE!w5lcA5r}>0HA`Nw?-a7J| zzj^V*ONXvsSf_4*7@o=NL$C-!q z4sr2y!j~eZy>XD6WI+TEMO|N0*IER5RddcIb$XNZf<2=$j%#FC6%*0 zQlyWHyR{uDk&`PtRJi+#^#nJY&utn;gq5lMWX4tO8cW3xQs7sEE8AQ}pGWVbA~~nh zaOERO%L+G9GK8u8GhWjesb-a4<^3+&$i*OJ@T1)O)F*b`v9Fu8cGnQ(R>Z035n%6@ zpI+O1;s5#Z%m3!5TPLnH98wj~-rEy5j$ph;a`DfFxM!9t(|5o7(sO_Dy^T{B`-p?d zhXQT~M8+%~@4R#W-A_MCOHGP-Rz~fI%atU-%G`+0jiZ_t$Bj-(N~C%69M#@oyPBD~ z|D_YU^T`MH-hH5(c9JvCG%JMR2)oGL9d}z-*Ps7CfBe$Fe&+g#D}hXO8a4u>9vC{` ziF6G!Z?#8%{?1STm+zc<`B2k)+mN*fMprnP!ZKYaVRh-jfAGm>*K*SG)ggM89%WX< z=v>?mxLHFEk^x51Y))e(h@X^eYc!X*dfmW6fg9PUau?kLp{C-o=#EDyQVKiom(W)b z;6DYa@D%IL3tL+^sfyIw*n9Fpx>P0twK zx47C)+R(|dU%d1D|Nf)t*=r&66)$~y4DfCeXU)>&o-aMTa`O%@@DOH^XAic(OtaX$ zaxqgcnRtntAVbvW3=2?OJ3F2pCn-C?-I7Du%wcKQ%H5xPI4mzMC?q*CHQT(AVIcEF zWMlgB|Mko}fAu15%qhe(*!t?)A=Av@fT7K9$j0ioYw$u1TzvP`&;RUur(XH6X9CZK zNT(qYuIsw_tUK_~9s3@O9MZqkN8N61WT3J9=51GU^)Vxk)mY}?BE@eHH ziiWmB3F78Dr;{4d!~nC!C4;*5ve}7vBJQUVi$z!&)N6@sbYLc9y-bkm&Db&|n>!$H zIy{z#ujUoR9W);!_XTsG4K}=iIvo^5*9#`Y8+L~nVfHMEBJ>=VFuTtk1Eoe`;>7AR zt3Je+sUkvHZpA@qnpifh*-n`Bp-wtyl9+6Czji#g`5hUeR!lpe?|b0j-g^$Vvzgm> zAi07`g!X|bbR^VvZ+!2ix4-*RGwYEgb%4Y2+#6<$k=#VL{@1pnE_8J4xp!XvchA!G z`H*iujI|@PAt2hJgNhK^f8W7<58mE&tqwfXj5YM&p}E&B8E)p}&KTd$3DoE!RRmnS zEbjg(uz8nJWau_9 zTcZ;z) zmG;1+cdXpJ*0r$t@8c=JF{d0Bi!c(}qY!VTvV9j>t*brc zd@~c63USGRF9LO--y#pFG|3qY%!#_Lo9tM=<)J&;K^_1S(vGN?dV#>VE^fa5*T332 zca@oXPtHK#1OT%E0A-Fcp%R1U&7%_cZ)~-1e)okdA6+00GQj`zYBK;DK~lwFe}1B~M94Elp@XdhV^0uYAx< z0_DZQz{x6`7az54x9hgOd+)oY>$(_uWDtZlphRdSqd!f#ebm6p6K8&f+16lMfyPng z8d;ycltxWfAuE~1D^v_7rbfr%>Z}O@GS2tqcZ$K|$-qiBR@W69s#0^#TWYCM5&Fn} zMQvqPF~hKLSLqOHa{w0ze-YYd8{pWc-*Mk5u&sJ>I7@a)XGCc#@Z=K=WT`Jt_pl2Q&EH~;=XJLMQwQrk}6VIMAmNJwdbz= zJ=$V5z96$EyMh*Io#^-rADn;t#AGs|#CF!SvaILP5g=oMNE}nQK7Id(uXP(+{hqR3 zCkko!O94&4+&W&_xq9X(hVSYtkj{>YhV7GZ3E!RpsV;4zBLn zDXlKutjhhs15eklAO6+b-St^K7i>h{5amjPY?34E=%IH*%)1YtedF56i^hFstpLtj z7&=5WLSuX@;mUME{-8V1q z-4TQu8uI^0t*KanYVng z7my>D+T$Z#*YG1lG@c4{tJZG(i@@x(g3fd|$*CAu=`<6}iya(hbd6*;2#C}q)=iq7 zjIBo-D^22JGzgqTylYH|Y&NhIqcxWw5`LjzKr9=wfbJ5|-9F%)&7ZjI)FTAcpkOs@ zkG@@p%hcyQ75VQZhMo0Rbch}Md68wJI(34Up%nXu{t69;X78N`nw4eU3n3VJFlZq( z;nK157d|)@LIVw3dtS3|OaL&->9MqgK4bmL(er2CIzDM8BH2mAx0V+NY~XDAyajmAo%<&%OM!-?&mj709_Xb4 z(}kmFPQU#Tb<}lj4QK0!i$PiQn8+Fki~puqHjlshVHm0q_2GqVe`J*rMRecUJ$Kx) zylX8`0|{WYzDRyYCX5(c#P@vMFAy7K+E$ng$U^-v+N}mO;*^!Jj-by_ydf9y@#0)i zy_2$LI*NxVo3@GmS;wcxaVcu=xLEYIkrLD(DN5K{L=}%paAF36qkNg=B>V4$p;& z3FCtO@mtx-6d*!;0RjQ+H7H3A9Gcaoowx7hrC>3eLEdrCV?VfaADolR%-v%5Z!IugyEQxe&T(!jM2{1hdDLVW3dEDp?AU)(vpR{!R9Yu!?_lh~s4^pa z5?paJtDUn`nvCE`X@-G_zaO_U7TrHa9MBuKc<;P<_wuft@nB~ciX)Tq z03=uD)yXmZBfu{OYPOTZCSSuw+Ke+K8TFD)A7b4>HFlwH0PQJ8ev0={fIKlC0rb|# zBbJjTVfB>NzUjjeA?YQmj@Epbvz~0sz*r~Hfqpe{pW984;K~Ri;6ICXjPwDmSs`uw zqk`NMR_)EpY#4h!gf*OOU|=3nJ;`j$yAr76$W4a8Rkkbs5REYjRRthZ=^H?M*-4L- z9-jHGmMq}kfEG_|PKx{%@ITV>j^&*P_N4rKHfYHVZ1LE9I=}GYDV}ws$jqo6U=^>2 z3cwtlNOI}e+1bW4gpd+Y#MG8mLN1zI-o4hWObAXENH;3{f>>ciSYRJ;2wdW+GVBJX z82QP`9vs2uEWnhsp=u~|#B2L@^&Vl^z@?X11<;i+z`jqyV9v1KNg5Lb_n zCdH6*4LH_@3gOC$^Ow(FkU*V=2n3N>zu7OAu3p)(wz_8rg`uic1^)}oh}a2&T$4gi zk^=x~G{WS3+(s-FH4i9mMK^~s8N97x3ZS>DJQ4(qx-t#&XTTMV;UX(2vo4Hvqy%er zF`Lrz94h{DLiJsjGX=H<_Dk_Oww{u5vT}1Yx(}e7XPpj>gQ*EKimMeMm^tnLi>HD2 ztTt?J5zVC(!0Mu2QrpAGTd@v`GI0%kTV1+Mpx9Gi&~IXLGc7bOtP6wuS)7p~o;>5x z9oNCsu1P=BUJZP)w6?Ni&+ZU|!ugol?0W!p8^UyRYyHe6+zx23yrN49?{CuQmDwbO zuzC5~^x9?<8eM3bQXv4`7Qd|SUYo2f>mC-dZ*eS4`L2X`)f2I8?c(BcL2TMC-m3(W z;+xzV={jC+mUr!-WbHCKsdV-SC=_Pbr&mv3WPwZ%FDLAHL@3wJFWH{ZR_W(H@bv2D zwR2aZ$4QbLtemDT0dn|8!q<;Z1)W~%hfeQMB${ZeoRAh;u3XCq6aHpPNM zpw_Emrcsa@QwjlGYA^f3GL9B$1ZG`|_NFQ@0u2pvMQqt0Hk}=GqJdp~cLve4kp9tn;XHtxEK!|#jxvm6 z<+7fr$?P-72qg5u9zD@^RjkYqMy<09V%Wp9R69Y{4cdxXp;$CmdAEO8{7X$SV$dd% z>U@xw(a#9u2V^rk9j*0PQqX-wZqe>Igx|G zT4`mrKBeJYF;-T<;Yf!24Tq(Pl*f|57+`^&Tj<8Tp;xn02vo;>bJ|X4*`h_=k9`$uxu8WUY)&b%`fxEp?Yx_B%>(D1z$s19 zzp52!W*gJ_Y|gntMQx%oSRhcj6=Gsg(83eta9fR&ZB;WV#0^%9qa@ROh2(VJYd3Fa z*JoKp3lpR?rDF$%%AbK{I`8If;%EC(F*;9O={_K6{!l!ZuC9}z;J>Y$XwMj&Bm%)i5SG34R1lk-3r_U~t4Mh{l25vts9rX!|C zax4~ON1m1*9<888_mHVDI4gUg8$G#Fp_R2FKaQ`Z_N|R=i=~AA3fR^ehKMxc+(P8W zy;%c9nIDZCA8=N(nex^jp>Dc0b`DuCgm$Jguq^qX$UOOb{4`h4^3RcaaIv6(KCw@?#tL+cl>POn}c zaHW=Zcj@?fX}b{ErA{$vTxT)Wfi|wKZ(O*{LqcyVMr6ej zWpQD(D>=x{j#f6%T8bD0Fl`vd{uG?e^laoU%z01`s5$f-TZbeASiENPlXDDGc38~Y z&0+^}MVU2N)p5wioMvRXbYCH>1wbsAz-|%=CR=Mn!NAACe$F@z)l?`G#U(3`@;yc3 zf9^gb)j5_qVPHMHUmPr!T)!Z4Zio)`LlMsLLx!uHwrAcwHk(en=>N%`rrozYth&px-|Ig7N**n_l|E~ycU`!9_gd;%VFRm|3}oQd}eYl(1#zA#-W@lDcbdit}ICoW=86*unULiyu&`359^ zJDs=F83`8w-W)JvY?z2(hdxe7y@0R_<#w6JG zQV%ltZ&;bk2{PNoen)8#I{f@Q7d|@EOeRBoMpE%#bq2K}p*{P-iKD-Ik2Jtzuug@j36tY5e{kXO84e*;bQ?~0TueuoETS&7S5I9$ z`rNxTmlU&iB~&eqS*He|OU1e!mR!!u3?W=Qdj8mp?}z^UuV{r(K4rZZ4Z=+b*DqcB z;F;Iw*JfQVLu5CiX?Iu2arQ5NAi~!6P~WNe8427e@4{L=YRAEr9?d zO(5N(SV3mSJy!q$EM>H7hL`boHjBefAcMEZCfK0*BXmtz}e(F4n!JHLPY{sO_aocofRxf^1NqSxvMw`tGG}Wo1gAB5py4|rE~`v zF;ax_@>y~uRNGo^OwxwAown1Bt=`ecc~gjKGYuPsggOA2vX><>o7yFAaGY+gL~JJ>!pV$Z~^)MVoM_1Wgd^?t`_EM%^bE}5H$2Yjk}4xp#i{ zzy9r&BWIe1!i4(l@<0?OlgZL@xP0vFbAR@|6Tf^vP>9>05;iNwC$^WYfYj`8Fe2S6 zS7SyH7zt#)urR_1i{$0CV3&|I!B`y*Ndw2O7?7K7yJUqxAJf$rccZI^&;9Jr{`&CqZ?SYCDPZb2P-rG$ zwz2ubU;ga%|MaUcwShB_vz4GF>53)h?WU69DUC3hsqE&!dS^y4ui9|_i}K#o?uhdf zWKH7Z1%uC{{#KvEYE=<*Kro3`s`FCTm9{UbkrXY<(QAv33_q*USpEAa)) z1YMqm#>yE*y@Myr>7!@fy71xYhrjZ~9iO{@?ckot>XOX5t7k5L^vVYx{N&Az56(5j z9q^L1e#{JOe!AngV%Bo^bjwrX(kwysO!g`cz^s89<*`73ANJ@(#DU%&X)$)*c2`ITTykXcvBvaSgNmx)FT8Q$+5h=J-~Zc>-S_k(yYAkY5mIP`S(w}|Lhy5Up*YQoKGByE2wk{ zpfgD=HN^FLpd7lAjDQg;|0+i4VE90Kp%hX_4WhGF=@e+q1UmpzVI!Vb$6)WsUdr5wOK%U+KV~S80gs*;h z?v;<8o&4wLSN5(o%ad+8zkYFJ>+%M76q?W(he~}EB&GCseE|#|ty(GWTtME2n~kHF zUi){yc-+3dv*6(*UHwF&8>^;a(&)p*hoYM8N~OA))A?6 z#so$DY_Lz6%@W0rt<-HJUkLbNVB3RjX#BAE!HSDpmBe6j2S8)i)=)$frA-tV0s;Xg zRCEpXUAT1O+-!Z?lw(#^Qp~dP&Tc~@z;9zZ*gi2CCy05I=n>?CN$9RmB2rBcV6)#O ziT{qM?JgcVxqfba?UtK5>dZQ14{7u7i9A@HyH6~{W8<<5Ep}uiX8_e~G9KC8z+92F z2K|{+#PJ?4_-p2cVK@7gjBsuyH+!2XeUT4*?{5uVXfIvAc5!{cfZS()N}x{Kk%E8) zcB(-tv`fm~`NmRTLldZ@*`>{`iyP4o62fHBpOBLG^6DBD^33241}mX36tZQIKP%fb z%ParXZ9yfH8If-Br2@^c*7#ivoM?J+ee3+SVF(;T6N1FL|Kcn$7jC`5B^7`(a%242 zA3w(3wb{ni^BWAgi3=mF%j0m^ z-Lo(aWG|W$ZnaL-j(}OT_uVksQt7xA3w|5s@bz#=^|j*{&wqI8p4;|zv+M<;a%+xj zJrBCe>RW(FL|`u1%R>(?#>a(Uz3Kt=-7)q$YfDBmFur5Nd;r#GuS}u8V%VY>rJ?^~ zJ@;k*vynVzpXp2L!W{=YN(wU&RelPMhyA^Qqj2bR%-P&a)pcCIO1Ac$C{Nw9Te-F$ z$4H~T4<%8&YhyUsF%UN)dw;uZZaN4d)*LsmG~OV7EUNEckyhvv28z)~q)Sp+)KvyQ4Cq}xrhBU54U0+oh!W z#_5J8S#PR+aDB@&Z~@@0X$jC4DG@{Z9s=cy0G&jDwOOPeRB*@tJrPU=oH>7Gh>@Ww;rTuJ4NDXW>IH{VFn+GU$w+E)R0FRo~TQ@DWesbhbw={M7%I77?mhBJ#I|fm| z@xT%#5!WN>hd{)tau!r8 zZUv*05k5Mj*r~Nmi#ZtaL7N6~hfSc@?6ctHLfVF9whgem5~&oL9ZG>@+my%UXF{f5TM!C?Cg6hJX?Tr z!TBhs5I+=&gQ|KP#Lpt*YlYA+S|T7soq0u6@iKCNr4~9B5Za?NcN86xyGIDm z`2ac5zapnGR$3855b*qunoWf%=G$Tg#(n@g$%SJzaT}XuC=Qa80@Lsc*Ev>;C+W5Z zbvO)YGZYW{HG?HpEJWZ`EDK9e=eQ$FM}fncLnlwZ@?jwEvKv-xqHRUER@P?4J`-|S zusYM3+471AA{IWiSas01fxE5w`#*hc>(Y7{>K1US z5GNW7W`bo&iXIj=MBrw5i9$SZiM@c;-F;d3c>T!+C%I|2psZvtG1FuOJAlb@kA|VC z*ratuF3zH%49c|$#*n$Vwi(*2?-?+C^02oWUx4ff$$7utb76#?AfPeH&721%WGo%3 zOCiDDQ6=|)r^$GhC~cSl#CjY zvD85OPaJh3Wdt&cjM6Q}3j-=wM>%Nd8*Uh^$Q^Y16s&|hL(@7Jfdh6)WnroIPQ0U3&U!uJ8E)6%Ge%<5jt=Zvc-nxGF8V6EH zR9?EMxqzdUQG0)!-ak5tNrUlF3WD)?3k;Y@*+nS~K>MPcd8+@46uL?)ybS*~p*i=? zv5#JOXVNr%dC=i>fZz`T7tor3P6N~O+A^n&6(udyIbKrSd94UGno%J&@@xxv20_V$ zf7*8;zYD>BLjiJ%A*NB91;R7K(A&hOCWw%c!1efe8Zj)Kdg_$UXvLvcXgkzE4lq_> zEiQSp4qf#mT^z{oRu9*A{AJp(N1vt6LwydEtMwcN0mE#Iy7)=98@7YDN&K-*p{P!Z z|AP~Y;y$CYrVz}O-v)0c5ycQDLP?&_%Zyi1HtM?o6r|cRkc^z+z3>aw* zmT4VA`~{X18#ytID_9Ypq&1ik&TBU2I$*mpjRGE1MR7QNF@S0J{*PYXJb7`_OakXp zC=ANPygnDzTFgfimUpl9Id+LtIU|{@I7E{Wrw2N=@bc&UywriVU={*&J` z+>$cfoaB}9#M=D31?o&*!A333&j0{`07*naRMa%QxF9#`kMWTdSgo)Q>Pyev?;|lx zmb!=?#OOc9?#G)kg2ILsu|ZEXi+{jSV53?Ta~emn8?JvJCFzQNFonVD9oyz&6+&vJ zYiWwK9{OyBK7DIxb(s@F1^b5p8AeA)@^19E2AW>kc>C{Pnx4BBLMSNYQA@&s;`{14 zuNbegpSYx-`8;n2skaVlJf{#;J2D1{0B4k8gj{%6qb`%B<%{o~e)I2sP1AW_CNGe6 zxaVSqc?2oy6*x>*b}ke^7!HTQQ;!uHT3h*JWHm7c*vc_hN1fLddz3?#etvGPAyOPTIoDfVFla(o7%@MHZ%~7sSpz|5Lj+-NBNnQkCfU98w&X@)&wLoeNw5dPD02BVmgkpvihy@Lph7_$b@iHMw4 z${q?)FO(c(F8$`{HHS*%}78GuF=B)7$8IRHp-Qh`04obW#?`xOXAYuh3ewWua8F3a&RWFT< z7+hqG-7zho$u&L}9|lCZy1gSBLrheNVQ7DR9K%h_V&CguD^yXB6+FaDZo!mqbC%Gy z#1|?2E50#SneKWF;U0(&s1@xzc;gq7xYzk!+u@1TI>vA)#fyJ>_*o6|-e@wV<{jMr zO3KFA8`h&P$$8o*$TBajEvdxzM9$!@$gLxDuHr}pU0!5z zx)RcKeZa?a+L%ZsR!(j*OcSPcUEm%yp-E@6V)SvgAJ~N?mH#4pDs!?YXql@GQ^080 zu^+4J%y3hO=N*t@)=W1c>DrD1FYj1wrR~qt6k%0r7Lg4t2kN+)G?$N_d-dP`Y<})~ zKBkdMOFs!!0izC3iOp+ZKM-d)qS7_fmr)Zal7JI$2S5=tu$|Ctt|Yt^<7nGnB^ihZ zB~>?0Tz>8^zIW`U_a{x$XJ7_NcjWVz2_SqB3a#wewKiE>5o2xR)jQ()IRAX!c3nr_ zPgqFJ)aM~FNi}o1;?g8oL489F1BMr9Q?f!cn?Hm`VpYL2{HTH0%J1eL7Iy=0JLdwz z*5l*4QpD$q@5CGt2a;~=s`=zNqoM*=F)1|lka=4i%u4m5ceaP>1~82~U|5wyII4;k z8tWJrqfkDHbY_Jsy28g@#znW?r?yAnD26|hcoxmDVimXyh@s9TY~NOZ*gP8W8yOEI zHz9kr3*g2?#z1Bf*_ux0Z7X8(rqbKg8ML&zyu53z>jbcz_1CHoYtex)p$msk{^HO7 z?#c&en}swpdZHMv8zpD(j}Zc{6G}XKs$NKe9%fKQw$gAlO_R?`WZbT8p{jWE4$`u7hmCitR<4$`phu!x*>3&|Bo4c~P>Z0! z=n;`qF8MoVqmoK^QSTAH5fUP*(laxgQaJp^QLa zU{_JMfRx6Yp^1KNy2vK^pwP{@EDGu1Uja+BT!@x6lm_l?6$oh?OgclWDDYT5m~BVO zaS7+#gKPs9R-qfGMs^ir1ONC(Gz~Xdq{9KRx}72keG^L4d6JFIjrnZGc{{oiL{YJH zvb1w$dDjXVH~p|LSev@gi5vVnesXK;Rk6h`IFgQ^oBM8DW=21cYC!Kyl!ZLTB_4Q-7hP7TFm#SB|JQFDo zMA)5=lzPCa(lb@hCQ)P~E{$Cb_ye`X_NQodT(%x&Frv&XIHgUwZ=_$OdQ8li?S*9I zq7;_19`tbwPQ=2Um=)>B0;on}_W-8nq`e|NIu=bvv18XRu7jn?k_MAe9@we|E4z4y|;efH`H=Neu}|EMh_ zuW#k0YF-(-2k;Bg+JZQL3bBN2;Y~7as@pt$9rVq;iRQMHAWLOSm5`F!ECDEXPK<7GI28dVf|!2Rw<(Hj zX+8Q;YSB&*ivb2>?VxU<$^dWNyK3j`hX$B#*Q7D|b+wP|u)I+xYL_k#%!zRJ1C7ND z91}R3fPG^^+kxfr{&Hj#Q%=FG;xd{7{sSRMdT_b_l%Ny*UFn#RhgP+o76KtZWM8=L zjOQmbda$~-wlD(5pTY*Dlg-V|uA4)>D^U5oz@vT5R^EAF&(i8jk~z^$GSqVD&skc~ zunFP%g=;VWzGI=h=UO@__uxsS&845|7OY0#5S|(~w!EgM8C+&?0)y zCKqpph>*vX@P0-pi|NL-kO2Ft*?AThU$hJ5wBEk=-Iq_lb?mWkeD>a_9^J8j&#YTq zy-!{<>spbf3A=CG(@_^Bm@ZoAEz+&8uTN*w6w@01=SZHWr}s&%tgb9Cuk`g_fqJ8h z(Q6%s36WxlN?DSUqbvrIS{PlBI0q7_%I^hEXHYOqW`6_F%9j-+<+JL$Q^z6;jGxvg zwL?uLdkgVx?A{I7ar=wCh$ThMeoJb37*d{)QuAe%ST{?s^eaM|G;7k$ zCFUsGOcG;4%AQ)%G~cYtMaWrYHq#*LH!jvkjs%EbmSc-K?}b5;go0b!PE;|7DC9>B?c z;<`j6D8uCj!zdTOy??uE|K(Frklyf#DE^AhneM<@I>3v<6=`FlAls;)B=SYIcY%|n zV%_)^li!AFn7wO_^U5+^ghh(7BlCc>%e|8goIjOFpZQBXaxuS_3b^uj+I0oYBr$L- z`A>MinAbxEYI6z}HBGo_&rL*-KI9O!=G*j;uxl?~x+r4(u`IAtsm~q^;idyOFEx|- zyz3-Tx0s%0f%&;>+l}*=&%Ax~=r7+p^ZL>D+Ez0hzTI_HaU7TOCQ6*dK#uHrjvV`1 z9Y6+@<&w!(Lo*lXXi{L-a=JizB-sGQl5i1R%z&Z7wSIh2h+8lsIVq^ut|asJ)T-6ynF5X=9Q2nc&5it;(VcyMb#D-Oe35cJJPuTVu+>5J4*GYQxE5H_22XlP8g=eMH^}=Uf~p zPnP$@0+uD6d6jorrZ9U6ZRKU{WQ$Q0j%pF^boPdL=p8ZJ#}cmIOp9pK9;~l2L6Nr| zAr})?>I!^s=6)^p&e$?Tf6BE>o+QPeHI2+9wzfH;-B+C!u|TYRD|-VgIi!U#&oa)i z6elRy6FLeUlj&H8vL&aF+QxpVi<>DH`mJDJU<>l>RF)-N1AfAqBvPX6Y|#)*q^ zecntaOw@L<*ryYWM6ORp^LvkoPI!{BRW21+lbIVqC}^s^;Nh{cI}`VJYDPd|F}P;( z-E3+WnvqEcq`vwnbdzlaEbBA)YiJ~%LEbDiBy{EIg{#Ld9s22;J8s#%`_{cT9o)BL z@2+NTNtXEf`E{PR@w_$|m7ohoWPSbGl`B`%*1o!XC6i8wcJ11A;J_^d6T~44tV6a7 zT^J7~(*ehb7|MDQE*AihN>U81B8qLvTf3TsnEaWFoR2CRbJ>E{HdBQ!G{BGvl)BH* z7fd9GkJrJVKTD)4iw)Vbtg78s3faLo0DCrtO29p0S(rmNxAR6&q5I;mlJU6thLxV> zpi>AkT91rk)uwxwokpmsJMoiq7fyod<-S?~fC}9dE7#QIDJg6LP8{K|iUKdXE^yj# zN=!2S#%Tuaosg5r>gvkD0|ye;W+}x_#s~{Y$n$x7{=&t)-=1ynhG0k^*$u~DJaqBY zx%qT9y|%f2_Tt6k=dYZ)w0Uv8-JAvHrfIloy69Dcq!E{MJIc8RZd+{~zhH%kRdzRX zCPvy)u0Vur)5icZP_ID|vquR&HAg>s%Nb>OS^y@^RC{X$hZqM(M68k{=E4#dbe@DM zT|a(x^VsE6-C>FWm{$69QKp=*ghqdkzti>2&C_Sj^ryOYO%p=SIjf5RSzBAX?Y7&S z5Qv9du9|xs5KH>VNLxXqNQwKT;^dX$cZ2AKjlPwUo_kSWV6V7NYZfQ5lOs`nXYUa^ zYz)teecLFwuD*BtGPuBxlvOoJ<+NFHX$={ieB7&atl8sUi&2__ zc)|fy9mEE@A*c}2qF4z43c)TihOU~|X(1O6@VJbR2;NYg^y)#J!#9a07J)LP9S%3B z35KQs8w9*q)H`?XI(YCv>He|)q<;Mog%IZR`Na#D780IAU;G_BON*!CQT?;^>#zRD zpDn&+hME zh#n}RbnY)R`vogS;zAmfq$w?>+P3Grj!UadiX0~Cr0rL52u)~caO^K6a~~b<0EkrZ z4Re^!+ta7cW)*DafyP6&wkxC9#k`*-sf?m`rBE}&7qh1IDXBBic-Et`r5!o;bBq}s zTolZVuKUI650+XusVmPvAn*%KK`I5%w*#;&&ST<0(KJR~3gTau|6(_@S@MqJOCxJE zCk?jcmv`pdeLJFtju}^>hadsn3W}`a1_wgdb_Z@buy@~H>4XCf`QN5QaR$NthavE_ z_4TWlujCqSkcSe*pQK)ffwx1TB@UCY5I92;km{qX+dVGO%D$u>a2W~YW-9u@v%-KB z42IYV*O8`eP|=$}N+QR70PN<-h+`>q06Kpnl`J$;*Kffw<`*H8p-ay4BIDZo1{#CM)i+@xb%kSEL{!DIl2L9)$+*0Zh2B1T(( z$Vq=|@9dN5bJ^e_?@rPAJI-l&hpa3#Pp0Unrlu^;l z?(V(s?$88uh$4gq9g`Ws%gkb3zjiRKSTGr@%KGGH1*8*Oo!ciNLf+jWT4;#n< zI^fNcH;<87m^q-YMM(Nwu3=BdnKNf*)7f_AP=@B>sVaQi!CUv=eDiEJ%kDiDxaCNU zC`{c>znCfm1ZBI3DQ~|iPoAm+Ei(JUxV+I-r(yQ?L0M~1sb7vns!w5a?rbU{r>u%v zIpS5IsUetPs@zB-=Z#BxV1{{3^%xT4Jo3mRp=nCLu3tif=UC+M;UgQHo4r1;=d|bYFoT%sz0A@Fd8w-vH5H_m`M7$g zc1pM9d+p72T_Va>bsoi8)K@UMwN6<9Kw;FPzf68{QGwBf)mK#%JdC&5dgwKttLZ7u zLVO5HWO*;BYQ$C{szANG)ItcSPoF+}_G}2Dq62b4sE7s@f3EFVyZfHI+ICLE%=cUS zu+LhuA7yvyjMjy%o)vX&AHG%Ez?j8}7vQQm(kh;DTj!fC7dX zIZ%{@ho^Q7AG(prDz5|61mva+)BsZTwwr9bQz6(im& zoM8?1bhY_mS#S&J9aHSryn@#7J{-0GRO*8*Oo7TY?!pI$J`{-sp3B+h#0)elCh~n&iCRrCQjNq~5}934xjNm9aMy!z ze4+M;AyE#3qw%poDxVN*Dik3#$251Oz&mgw+m0BCFiyJ+F#U#=3f&+;n$ICHohc+h zJmb5Qwv+Di)vF&KKEhf2_p=XL#Yh=uUS3{);_)YTtnG*bdrX4ifJ^bK6cA6Valu4> zT=ly$&f2CGgT&c+9`O;o5sa3NUqxxjZNb2&dPc0~1? zV33(}b7gW*2gg#|Or3I%(O8bQsHjRcucyW>6u=XodSc(cef=>`HT&MZUgX4y6UUAn ztpuc*5RZ?EEpM}yaEZvBP5_%5a{jp&oG5g2;|_L=4vw`+b{fFFsBf`uC`BMTwi%R} zT)Zni0*}XlZ3T;ICpd#um)J^)DVSYKG83*;Db!9~%_Q$(@50%DclX_Q4Iff*!e2Q$@}N3m3oQ{1DGZqs$c8LjKvyxuGi{4dktJUh zE6Jf&epva0eUi87s=<4a3sM#M8bI|k;Wto&y~ znf?kCCNw#5{N#lT7Y7_{Zmx+xgPf%@oE$)~a}n2l4di4vJ`gl>GFv9H%BJ-8V5muf z7;CJEh<$`6{uWG#$AClwVoPCChYJiH$}2WADZE?Q&@C}7>7vUZPS-WGBqngOttFWU z5+|r@=Wo67*4eY?x^8gjtFlR&KTps=M7jI!yPo{alS3ex`y*mXR2QZr;q=USW`@l84-!@pIWb`hX~koV~kp~N?z7rh&C z(TGY@d_1E)j{{sAH<)L0o0#k>g{kEb5HD-LJ2VF&j?t%Rn3s-g3(mk3ZgT2a{@7Qy=2hw%yxrzjN){ zdR)!%;3yhd3tSmgF~&Lx*v2TKJ44G+{7Xi(Fu8*4&p@~O1sbRd8kEO}R(vX?#oUDi z;DR;?o*?*Cd|ylTGXu#>#+VV5D8xm^l{h}HVlEpZNc85zM~<92bt-Nl8#WNIQ3G)o z2~&TLSlGK~&lkV=#g*mdW+DCSbKuXj)|(7sC9W(Q>uFICN?=>DLL{lU>=Qt;l9NXn zMmH5lT|1J}RLr?Slxc7P5RlnN__X-gae@L)0qqRA$l^Of1&1*9&+8?2pQ+vlzYJJP z1I8~^oT$Ogc8VR51ON@_oFa2abd^GEylQbu?m_NtereV?j?|VyMrzwId{zRQS7lxc zG-IwhBw{jx3KV3NvCBb*v7Q4|h=6f{(jI#F;m02PMAxVD^ak0Gxf4}>JoYi+z_x3z zZC-ofr59)O>2OxyaQ0t1^4%>`#(A+5p#RoR&KZ;yvIbH)B^sqk6jK4vQ5-m#Eg5PN z4%-DFU2?k&%Pe?-F3{=-5)roWlks7T`l)ivk(eY=@4q*{APqpMF*H;Gr^yM7_a=jJ zaWzeI;o|wjM~?Jq09}2M>FHrJa0ub4FMV;}&3p9-i<(ZNTFI0IJ1XsWRq#-AAP6NR znT&9ZzjHQ8T|4Coccj$O8I;O<1+;NS31NRrZk(o>CJu%oGu+jKcfD9v| zAv(cjf|2JLE^x?r&>%d*UdikAq-yL9o0{<1M znNl19bye4Rg`5ofbd1{SZ1q%ep8GKoQCp13?L(MWU!z{!BL$~+ny#?PvZ_05vvw%AZV zPRNRp@d!0Q!`TwzD0{VYpQ{mi*!U( zK7FgjafVJVB}W+-sjy_Jfx=a~w%xH~?ddN+okh6;%65B7Z}+zx_{fouP8>gx4*d?O zr5FP z+}zxpOq$Ux!4c5&kTSfqH2K}%{fF&*UbrN^HXhMQHkF}16oM3%`pzB?#fI%bT!SuN zzOj*)`PdmD@-=kw<7{4^H|^GK6sZiaLI%MIZQ+oIzWMfz^yUJFX^hEyi-OV)$MLZ? z;=*qQPu29FU2yJ+A*YoK(Vg34QWu{)eH34zXHTqQP%y*dw4DpIp-s%lnT4jGYI z;G~iQXB3sMz%3QLm7l1`x(V3}a+?MhZr63Q`TUcQe)6G*AEa$cdnN7=p|)Mv@z1~T z!sg~?Gnu%hrXiW?WEM-e5PyR7K*r1Ywc=X^d=xi4LHu(>^$KQ@Tk&8}vRzmXUsrEosT}n#ctsPnZ5mu~My1DGt~!H~1;|S5Qdq?T zH!+kLR4=-2e3E^tbuu8ksz}Ch7S;=+)BrpuVp4RdM2y1V$z$M$EKezAD?8=Gd-qBX|FZ8NhWChQKA*q( z>T41cMy-4;9CQlXN5>xT5w`_LMINj4F9utolO2X9I|G@@c3?X6PTM0%y$YE++6UJV zU@69C1Yl`)5jhZ9c3p{8ffQA-oo;6?ve(g8)GpL)(d4d)Ed=KC=g&X;v!AC8foUJO z?g)uFjRV_2GQd+$?EeWC3u!J6E0CpfUjt`8OTn10C)I7D~sl`KOhsUtO$VPM$I znKx571X__50z4vCZ{wSM=3-S`vY-+Ycc)A6tp&1#oLKR2zMmjYN}+&Pf{LPCYJlgg zHC_&=stdRVv%LdQ5xS=J8||N)NERn}v7url+sD9loHAw^4Uj4=EAtIZQ%h8IQm{Tv zFm}hiPM*|c1R-u!DQPxMroNP`Y@pKZx83^m(@!leEe!;WHQodGRayNzA%vx+$&n*R z-h21mrOAX?Sxn1Z4kTfx&$w{I8S*d@yF<6 z59>bv`Op2lKE^mT*a^%hw$2OUO#p6^x|gS;5&45!xmh+RlqTQz5|uT;D{R&B5ZAL4MMt9LkXJh1M;DTR#q*MAjlT=g7M982QLWnKFx1EP1x-yMQW*0OF(5 znV;AtZdkyh8yWhDZ&=&0_KmN9egFP_lgXrM8c3cs|7Dx1>$>aLufOu@t5-MHhY)gA zhjI#8D@1l0+(z$kqjTxSbtq~0*V zd|=%|%#u==-|7JCTlC7@k|S=HEooG;QSrUgzt&R7-p3p|BIyo)bmW)6`gJ=OJ%gA4 zFR;EaYrK2zzULo(?Q0=a+zN?QV#9GGc6A;P16C5RwIkt8177=@2)WS#%JsEZleh`4 zUv7c`bBcL?h4WSA=na)w5tk9O&6QsXhPd}1S9+~|uaIH%ld*ObwK%u1R5E67Q18LL>I2YPT3Y% zy6)kJKk=2Xd?lV(zHI@BVdc-;`SIh&fA;gAcWoU|x?a>NJ%0&e&O5JUSNGhy>i$JtN9DZ?U5WnfN!Z!6+ifdl{mt2whWTzI44KwfT zVs-GXMpJOeS&(f<9ud$0LfyvtwI6){hu5yIFTMz!aNHBX)*U|pjUk}egy!4d{MK!^ z-yV-3s%i8JW<@nTbfu!sYviTMa&)N87tG2Z0$Qr)LY8k57yM&!E_k~fvk|n7=pGTQ zKHNcI8M7LxtnS&&8{$?D~Zf{EUoTVBUf>G8qBlc-B}6 z+0=^py3FBQ-~8sSw;pWUmdd1wg8RCzYuonCH{X8yt#_jw038rVlV;QlEk>wr;Jo2a zSF%?RH%?9EjbUYHAr66W_fc1-9P@DsQmI?`>)Gv6>K{-G;D)p29QMVeI)TpGs{x6+ zY%?qS!CQ`!ukN$999H_H{4%vv%;^J*kAMBb^GA;z3w=e5zJv&IrM)4CnVS$o!=L!X zgMavkf560jLFvFL=CmD*&Mo=mZt{OtO2U|yO$jE^W=(`%svdyB?*hkA!eSMJGE)6R zCqQjFe;t0}`)vPNR1HQcJ0wj+V_OJ<5EUY5W7`l~ir?87YXz74!!_7YAm77@0QL8I zz@pN@gbg8Tovy- zvT+TPFAEnU{;B+-3*qy z1ZF6^o~cD4RCb`42lJOTp|Caa?bK}jnz$&|xR`!sRS-gR_S~5t|Kum3;ch4(5~~W9 z-RV=N78)Sz+O_MO|MZ{UdFP$O2{V|_cf(7_W^N}AGTk58r83M+mRhnM>oP$-YQ5ab zGh%JNbTczL|1;JTZ^FlIRIJ-w9LHggwdbJbC4o>!9RxdQ9r#SyoMS!aSB-3%fJc=f z#l@c;GZxj!t8K7O?BK62)K?u@d-+D@DN&GL`?>)3?lu)A2E5+*N@A34kBy<7USS8| z&^`H1gOEcsSV0nIx&`DC+z}dHT3Y({x4!w%gAerQJT=wPz#tY^2;rlTj{fXtKTn(R zU9y~)-PjZa2*p8FmBSZP=78P`%4Yc60UAmcF_nGh6X)Xq+g#R0e?#NV$Qa5HIBg20e4@WUVd=*s1* z!m*b50oY21P=3fZVx>56Mz3?-dBU^fz*Z*MzTV1yh$7f-tU zvmmlt*RY7iJXKH*|A;LqCuCooj>XDkyQsf(SyvH_5}zgwi-`xF{vnO$Q__sP|phh zBA@;Tw<4(5PeZk78R?VbdWX)({;JEuk{^q1jDA(}kb#+062!x$Ay1fjX)<~5z4xAf z@#PHW$9lqQ%F)i9JO22e{n3E~w@c|M(|cTU)~xDCZBUkJ++IcGgiA z(f}^9pG*n?H_iu?mmb^oRt-%f6qlOZCS2MM5EMNK1`aWy)=e{*y`uJG+b@nP51V&N zJE_TxY%!=pwpJqH>Pmyl4m*Ulu(*|{#f}=iq)eLq*!%G>T)gB6pxy%M@$x0%1u`Ks*Vw*-}f%lMW0^d%BV*8vJCIHYN zD=s-N2i28I{4oaQ6t#lMAZ40{6~Z`;CN;9E17ZUvk*U@=v+@+1@z&|!L^w&|5!Ya^ zvQgnI(Hu8HD?U@Y7Hvt3uWF9C^#q|^>KV+*kpLQQ3)f zZTI~j{P4Z^54Byp5a{@%6K2UI?7D7sW%W=0^uN9T{(C7N5sYG+amsM#t{ElQhvO&# zXZ^gWY>47H(%l&^82mBN8v z*B#k$GM?0>%9QPU$uei+cQM&34$N|8#FjEe|013jk>!=;KlzjY=H7en5fOHlJF0vk z$o!*g>l=Ulo$p?~a<$=xhj1TpDcby;frbFyv3C@4tpY?rW>J}? zCqypVNL52zf|CR0g$yW8n@!8MKuf?n;JB9RIBLj)6bVoTS(f>g;^H=}IdZ!H#*auT z)Q_TD@Y@H6-v933{_WP**1RwCKp=6XnkOv_O!36yzxCh!>7PuRCI=g%V&=-(0FDT; z%uZB(HFd@%t`@&*#u-eCeT?=iNe!6{`9_lt5uLaYKdlB@Aw6!GZ*kEO0$Tcs(5V8Z z8MFoC6dA-W);Q9%8S(b7K}p&}O32@n1cdmA&nkIlq~eb9H1@@i^*8S^!cn4O;U7eM zW;CAb0tVorh<}@M^Q>W8h5>3mW?d5FB4*zoun<*g!>HI`ZD>*VG!eGWHYa7#S|{C; zpL*ht{^-A2US8@0W=t;0(Drla2Z!+X+y5VRe*$f3R@DjOd*AmW;zeX+K^$l&A^Tavrb*6;TQ;$EM5D?zMVVsa@68P16bjDkwM_{Vqj6` zw(-Fa{_#yW-!hxcHf@ZJVWC*hqtmLg;Y5KU7W5t8@$D~o!SgpZHUt)9#S)%!he%5} zRVWo;EuCoF(1=qIyp7ybcO%-PKFu876YSmEX=v9V+E^V=h>G-MD3ONsb_kqmu9_pr zr5~6RD&YY!41hr%K^da_$vaZdLRZ>43jqoVb-o;mvZq#l7(P47JQXbZ#F)7v6R1e8 zQ?kzdabl7UYeC`=Kpr5`(7J}rO~-(N#ZxQ9<&f``W^j`&v?--ZVtMz`yWjZ6H=cId zDZ^4=N3UR2nzWkd`P?=(Ha6A|9s1x0KJ@Uz53j7uL_%eZMQ1kt=IdQ1>N_&aS@?(^ zghm`QwopuYi@4po?=0&&4N<1q9P%PO1kuKJ|1P?)yF^rYe3nEM1pm0S^CqhijDLF$nmsh>wm9Kck z|8J&4^nd0JXyg?InRfjRH~iIK{q=AHpd_nWSd#N%5o1Nj)KBL!--(!Vs%ZgM(1o4-03P;%R7Dzv&ylQG`@}UJa;W zsRwWbM6QI_wGyPArrUo$@ut%Eq18f%Fum8m@FOfVdq)L!ms}(wB%0670?K1VC_@$A z6u>5qn`xMyBG!0M51BY6%u#Ej2i|~84ZZkrix%kD1Fwm&b{)=?>lyJ@|SzdA>fLZ z!s7KqhYtPG2R?Y~ZMV%9C#{%SQL7DvgGw1v=294S+E4>_2}P~6D2P_f_^NX10Nh^- zQ{X~lxI;m~4lCpyYx@cYGUHjCFeN>;WrVIw3}j=L7kUA}d&@X{%U@h(`H@4)y4cEQ zoaSe$*WYmCpZw`Z4j($)qJbtJp(tePY;lzEF?;sB`OQ~8`~33;$AHQi|r_J*eL^T2Y>j!+i$;fHk)mdoSM$s#v{}a zS+XW}$;Hom)0J=9wQJXKwzMkS4Y(F0W2z%x$m-TYo-u!$*EoPgH+oLJeU+pFS{FrG zr$eGS+ODQpc~D;6M@wQB2slgfY~&^{vzKLpC!*PY@w`nwr^_c#r!3I&DjbCp`f=Wm zK<=l)sl;%)OMcMBWd4fHZzGvkPJ_D@@8!JaFB0*Y^zxHEE6vr$*bhR@htUcp^J+CW^5}gsWz&M@zX} zZ^^96tEybt$9`2{zT)KPxN`)OT6045^H_y-fl-GbLrZI!s6LOqt(NHDi)Rwl@GyKX zq+@XR3pDe@6tf^`H6_WQ5SYm=EGwo}q9q9d_V`Aa| z&p{^|Z6kLg0u7@aQLIqtZMz;Z$m33<)V~2iuI$*c(;rF_K7Q7z;X|4WB(AdbimO@z zaw=&rsB%>?yrQTdH0L$D4ifVxyx}1y8akZY{GPk-dEpB$J@wR6Mk@pJ67CiIJDY}x z$k092_Kx^PPR zJJMu|eU+b~I-w|ZiaN@ED1_IA^>FC41|NHBA7JRPtBoeoO2gEwY5V!>SX=@@*R5yG zB78842)%|Mry?fvF3-5U8OJ6b@cl@8h8vH^Bj5~dr9X$BhZ~ceg`NSvxZj*Psw8K)|F3F`~ zbcDTl`HyK{M%zlGsv)yb9dgAzm_~u6l45>@rwBbf*+^9@mQi@LDS#!U%-Tkgd9?*4VokZI7f0{y|1 zMQaIRK)+bYyjIZ7GVr7-XBvoOBm7#pfQxSiH7)f+K%Sbpr{jPgU50wiua)-T^oy*D zO4S)9DYIBG)Um!`yq`n2=6b%N7gjE#*R|XN%PL^tCtOWYizn@o9KV4D%ig`FW||IJ z82sOV<(2>P4X;~S?D*z>IX>-DX=Ky#~&BiiZxuA05fp+Aim{g z(P-&{6|WgV!h?p-qhM4xEGS@2!^El+a*DlzxWzfuY=5W4Q7WhaSc`-=aaxeASbS8d zxNguh3Fhq&2R5z#6544?poISF;?MJu9g(a+=ZbLyjjr zw4JnwX!=s;R{pNyvfUdhQt|9*hcK&apfrToO7yl>g02nHJmadzNflwMm=GYtwtz~b zF723ZbrWcvvi^u{Ai}9-74bMiz7nd~Hc)=b`DcQ9l3d77F*>AM=jO$9uX*Jw-}=_K z>^XXmi8a5x(7%JpY%=Wqu=(kM2Oj*DU;Xv#uD@PQZ9XqyP@+Rf>I_jBaPg2c6j~p!w)_D@|S>0JPZ_IGBw5WPqgLBDh&i76&=I^v%eKuQq#Bt=z} zMM(*zN;DH)d?k#!w22A;S1Km{V@0kpFbnHVHX*s}t6%s_@A|i=op!3ubaizVtudE# z+Gfm#;Lpb5zxjLK^WOLTpYL5?Uzf!JP}E-syo1?HyCbM_wyK4SValtLtXm2IO^lb@ zMI}*~0_ZL`KB6QrdJok9WRHzax)Mxurcn-=hAsehs5T*;70l?ZQL*&5s{VI`VYp^1 zvj-k{;HaaHdj2IBudc3+r_D)&a9q_1VEXbTTU%SZ@WS(N{L+m#f92*yj&yDu>zGXC z=$Y;)-uY8rG-ZV)4a; z5WsIa3^TH1&uUEINsml?gb1=gEl@I8DFnI*)p$Q$L{buFSw%J#%o&U# z5~UvB*B*G)w%pbs%RZ|oaBR>qy#N;`ekad}hQKEQ$rD_lj)IJSn7R(oQ z({`c)eL+lj+;RKPT}NI1qRSRbNV*(tHE1d-v7B*u(!|a=`|MLrJ@wO{`qZP3K03Ot ziYeoO<0JZ3rU2TkO@(^c9w`)M@wE^~DCj3NMvPN+S(9XPzZ>Wz+JV@7kMl}BoG91Q z&*iXvDU(X{tBUAU(FX0xRGEz?khW!Mn8EDhQnmu7py*;uK|L&6Tsj>S>wj`xIceA$ z4*JC=HqgXwx{w5ZSkxHWd~L_tTYmf}-}I*M+p%-Uu-*U2utjvwftt5PUbozG>pOno z7jC`vwwY2XG7UaSSo~EbdYGEc-aKpl;r6o@T@|D)l`EfKRm6V;AdnPJTn8JfT{;>D z1+ZK?vv4u`S`^=F1<+LZf`-a+nosf4lqf8m?zV0c_FSSih)_$P+EBmQ`o{XLx7>RE z`RAW;#xsZBXJZX=$-90np0G2|JoD7kPW#;FKX>53fqr^084fWW9&>q`tU`tDjPe8M zV7g&NII6zX{GC6V^K2=@6jXrnyS|YSMl%({P2%{|CLuBwR`t$rE!hS8evqZTwFuq_p!$w|M$Q3+kgJikIrr0 z8}Iu-)}mE}9cjlB28(jgK-~I8Ptm+eRim%0Y|O^K$?;d0e9A@J?dhz?3i47XtuUFY zva+_ei&aavq;k_d71$D zpxYz+_uqNfT`zsfOO8GE*iov^W;Kw4D3XOYuyXFX&pPFlQ$K&r=k`B(VAGbF`CjB{ z2Mh}hWM?c!dn5^XXrm~($trV7)a1aBh{q8$)l#@eJwXG_>MsL1K3=dcG#eAeg>irE zUNvV!>fvi3ebPCR?1Q08EhAT;cvK`{#lbQT1vFRpqe-LjIJ5)hi=yJD_Ibb=T1_MB z{aJMB#@}LLX&-_rNRYt!(L00!YLS`DHs@>S+S(8Q&=39SkNn`#M<1;#bZVFO7)2Ug z-#B45n|yF8>Cr%d^qR=Gi968 zS95&~#awQnD|H*qX}kNLdvvyX#TA!tO7EG@Hht2f{kFysq=m|CWp>`V&pPYOGr#!7 zzkl?=f!WHifvx*Yq_kpJx;leOOec4I3?5d;_R2f&KZUV0gSSu*JU zlaavCm}%#IC_w>h>K8&MOXEw^GYgjqO8 zJHbnmK(eU%H&yxK7q5N$JAU!5yYC!l3`8yIyEYLK$Z4F53*D?ABXuKKaCxE_%*|Ln%Uz;`c`dHf+#E>S{KdoqhIM7hHJ3jo07! zzylBJaD?g5iRInSP}md^Nk(_MA2%U{CWzDW0wixC`(%LMj#mSc%tByd3knb%K2;^W zk?_GqF8**JsWvxS-xl)dBGJE5ZdD2BPL@4kpbSPupX$FwR%IhmksE3rbUMXYaWU)= zt@Y+j4H3of^@ManU^HdZU{m0$mIKsTJ0raSMr>lfeoOP{&ws>NRJ29D7{!9&nSRkHt8qg)9^6Aoz<*dU6OsFd3( zki^LWXT2h8pkby-pXo& zdIurD35)=GNtvuP|0+*B`PB8--*EYhUUb@Nr&_51cn5iR+*-)z?6c20_uO-@zwU_2n&cz(jn9o>6AU; zp1GkFs1iSwk!UUFm1sgGuufNuFmS+v#U}eea*o4MU(6^)|~%V{{J%5t0OINDYfi$JfT+UFRsoUtspV zKi?S;#x;HeF*ZJ@wg8xAiPgH{oWPiby!k2S`4^2beMT05&!@);H$shY$bn|Mflp%YXdcC!c((t@!o& zA0Tx>fkDwQHyEKj)D1>3%vvnQsU2jIWQkO*1u1>t{*%U$3wJ>GV!T46*yJ*QZbMsH z+p%-1R2E9RgL%$0$Q_I?pwHCsj>$sh5rJUSsI7A>PRq}Msl3!sQwgzZl4qslYL4f^ zPhGwH?tAt>^2lYEUAAl2&T)A#hfo@pT-prJ%x1IsVrSY@Pd(+b%U*c!;A7WafBm6D zhZf}t^B5V&qPI~5V#YwgKrOsO!bSo9vQ?r;p0bm_ zm6hatJjh`_Z1}b@|Es_L>v#RTUwwGrK0mm3`pOQpQb;rk+UQ2364q+*w>0ET%-~eg z$}PQ0W@AB8@oN6A5V2%O0efHBv9{Bx9JEe4aXX2X(h}hqLR3Uq9$D;Kqx4Uom0By} zAr_I}DR3ZtET-`&F?=O{?NOY?_b=ac)AnuKp7*@3+OcB?J!=e%s<5Fq3O4C5`?!U3s=MXlHZgt;OoL$VOv z0;l50Ttb-`6Pr>JT|5#IgGO4uEC9nxORmOK42B8>4*NtX%!>+F`waqmKA-PgTYKH> zUiZ#-{^I%PKYL|mWp&%O>Ekz`$3eVr%6?m)uZzm(uKwK5|H3^~WA_>~o)c(aOpSZ&62y=)#z7%)fJO?UG9_KJ(18ZomEZd+)t>+$VW) zvdnm>qz8SboGpzr8ITK_D3p<?r7|H_>;!4npC%=?~hFdVG8+UCC9uz8dJUVMyfuh|3@;L|q z0a;S^K;o>rWarpuls{TTZG&^5Tx+P~!WnpKV`Kiz(@y`vANs)`|M4F?>EshvS5{}U z*`i1~Ah23|u}HRU+Wod6YX9&LUwZr7f8h&X{NiHYEyMF(g8a;M(=j9Xf83_IO;1D^ z&w^wz4W@Fi3Z<$KBC90Ll4_a(e+i^l7t-qH2szf8P=N=+ED4BCl)NlnG0Rp7LNf&YBpo5n4pC7k6i*~#!<5^`{;sm~G&Wp?&Im7erxqCyUdg96?j z)Ut4=an5oamWE;Zq)rT9qURV@{B$I$)^AE^!Z4t#fZ6!b#8y{UuDJa2cf8{r-}ddV z-M#y$na);MS65~$v#~sSa!4a-j?#bKX7;5of8}R>=I1{3=}%+zU9mH28m9jevb{tb z)8>^wWCb~C_Y$Idn4(m4Z%B2($Bf|#kg6NguPjyqTa@q+e^v~(YFK*4&QbS371*O6 z^Q76->A)qWSMHn)1`q`<;xj41;G+Tug_%Cu8X$j3s}5~E@%R&;zvhdloqEc%&pQt& z2_oC&1*e7%E5eRF?%2yOf6-~Dop#rq_dN8_LuNW#SsBhiB7+tOi34ll2#UsZ>~>s) zwJDtSt+rrGil+_B__id^El3&+BpSj&l9SGFghyb$MY0bg6=e?_c0=TS=__barMRUN ztU}`>q}m||&7`c13%#i@3eN`j_+bWigv}cHm`7E@DEMo-MpEitr3zsi07SObcqVa# zQ3WD!=zxtCaKqV+3-8(H^OH|G=?!oAmv4L9Pe19`ShnhbJp4CoO90ExWz+++La9BLv^;< z*UZFDKKZ1t{byfu;e{9M-@pI9`|n?0Uticw+F~$c3uEnRM`%hwb$UVrg%W5WMVQ!_ z5Q&nMkdZWQ!L4WtffW`-dHxXGN=@=sV;2FyU{*6-)Dj`XPj}2^i;7ajhNY_SlouG6 zI(3!C8=|)fFq$7oOE=kyZbp^}rAY+ukBTUr$%G()_L%U!44zhjlvNKy57yAk%vM%r z=bd-%4}AZd-}bhje$I0*T3uYTVO2f4%-tJP$?|&G>SvQLRPMX?{$Km`-}r;~y?=e9 zE%+kom@6oqbm>_BnxGN2PQ+2O#-_b%Y{VZa!9l>$jjK3H)40g!34F0UsRlo_^8k8+ zjYS?g)KPnI{LGV$0ZkfoK;$k2Ox<-7nZU>tDq}-RSu}M%B9GbslKP21fn;*FJC(8}00XpF7~{gl zaXs!F>i$U&p#~JGc_k}C6-=cg^`MRKB3_GUO4{g8tH-RO^VYMkF;pAU`v`3{B20cM z3k&67hU+%%vO%0XAh^|n#j{?puxZ%LbiN4IpK|g^uYcWl{@l<0%&WitReSb~OMHjz zodtrNfMlF{2Y+K@V>p)Mf%_l$wO{*<_q_N2THjdj`M}_vYAR&_;S(`fsx}eA6a}1F z0;`ZUFgC(6j>2T%4i>KD1Tj_*@j(Z`Ymyi`esK!bmF+v$I7w_h`X2{sf%BJc?Ggnt zJf5U^NJ9FZs&q>N<~uBeEf#zMJW}x}_u&pmvCfS>%hZF8Gu8c%?*Hs(uRi6Jlh1k9 z*{dt74Ib(rR-l;k)qv=k_UgBdjg7UnwdY@Q$xB~y#R(_ueej_N_wRpX{qXwY?fG~j zrI0(JQe}%tBu}}8RtSnD*~=?#CmEjCbRQ`a-4S*35>_C6Jd~78F5D!KL#( z0zPqzBe&*=`9kfT0#y+_6Q~RO_7J;eWaVw7pphmQv=Vv&R~k{H0m|{1XThOC6m-5| zk&~YBjBomeZ}{1tdF!{n_FGOl<)mS8uOI9M){3%nx76o+KHu2bP}N&+z3o?i)ZZr1z~lA@;?I4TCuJnFGyqxG19TKnsdRUcoTfQ+5y>jP z(!MJ3Iqn6t=*Uz+rnpK;TbT<61-6dGPYMY4tw+$*6l|g`FZQ9L1~>wt0GvrRi=s;L zO`3e5e$@j94_y8EYo2k!2^XAy{&0k3FOD)pkBMarSYbWgJDbf`S67ce{`jwc;iZ>f z{-UGz9P`ja`ySr+(4j+zV@+PySLl5A+(MqGn5$$nNxRbo_8}JZwTIEv)fjmT<`T3M zn4%|B7xx?0X*V~ag)o1~B0!m@f<3APTQ{^s>09EL=135#@gllX3W`vafq0e`iL39d zh!LL-O~-8?5zdzuK}c#35|XK>@U1JeZq)Z82WiDPEuqxB(nW}V^Pdx+apE_;=G8y( z<3IX+-}~KXpLN#u?c006XWSE=XD^Z??T=*}i%V5+y6NV3zVnwp@@Id3cw>FB{eurm z+?K)dm5Gmtl#B#-6jO$vw~k$oosqeqPKy{mqNWB^SR$qL9CKpPSZTLq)49S%%K=cF zy0Q_J|4m9Zq}qy0CwAiOBT^Dlbt4ud>RYIZAaKe%)${zc;X5&EHPhM3%Hxke@wv}` zVfU_G=bd-%jvYIi0?=ZuZF^4`S%dT+$Yro@PCD_#7hnFOmt67UlTSK%|Nci0Ji7m> zLr;x|CiGU2?K_KVcAcgW!{4iEB`W7Y#}-i`xx+wHO(VhY4pTNI)&#XO{VfPqE3IK<)X=*VnD-aj+N7E; zR`V{Tv+X%%&+{+2`1RlQU2l2IPkifZzxkYV&f30XhrcxbCli;!d<@;%s8bdSW@BUh zs;jSl$Irj>GoStJA`D6a_O8f-&h}sF%wz!^GmQ+%w(dsXl5 zdW^C)IWV7jRfWZ;5&1n4R4#bWrbYV-%Z?()CO4xWG={fa;kC5ZrjfH_$M)BK$G89F zTYl`M6Hi>Yu6<>%fO{TR&(-^e4EvnS=W|=^cdn|BJo@Oh*IxI@Pk!QKAN`9PZoKjF z#}A0Cu5Q~lTbT_v(1@qRb`5 zND`5N2O;7iP74-X!5v*3h{p5cNuYo&Vr?Cjbdnji*B{ckGl<=6qH!Vj*XJAS+qSJf z^Ymw4e))@E{<4=}_SKi3cKWHam6^@0*8vR%3Pd<%*=6G{K|>&EK+IE5J^9f;``Ekw z-LKqy%Pq5&6-QPBwu=PZe2kF7(}&wg^4ipJc1>Db5Tqa60a-Z^$E+VGLJcO+f_9K( zJ4hHstq%&&{AP3NP^K*u`1o~H09{I8e5Nl|kI-Z&jb7yM}aYPrC>)Zw#TB;n45X=je0)J8!v7J&|@)@@4Tm%sdFZ++`eU2y)h7yBCO z%CNLcrcM*96~Lm_bg{Xi$%bxh*xVMOp~LIzkL-KoOJDl(fB(eaUVYWox7~L8!}}h2 z>Zzy3okbT3qF$?N^C;fcg8|ww2ztC&RpZ0%R%sBNO3tZiO_dRsmX0&90sR1*WO_?2 z8=TkdztNUZ?Rd+5>t*;-%JcDO#jK*T>d5Rg zg$-05T>(foc0f)yO6g*tf{)dsp#zdNr&QuTC%2VZ&P#A2sz*H>@QV6~l`_5$Ml5?Z zNo#8tP|~)W?yfaZ#>V>kxz9THr~b`Pe*J4+y|%Vvw4yfU{|Zggz^@>k=$C=Q=Zh^v zbv|(CSQXliyLo- zHa4A$fl9Ca`)hyg*MIXPfA*1$MPk`Kih%{A_#796aWRx&Ub(qbBmpVV(ZzkIHv<|8 z3SgwnF6Nrgpn<<3u|m-cbf4%Id z`==*9C4Iv{PG5zufQ8$lw)e!hdjuod6dA+2|mfZEVa{by!sS)KiD{ z?c4Y8zI}JzdH1!~UU&1&x7>cm9rxaM@BT*~dGd)To;q}BeSLjCSa$uW)R_*#f8i!9 z&d@H*|7I_mdcal34Si8R#1;HNPF-g-9cyEVCZR%w;x-u)Z@cq4h6MylRPo^^kJ>5@ z5s9iuQ&bKW!T1brx~VK*I@^q`TXQ?K#kZj?w?KW*$q_RR-F=NKSvtf^>Xd?^XV*xq z0LL)oa7f)O<-y1L@70yn9c$Zn?K)YHy7>&%NTy6D0S&OhN9C+t1`_}#mY z5|Q=w_08FvwFT=oQ}Z4C=|cmNGRuhr28cHn#hr%_9s2mk|NF21#&2HzxzEYyz>YB} z_g{f1VDi;5OhVj}aXX4+>kl+1fb|D|GFY$Lro5z?sshkSMYVc8TNfi*qLn&%S$vy! zE{R=^hnj4906`@V=3S(Z5K`<2p|m$Ic@HWj&4CA6{ejZYQicJ2UM)N5%}2wK#ZQr)Gwzg~6u49flX7Ap;C+t08@808& z+O=zCWwBA`VtbO6c2vvYA2?`pKW$a3N<}&b+3`I>y&%Fd>u})Uf%m-Uz3+ba|9Id1 z_Yc{xvDKL(;XxUK1~Wj|gzcUe2_F|I0~g_sHd!r3&C)laM@(4fr(ugqEDs#P;tiqa z-PDXJiaO2VPE4Ty01eG1Y6<`}gXfO2mQG$q4yae#_;{)>?Mra)R0z=U}aG>yY=U_wTQ z4Z;XTe|SK+6Es#OI0K9Vl=(S_YSH`=84Tt{LvsUDqABs&B2@1SHU|9&)tO#6u=v~p zK{8gG&1SP1sU+$2H-~wg&i4{+t1B!qS>IT{`KFuy`>Lo#6Q{#>O1 zl>I=HY-Sn~PZP6U*sx58_R{uEOq_0s<}of)0ENpib$?8zv>$1!qq61RkdXo)hZx2? zXd69x&v7C#(G&qHI#KIhYLFSI15g3b01b@*L;lZFPdQ^&Vy(gm)))21+474IP-{J| zYJf_Oi=naIE+-Z;w{3>7X+f+iqv!V524U0Gdt&>j(56h9beAeT)* zaHYE}kK=H&yQ{HYX@&mKQi_@YA>9e{j`zY}l_2rU0iwN7!~!L2#u7q|ZKj8;98T9b zHp#D{n{<>=eulyVQ=X?4f=8_I>ovKlYz~@At30{>Jt7_4&s7PyiRe2jG=mp+v$4BNp)|yU9A9 z9{_E4RgdwHNwO-g7@YZ0F_#t+_Ivhtrb9jVv4OQ$aVKZ0e~q=g;E5Pksa8J z$8uUcFG=VDO=Lq3plG8E_vsX#vw$5RlD0$clp}Z^&};S2-Ik-adi^YjDXiONv7xbO zD~K0R0NwW=7QnBF%DLyA_onat-f#YvZ#-_{3bZC?d0bpQVuY~e%AsBJb~uJf8uOL{ zf66wYYb)WH={Oi%TiF>^k56A!{eSpcKHg-S7Ag)aP_}4I!FBQ&OINhI)GTRdv4Alh z6@ZwGE@~^u9XyP`zxKM{{ZGID;g5Xe;Ny>}&gL5%2HtEK2`B)B9n8+AwZ?Su1iE50 zn9aCm!fVj57MEZUKBv-?F(1?H0Z+ka1+dHj0I;V_DRZMERz2`ztN^+xu0EbFncY_C zVu|I*_ymII9)+Ri2!shXS2WX8rb>MBG!M8V$IvlXGMFu9iHu-?Fp*aS*NNM5fk4$gjsP5!REs1>FgskYiDL<=q^$v{ z7SnWZRY*Fhlv_o`1|g}I>^b7}=_xbV7(#NscMMF8g+{}ohud$z{r&I%!2j`{_uhHu zUCn-)%jid2h(|GaE?Fe2!e0Ca$6a`+wg9~~V&uj|PnO**MN^zaiv*gl4c`cJdlPts z9x=RYC@Q1f+(p}jhiqVrj|xEg4zXm=GQLz(JdDwodg84R05Dc#i)30CyQwLSZchh; z?PHGLEiqS|Mn{V9(o(fn*f$~t(j2iq5QmA4cOn>tc3+)4hV6B}v3~5a$9~hl_{M+r zhSy(o;RV~aZ5v2^SWDLPT_Zx;3f>;4Ef8E)L=s_|*Qcuk%47}KYBZDK z>8b$NaG@|LKGUVd)?F-_(DLz|DmsELwl*e?5EYUCKp$8XVmh@a2-ETs6!o~xfXOZf zIO#Hex4DMGl`+P-FzUzH%Y6RO!w>)YM?d!azxSU%^V!dC+qP}sL^OFUWsvxCi7T0a z1J#ZJ7;R#~t3gRaaBnTj1jCDX0fA5hpw>l+dTw>t@Ys}9^#l!oJ>KdaqL10;!yEGrTNwTa4jlaCCqMPS z{^$Slx1adL!Gi~VgSu&q6bhg*AW4WRJ&O5WnRte~D}AzpHYR4Izh=2()#6~5>`c%b z-6S0+tM5!8;1zV=uYiyxDoE=^6_9lqFC1^5VwJJ?Mt1D@(6Y=Z!7ncYwlE@>u0@=`y5m~+$oGT30l%y_ga0(%AQ?FqN8TxR{q%YkL zTBIh}Qd%(?SyJ%n6)1r*d{D|A!2l;L+`Hb*9rWfst-7^L8OiP+r2xh$W3?yE*+s(0 zqOMi|g2%N^!Vk8EZ-BjYyo&`Ti?e$d#bra?^Wq@Y$FI8jbMJlcAAa~Ff3|P`Ba0_C zEbKqrz3o`r<%2O;4CK{YUdo>gHxy}*<3u5p_VlAJnUs1{B0cuvhP*~%4I4tX5r@#g1D8-nMOanAg+N#ms$DU72iqEN_g4s%2EP<`4bi- z@&S^|k0iv+J48e91Ov;_!i-$U!qNCilp2vCY6QlvR4E?M3Scmg(b94SKnJSp$T5<9 zW>8L*Xzti5M52)BE=?`dqxZDEfHDtqQb{tS~7RWyND`uIFZ`5;0gNt zAa1JI(Xe}=Erk9M^}gqxd#}3sb07N9pM3J~{%+sH4_hnJ!KG>=ZeTQwj7mCBGAXkh zBO(%7Ec*mXdrn^Kzi zQdMI~unhU*zKzW;Xgw<-$PzZvY#Ef^6_?S6DFq<0(PW-ulO%jOY6?0)As0v{u|v)x zRg*qoE6nE$0zo04QPe)xiBQLRx9p3l_M1@w49yP0T6IEtO^6i(Cu`W$V<6GK7X7w6 z?)cbW{N;!L^dq0W>Z*eW4s6@DO=mM~tuI0h9OY|m6u93K65;})akebMjqA1&T00tx z^(LWIbTIb27|~RBE)yt(uvO>Twr!)RCMo2>gHSaG!_~Y=JIWpr%Wz&k zRW51ORB2}{AAfV%B%-^}(87|s_tax9F-=WeTY+J*S$Nf8VNNxI(73c{t<>WdQE$tIOa5E6OSrxo6eP~#V!B16#YzXijeF!lwz`W*V(*sZ(phH<~n9}FZmy{OVXq6T8GD|#TA#o{N-PF*=1k-%+pUF%H~E-VmL0<6pw*v=UQkX*oPNK!Og1uA61uP z*=0t=`Gfy-X~j(+QFZ4(;R3ldshRxYqEK#`KHD1bSYtpL{v%UkQ=o!T3yLIWcgPH* z9fta3HgP-O*m&^4hpxN+hR2v z`>0_J!DdV2#u5!+%&LtU0*{73#RCj-_Ky)NxSoEAseR-Mz<6_^a01jg2oZky__#zt^f#W zQqWI)y@NnYur-}l--IVsoW#ap0Up%7;L3(%Te%PpgrFJ}87P1__N-)l?K5{1@nMOc zw23XRufiz~#62Mzy6*CAAjQw*^W%b7S7v+m9CPlu=Uja8^DcVMMQ5IQ)@i4la^i_6 z?Ao=f8~ujcW5S*gY<(H|xBrwYOzjaFxsxsFlJEb7mj7|kkZAvZ|FZD+2;7ma+q6=I zDjVKC)70EU-Pl-v@WF@fy65h@?z-#x8*aSjnrm*n;UDg~=bk5@dP+Do)l3c({NK}n zOPUq|S5;f0YAg`MVn0Fjh*mtGWiq3{LJG3;dJ z?vAzX;%gn?&vC$P*bMG?l$97ff+#X(k9aMaH&=TARU%Pa=C0eI2_rj)L5(4JQF?UZS#NZSDBu_nvyn zDQBN^_Ic->clKFlo$<`mPCfP1J$v@7uC5LnKhAo_pfPkG5GDy@5QBgM;|)~?CmD+c z$c$Wz81Bt@uW_oVCC!r-O~+=7H2W-ZpT9tJpR=H&)0uG5hsD~I23S~5AZQ~Zu+a&_ zb4wlMem3a(7BA9ZxpaxwS{^$cb_O2~BAw6Y>l^C_4?cF+U3cGk=UsQ-eb*g#-1+6N zeC3Wi?s(|ohaNll*v9&XjHmrtUok&iE(an0YY16wB2(RmF=aGYxFb3ScKV`O<=8a% z4I_}P8qb(p-CY(zTS*OU>?H9)`KY7<#A2QsfzUQ}3ia`Es!0=IEofh8Wu%snMccYF z%cjKeh_3{O@D7Zj&{Ca@ASD1)KcRY;U_@L}8&0GIx1c?PzP2xb>~oN=Zmnm9Y+zgj za3}Ti(!e75fCVayOs(*+61cGZP2D>fYv-_fdhv0lD=Vuzwr^kCx%1d#k3I3k6Zh`j zd)#ry?LGduy?gf_zxViK_8h%y*RHj-we8!sEq10;;q6uy4Q;aXd~_b>&4L>alCie8 zzULc|-n5TMjS;FK2S_4F$n#iG5>4$Zp^S_q^B|PD?u70kwgJ}UC_ozEj$>#!Z=^~M zs%v|Yu9AYgNK~x_QUKHwP*FH;C~F+9#h4>@@sJpG;n}x$_U)nhzcO&nEMx3MHov(B4(eV3lK}-L3 zqEKYUhQ)VjD+p&Ave7iA>1m5i5s68UsR1h}RU(9;wVC=kDzWyG8w}6*On~VS5QLLR zs>wr%^i?c29)+qSwmnQ}ggcmwNVJfI<7V?AXv_=L&^(b`m(s6KlrN<9MY z;|Zv;ATHe%Z5l+*E>+9=>56|c8GAw1P(>VxXvGUksB@v#G4PT4Cx{cP*yC?%Sc&mbRz{N*3w{!LG~^TjijRq5k?xZ z!A;x%eqbz(x&pe%CQ=hGPGhj6n-S8rh{MJJ)&PsTl{a03Sa2fhKVhp3+e}7~YXEPe zc}`6Mpi0($Y>;bBi+Cm1e;#V{he`w~!ZM%$swh7*CP}OA2iM^R9$zeuP!@Ft(^28Q zS{oq}((pX1QgEJX-*M53%vl)>2#tY{u}_eX+EQ}rN4 zlthEIpaudm42Q`mal2*}ANZ?pmNG(!i$C(|Fnx1U8zq3HZK_5IVxU)BrbfwjR!|9A zsKDujABtSUc;Ztup8+@hxT8*z#t-C1!Vr~0#UgOncJUGtq*g?bXj!$I;s95d2@{=Z zgriM?t4|tzsCU9+$0BSS3W{>)cHI&Ht>W~{vD~nj(@Vbk2Ygy*bV~R{-Q|#>0 zD^a82Y=u#}`biWV9_wSU%1DW$u)^;4pi)1AuMaB5_S*P6&ipjsCIS+WA`pi#m$0!w z3j=k{r0Dg`ZlHE_6^J(B8r-ga853-gWbFXHi(pVlJ9PM}B3Xf$>O1r*PB;7TV}vnsTy~KA4c~7IQfvts5#xVy&ugS{Z2yIJaF76`HWOB?gswu-Hz5+6CF@cmU za5;{cuedcxQ8YM5x#4c* zXj|87LAH-b3`QJDfbJi=>7*&3;fmzd&DBf7IOV!9(!9-IaJtd9Oe;{^VI^!A!n>y`E zmk0qgad@{q5Vfx&NE5~$3EAWoq;YtdJvtWgpk~ib<(9bFnS|dE?o#>^p|CWhrh@1# zdhr@il^DAZBQNhN9Gp_u{Tj+U6|fn-!gAAdw_$p> zvb@rzec!Z6jZ&(CW&LPm#R;|1K;HtqQ4FdeQ!m%|8In8z_^X{Ml#P>m=>1c-hZFq; zFL1(I@NjkkXotX5GFS3dSitzBiC{rUmjD)qWNkF5D2ke&yg)DVTZ$K}g^6*)W^kra z@k;$Q!cpDq`q7BIsmRx}{G)w})o^8sBa8q%V}E7BzL90dQ8f+0P=e(x{vZ|Y^mP`Z z5b6l+<_Kss4MMNwKd$zmKM7YxJjO)&(c)o=|; z36??=wkgb?r!*x71vK1BL$VY#fEa4;Q!CQqf-x#*)8L4R1@Tr^LKjzS6n12Z&t-cY z3zEA~Fo&5Egb6X0a3qNxOj;PvK@2;LX?{_pn9bCqaW6j10vtTKB8_F~%@HYK=`SV? z67ZeXg5bhIC_gEY0=>|aCFHV&+=Z>^`Qg<`346pVFpJ}r;hk6(2|^$tHfA*7KmmWb zxPg!b7Qn#YK$}A*41ybPp46ng;z$n-i_nKf8 z*6+bFe677un{CT5GjXw&npg*l&8l!Yuk`6@M^$Fr;-NGRP@Gb>Ax1N-!^S$afDd#4 zR=c@{WJwh*^Qdve#11PvcI*^{CAw6g_$O$LDjK;z?dn1OcEEP0fM(hVtpJ)FNf58m zc*tg%VBo%gPo{dTo?%IZ14+XTH+2eQ)x|Iezb$uU%a%&i&{n?}H#I}E<*N*K@k%jM z7Tbfdbve$aPL&eS+%on2v_?@hpvBi3=}~EQu%}2lybI*1wJX3)S)m_=R>d#tQ zb#=u>tnhpUGXWg4;xOExFH=k7|Atvub${Fh$j;w4pEn`ibG*5BvarxX6L0~Uq~83K zdC%>qcAoQ8y%fA|ZQ8LCp;3;1F+&k)0-U75mX#pKqLLVNKk6_ya-k%kV_`4@w8Nl* zh2eS4-J4*vNzj&WPjri;4;m)MqF+_i<_)H%fjLdMVDxlAr{rBT0?fmJxXvZ!M0)Gi zxn3)|iX4kLb8yl`RZ0pEKMub*Z+5B58`KDKF5pYED>NZL*}aA<-rqQ?LPymdB40KP`i8k630ven2!>Y;JTlJ!1kXS&QAM zkl-_k3dw|vm07%nijq9?FqqbUjJK!fwLt9jx);KS)C^6cq|2Md?(<6<)n2(Y`pI2( z0$S9Z_}T}HCl(muzn9vkW6(O36{q4j6^}hB(lUmp6TVql#}J7A%k6GmVeig}z>>Si3OMsPi16=J4? z9b(uIHAZfh1w`Wh4jGeqHIt2#-q)8oRS4pJ;>z~^wC&3C&MGd~Nl!2o(q?t?Zl~}7 z08#o$L_t&@>Jn%tp54N>+Ok!X7?J2xnk`AiG&qdNy16nwZ`FGEl6*OEB%#Wr*-S>M z;11YG0LpfYbt!9Jd2C1v-C~IY5m=>IqA$#QLy(esDg>X>x)Ko&FLODAOweys-N0YE z#54=aRP%t^*CmR(U1K&$eImx?^+F4~EO;$N-G8wVfN@TNh49WW-qrrQfM!VgE)ODF z0LlrA5U%wQ07D~5inZo-k}12zYg8r^tiT~F64|U6hh{s;q4CZ1#}u$?p-MPNF~TUAPj-y~Q$Yw0747)ag0yC4_M`LLF)5Bg(O(=CDhOsEyCcgbHaQrHnuJbzSMlu~UffN*`71%zVbQ zP+5aR^6F=nIuqfo~*R+~#YG=vI z`IDxR!4@OSE_IPYo8Ptew|Kjzk$g5+h{II$qmfZ8e5iDN8cl62jq&2nkTv~Eus;mb z7^ETFM@zcW;wB7<8!-e;LMK4d$G?P7BFrYtS_Y6xgsZpg2dHO)v8qybf_V)V=K~&G z^m@;r$Us_vd`Xp(mHjnNm?bYV0r-z)dGrUQ#6wj zy+kh}0@GvCvgkTxDDf6*6vot80=3r&IjEZw7 zaioSbB`gNgXQk2L1c5vlJjljon%Yt2?I*axu=k@$N2cVMS(2?nrj~UntQ>XlVvm3n z5JU-QKD(MgMYA{WJ}~y`@rbC`kh2SIW!N2*87S0=APq;*v1ACnKpjFrm3}nK-Y^R* zXj;{Q!(DhNu{GE#n`ekAmJ}+g1U5FBz+Gm^1R#M10Su?EXkHebq<|S`oiyxglW~Ov zE1{+@423O%YD5X2&39Fc5Z2{Ii~ zs5&ONK+5MMk#!C@ZjXUjwzV91q_gfni{(%cu-RcJOyvLIg9Na>k&H6k72vDLv_eucR2zA+aDy z5nW0sprLlkiEPjIurP)TW5ttaE-V|u6cM3m7~E3OLl_{B#k>9raJ@9Mcls&|mb6bK z%&n&0mM*sg38F(RwBJZ^DJjO({#2WX=~0=B^vPu z&QvxpS|XXu2Qq^+pbnKmcLTtFQtrpS$w2c-`iq}~#QCSdTJ1w7Y@>nY5yY#K5HUBP z>6O0exh0t)wF<}G)%pu>a?RSjxM z3jV8#_cc5aq^~Aam)nLFBs?^ws0gT~!cO7b4XJI${v^?8K`8%=Hhh6jewp2uO&{a6*= zQA24D67lUsE0R|5zM*|#h+W9kf##%aMi?`GRQBl1@j?fbr=8yz8^zfiL2LL%@BLpL z<9B0Op3>^#0_Y$EX5#ds9C0{uGjh1egrMpPB`Eg zoFrGs2%Z2@0wjK*8+|)2T*VD?z4NK^mhOT^5(Lb(mAqTndz4ZS0C5i*HvR}Zq71Ov zc%Lken1`}3^a?Nja7TTLzGB^}QqU&08Itk1bONL)l6uaafl7|nnzy>7oY_i&ou7_O z;uX`rc|{n&L`zsUgas59s7>SgA(%eaXq=-SK3Xr9Nhn)9IWus1x3&QSDm0_UqNW`C z)8-OXog_gB6oE^*N2oQ2LIw0=u>E5%7->f$x=MWHG!Rp0)o=&6F?}CUKgo~qn7gZ4j|WF z%ar*+pj+T*w#;0O_G|)HS2xomVj}QPeOV-C4KHyJ9>=dy#D<1JqR5n3_F**D5RLr6 z8XBRPfx zXa)lXcF&J)_WEFuPgvYxbhDi#H#X(z&Cxm!t6yN^{jqE+YD{_u(~u>8SUbjqr3J2< zt$=x{AO#(Kgf?GPb=|>66U2y^qi4BD7|?2`?oZlqU|$wX);LvT!llvl;E!voI(5yf z1Rf5bfht2ruyC62IE|og2PgsA^QXR~Or5e4Jr481MO+=v9;*ka(ek~xeoVe@lsAFY< z=!H@S*+B~4WPPTi)A2|^8W1(&fZFr*Vw%YTs4k3{W5DqmWPztFokaYr1?5;bM9UK( z4WfvPcyB^z2BxLcl~J(8v8J!(a}BLgrjuy3FpUn`1er&9ybP?xhoko#Us@kkvsTn+ zn1xiv5niFz+E{)gkc{06? z)Yxs+YIKtn8_y$U4ox-~l9J0zBO))5Z1786rBZW5{O@;wLzRJVe z3n?y>PAjL*&3DA(x@}YRPT0(&HVh6b593&Fp>(}WwLlGbiOjm`tw#R3I`wv4rBEIt zLSdP*l43-kD){PB)(eqrQ7(*5)fizztYYi19zW@rb}6hPY464cXNON|mEsc7kAyd( zA~~NuID_DI0Ii*?IuM>&^vN_>M$k1&WRU;bH|kaEDQmFL%()1NTmWR)ADgsm=NHyG zMASqJ(@5*M01FKyK9(OGi5loFYFCpZG$vSHkt{-iIB-u2x(-j89N!)=k=6mAtpu1T zg&_Z$Cs~V^V_)5o8o^FHv1H?$0m-Jgu}{p+6`dE!t5^u^B!Pnk{PnhtheN5m@LPYy z^F~_XGMx(yc+dzVVn~abN664{2zE_-FU~m5YzkROJewYWxA3JRf;@!O5@x*FKp>Yo|f$QRF+{t@>rZ~2GRkhqp^hHXBy91zju`& z(DJ^9c5C?w5<(=z-qYn0tmS2yzKi#;S0|5E#r2JYVe03)j9^60kG6wJ%$TGxjxRP1 z=Z2>BJWwBhKcBtPivD2{)uxH%uz>+EgxLJq^m2nKhrV_@Jj7**B z8F5dlxv(YYR0~;IHTSQks59M^9RZEW`|%&OkS3=QD8W3ogCRs!<;S#B3QTcmFxcEk zy|`%xagoM)?=XHhlhtw@h?;44G>8QhN>-{PS`m$Ntdb^PQ^>fxAh4Lsn{LBTAZN`P z-xAae70LNe8f8L58KbkN_jcq`6-km2rPE4$`WK)KR4W7lZooDJ{I`s|)M%}c;li(i zq%f$)cSfb0K!aut4~5WNb6V-StiDGuEUIyw9&C{fIZ0-s#;57or*pM|nKG!s9>`9F;*{BtVTI$F+stB6(Gc2 ze5L}!EOb-go)C)bkd(ZKC%+QhIz7vABtwR7I;a+vs8)+0{Ve6L(t7y26 z-j`nk;%8$-&J|A5ES=P?Sz|5cXKf7LWOH5=)h=y7gZFZ$?e|iF1+y?~ZZPLNc8>74 zJrj|#h%hX*LQkP<_^8o3z%0~Ya};p+W>U_wy#i(26$xk|;=Ndn1BRJ#e}Sf0a3F-V;mPFGS}_LFwUW73m*CAZDEdH%DRMxfXCl3*K~%x*o?k&9#)@-$Fq3DQ)bb6$Bes*TXgIf69q`hm zKFOBMuosZGO`U5Hd>*AFD(%1fCee}!Av z7cm}uFf_wSt#NzG)ZhLqrd#tk>Boww(`YomtZu=Ki~}d%YQdaXi?*zKG6|ztrK-u9 zvt=CEnSZ1=MU|T*&{(xF98R%3zxyBKdGM94{+x>>{Fzb z9LxA};!ipQzWUxph)kqmO?1N^OoYP%{%8RNOK?S1Ew}|4xvFmk88}brmbPn^dFclYOYdxadIQ{}M5h02zxR3KvkOL@#u+BnHv!NT}LrywmZo*@KDec+A-iPuJ?L z@kU%@^`Q!r#~|doa)S3N4_BHN54@9w`!ublpUi;8oT4f(!H?n$c192?J#x6XHN71) zT&5vOM(S5c=Y!e=4`u5sd4)K#kqirwP{l>!Gt)ha>)1R^7KneD0?W@#hA+(`QS_rQ zElQm@Yax28#6}7?0}HB~n~Z@Fkd5OK->-QgNsXg{ko-eLTFlWB+E-cy<|!%(=tA&&g21m zTU=V$RwXD`q|J<+`tpQJ3wHUcodSAkczFo6Kw5;qgA7J50I)~xwS zBL*7LjAfw0$Ln>n3!P0wPZ}>$oW!U;k4O*F-BBcVS_iR<1vV|MjgdL?K$cc@u+=qI$ye zgO9$Uk^eLqcqSXi=?FmY%_N4FC2W{)$BcCX0N2Sav1GQsOgP!eA)5?_<2qYZXwb#N z*8XG)3K{pMt#O0F1ra|y-$7AsCfGQ&24Yi1x`7@Z;UiCxZb9m*NzQW=xpR*)fgN5K zV!`ovg*04#^~xVu_mdNtoZB-9%H zqsq7vH*mu)r{Gn%_Y-HbduaW@%{WTKVMZ|z0BMAl2gxv_FU6&(U|nL(rCQ+*>Nt3~ zZ__4e`xj@tLPn8GOvFQPWyVn+Es7gEeJ3*i}%al^0K;Yj>YFJ5%uM1!O@h^=~Pyt8Ph zR%I}iMDWlGer>{zg!xw0(HSFDdQmF5rWtGDDS@FQG8_-m6=8J>MR+wz*)GGKzqs4#o74)i$fNt0iI9=%MY9+)x$f zEl@#2706$MoiIpVVCRKEPf;UFLX3NnwEKHOD`Q5%%%i1_Sy~g~H^VtE=8W0Ar|jX_ zlqgackl3&ZD&?Scki5AILKq;Wp-c-Kr{i{{b4^OfC4i4LSOZ2h44dGzJ(N1cO=A0= z?Epq^aOSuXEK3X#yns#(2TyApt5k)w-g5U9tYeuvvr?J#j9)eFW@P?I*Dmz16{*DJ zIX*4v8sX?HqL8Dt+-@0vC|4~AajypGj{s)X2wS_4XR=NwQ?wdj@hcQ>8{KhB6dg5& zN}=SfSAs>lfbj%Dwgnm>+Xe|$hYKQ1ea~{?aTrJ!8au8y#r2qkHn;sVuQlS;FxoCb z6sdH$*>NUS$$Vmvh!;MqQ6&r(8%g1j&}jM*H$>;etaS+`y*w(6bxPQpMC(``73MQr z!Kj2O7FSX0l)+3bdZjTzQ;f?(EC$dljx0lXJT#|yESh+a%9~rjhEE8JSW0^L?Div%aZ$B~ACN%NMefQ8j z)*66HMxe&cehb95ZXry@qarslq>J6&Rv$0c4ToF{>hJ_gA5V|IX_B6O>srH&zTj;Z zn4>JCi8T)eRifL33)(o2ry*!lXHS?{k%b}~h@+|sicRf`h7*Ign02+NvQdd<#Yj1W ztQZV@cK8Au$v$X}&$>v?5}k$|o0$^#-bb`)o@VN3S(0Q5J^;=5SIPs^46!r`@&kky zTMnI#iE?Y0myQ~QUXoH&1x|j?XedXJdkU0oDSEG#g|1l3OHuNgVmv&Rf-;cHE)LBt zd)(TlN#N*B(v#8@mU?~~Mm}UJmIxOx4pPASi0b-qWvd^sz3dR13Bak1iE$`{+{~g$ z?zSa8vWl(dOxqweL2BWNU|pv!gTNf2h8^b;t&IG+m}ntx<{y?zDl6bl?H;-K zLdl}^cF=WhjypQQrwr|7APSH?3G4_gL$?|Vb{h_CS^I8Tfsx@U2Q11WS+=~AIfDig zd4!5^hTkj!s->)Kfk_QBVQ4(_D{v>~0R6GPRF%YX76BUU($rmvA`0d^E({<^jFN6^ zraUBias#Frr5m@Q90u7MHCG;^mV{<3twLx-BR)U}w4G(gZCfyI?J4%BmvAaho6n_O z2n~(|Lvz;bxSAk=2yHpbW#CGJ;c{wG#4A|R5|1`(K3AZk+A>u3f|c|V21sjBp63!e zX=o=RkzSM#CLje>m?R`VJ$c8*O;3h=5a}|a0gH}+8xeuGl(E3MeJ*uC;6uEjxYWyI zE9E#oG9$HZ%zHh8r%nm@Z)nRO#`V9+7x1XBanlWVQb=hZfF`p{7`K%y>1`X26ZUUt z_8GBPueer@9=4$>PFGgAs_~|_4Rm@vUjlht->@_+Ph;TWSuO3$ute6wB4thrpwZ?L_wxK`pLaynq>-LiytWR5>ElQZ&G&Nhw*Frk zTa^**c7qWaqkB&5$s?qFEwx;|9|uY8PngsdMHyfz0Lik#vh3)hpRt}~s2jY)sS)aY z+oUv$))vTsSzVn(KtzNAMvMI;{9JnLiqZnIFk`Naxxw7F3GE-^-fW`s$`^XyVjJNC zJ5-ruz}|72&UCrx^yM5gzea;XowXK6%z!vrXE#Mc0eVitT`@ zw;l;)TRoVSB|H;XHY+niBpA$C#bE(CVJ>XR+7Y@}K$};-8CLkVBl?tT2o0_2VmfpJ zP$y2)o-qik6vfF{0tU)mUXp1V&5!sgaAcX|7PjOCzg73(PT1@R^1&85^icjZ4HY#W zNd@=D2KpZMu=8wEIpHEz8{u}!&P^z7!{^z)Cs5;5p_pI-m;fxMO_cd}aj^o(Xj2MB zsA?1ef!~b!PUl61GU3h@1cVFOcDO4T6^h3QH0z0|UnTBp%ipTOrmG9D&TrjeZt#Qf zG4)h!)vQN8I$JcM5&5VQLX-v;;qLsIi5}EgSKDE|_c)8uK!}!WJ75y$d^9d9GmMV@!$_=; zRoQ%&^YCp%ut}UcDL}q!o*EMZB!0i|+@6SCp=SOk*&_HcesWtC=XNGdfRTospqdhR z#~h7p?;43WCn%b);L1la)oMck9L;8JKzCc=0z+Zp@Woan01LX@4RRgrRxybwzBx61 zqKZ!~Y**nZlLTh?^^XjihGQ8a zwp_?hHOo{TUdtqAh(!&kTNyA_C8eotkH3TzUg={!#IiZ0Btxn2(S`g%73M3SF=XoM z+2F7P%5&Bk3(K;7CK)`JHEljJ41E4_cp7W_PN?}l`5iPJ=%&M~|7k}_qmoLBg?~O% z$zyjJ5fmUU{o?&36SkIQFeA^)!oIYxwdMvQ&1?TLWJl}v@$!FYOa-Bjv*p*yNobys zDP1Ip3ZObM^$aXGE)+?d5l#E4B`jH#Q{kQx4B?lOp|%*yh2;K11Vuhxl3}eg#j~(< zmwCYt^PhkI>wmP{+qeKks_{+W&&d_+K!;b|3Od<=x0zmK!Ob~HN;-DW-8GI@{(%Kdl;K2ICl&y1e-0OF4vwbS z15mKb*U&N->ZZd(aE=I9SRyPBZ+!)nd256Vh1*OZoCOi&ZJsQ>N@et%b{3@U6W5cX zyiXyaG@0GrRZ5yFNaxBSJk%)}t6y$Ja-FFYl4j(4jZky9Ix!Z;3kmP6nKvA+-yWQ^(l4gP=U$Zj zhJ^2aSHCrEvT`JVua$@!F<}d^lpmla_rqnC^de1Y>v&XS&kH-%PqDnn581bds|Y&1 z{y^o5MHp`gTl$A1z`J)NGx3_6FD_3?_rzx?w9*4oyVK-}jtKqCcoFC1hSHGM2rl`C zS$NufJODT{C5Tx9Eq2i==rTXCd$Wd=o_xDAki z_keqa0JIUr4rO#L5V13aK-J6qJtYQwf$n&^|DM}!*vSWBBo&xAxwVi^rGyTV`)(h4 z0PTelL*-hUfVycXleI+)%~wvNH0fAbrtMwe1ymXrVl1TsSGiG3w>q0%Ykb4lPsQTc zQ1_QYC3K=`7o95wwEHd%Zy(hN3fA!MpS!3`pKt@gQ9GYPRY$6p{uk$xK&Q09yy>IB zeVBQ6uIwSq#=LL?))$0OG-%>-$q4lOtSs33sBsHS6y8!x1qJg8&T-8e1!uby?hY2r z`y?kI;xlL?VJL2<6Yk7$bPLj9xVIDM;9t7e z!UMeG`fA2TZdpe@-B5=w2Rl!$mQ2 z<8X3;`N<)pgx-M~tx{gjkGvCe8CD_M^XqiZ3n_lGfMAb9v0D2HC1#p{)B$T5Wf!C{ zzADs)i_lZ@&A6}k_CAKRL2_3mY}7!jr2}3o#iR!w5Wyl)Z&~}`I|VyZpPb2JJPLL^4}HDf#iRG{`+ zXBICduumX8>ClI6MIh|$+E}vT=8+l1{mUZuPSveuyrSPlUFGf`Q`K1v@?sJj2<)GbQhhFcoSD4A%XS!^nH9z0t8s%v4+yS6zBwWXdC zl&PSqHd?b~6;}Q(5$an5*2Y*8^OEszi3#@BF<@KhcLX58EV#%fbuxwCqjT@~6c@|* zU@jLu^s{lBb3qce>q<439%mwG>6di6&9BEVqsUW}sY)x8s0N2l`aTB0>ZbTsEIsn$ zj*HSP2jQGG%foPMTYZ*aCN3#gT&R##f(u_=EyL_WSP1e(;O~c7YWH|_0AUs+zOh~_ zaefNqj{cci0ai4|xI)|BfYl=B5a46scJ>Me07m=SYeNjxJBfLhKz`{C%evM6y8EoS zE56tqE43dT#7W}q%WAS+<;rwEa}4PNFgwZk!qk~vjG=jzSvxa2kFU1JR3#G?$YRUQ z0TtQ)^5npy@ORf4C1#PX*ksuI@C@iS7-X`{)u;xa*nnNi`m;H9Dj0rjl9FNR6{(^y z4c2*|0G>gQzWp;b3yN#?aM7&TOS-TB*k)}@%Lx25)mPFZisBpjDaHC~_X@bxxUMS?Q24f>;`L=alYBD8t6J}Gd=CJcp z%Z$p?;4lDf*8r*Fa`!b)0Hs(fp`Uq%6cndiEF=Cqvj zk^m7?p~oNZj$XHW=%8<;!BiG$(vn<`eK2@JA2dZ@%TxHvtIu5GHuvS zI1;xE7jZsD733{l;Y-bq6lvyO`TkW*K3F%HWS0z{03!-U-Q&?b?)xhov;vIox-#p0 z{bxjB5yL@b{5XP>a^5q`(3Cj6wLngl{`B%pLeCH01GK^-o;3oPPsioF<2z3-&-bR9 zBtzJ}cX>yW{)c})h-2*cK>_G6WI$OrNqVxN=!)CMzdqBHIDJM+R*$Sgc70#0 zd{@A0EiTysEAiGW5*cv|eLWnlmcTSVLq3os{hwj=^JdqFxJ7TV z%auKOGJ@|xD9im16}`M>6-OQTrreCIjl_{=Yaq2Hr*RM)D%9&dqLg9%d{^Y;d|LhO z-&zy5#e?dQX0Ag0q)fB9db^eai~IhsxLOI}tvOs?N{(^@x_`}^mNSxYKV?<~BE0J; zK^wZCu}rH)1g^lP{E(F_nQMXiS*o@8;lLB&QUtRHM=U9!jE<}opBW#ZayAm!|0;B| zi^gAEBGS0^<8q#i%RMDXJ)5l^tpRt>zn$1XyZxRJ!2#BDv827sGHq-9kr*wzcu87R z!;c`;cq0nBE`~zJkN~6BQZREQIzSfuY87Z#mh?5uyZqAC5sbq-i+e69jiTCKtWq){ zI5TTAOzGP9Y;$&dSE9lJM#f{}cPuX?3>lCF{|C)*e=lzV9SC{AktEGRgD2oGOjHRK zP88q3%aKSV#xj;n7~l0q0GGeY z8vf4GRHK>2kSwP0K1D9g6yS|nYS)TlN>uJ@Q0`z)*xG0yR)KL_sq^ifnRs4Tg4a*} zpm4frRohgig5Ju9sltL@*PpTuL)Zj77sh)S&v-Y}k#;0yQe#L|*pPDCNMP4t~37lMV6 zyQy%l(m$5*_p^n#OeL&~c?lpiV5ypyA7>1(@7lHU`tQ54Bd@7~h1KiT;`b@ZYDYR? zqKns0tI^6;_rHQ;pbnysdM&MFvw+QggGq)Mp~1WYKeJXj5(IvFnj8ee=YD~gUd%8i zI-`IZv-yWCI-9SlGw*+urVuKHztmJwy$Y z)mEs>st{wT1d`|*kppw!p4-#B7CGNHF~@U}QhSuhKm+HKms0pTX`HbD?oLLb4Nf9Y^bl#(3b6V2#tga;l6Na^sAY*! zjmO3>^agtOK=%u*{dj5NmO1}@0m>mjed!rZuW`aUV>$L%b2~c01|lq*w}e$!pY3j~ z#La*Z$eKc4=0;p5%kThBSyU*lRYd#4MVcUxb(bOM99?e@SH$V-Hfen~fI`xDi!wE*~k z+Os&c0J#X%a)Vy)xy;}@sa~QsMFrymQEw-M%w{x}bx=#G`A#7`V7`Bj~eCAdYs zPM@$b_B<44$V7wz)QB7#u3*vMV3Zc-$}ZYqZA6H`TU#2=BFgcM5*@7vu%L1`=tzIAaHReGjjXU)m2-MlLfvAL)dr z1t-h$vav}HCD@?At$BBTlct-78SYJWj;vuEJt1spF6A|Gu?6xj>WWI<7zEPFP(4^QDaJ z(;?+*`k{lei-M>FMuL;HV&D8!$|vka`(k$)Yo#S^&7^_f;nKaYc@Z}u_&AJch+bGkVjdtDd7O&V zdzE2md>8B#=~R;jcbk+DFS#yjwG@uISxXQKL&~17k3mw5RI(^kbA+V4VPG@MX*{R& zFD62`(Mn9uTP&LHMOao4wai7A4AwW(634-uiCFS09beE9or z#}&^DN=*ZxQ-JjHDV9>(2Xw)cpn5JOQ7|OCSz&L7M$q+mg`$>wz?2S{?Fp@9SaQO) zO@&Ymu@cIlVRlQHL2oFB+(Ba#y^Ai`L#Y~MG(28EOY}sKKPcxX^qkQ2e>v-HFW?d< zf}@56)HV^!b~Fz#CKGj+{GH2l$jduSS7%ytbd#Xg)m1b?b{@`sMYo_3A50H6iF`J< zZsfeoU9}B0=q24MeY{H?=yDbmyc7sVr!;pRtzi!jsWtOSgBB??Ou>HPK0mw*T2vTz znvc^V#St%W57*Tr7%6KsQz6T7+TOKQhi?drR?K(WDWUfE_9s}v&Z$V%Hlxpt>2P`& z8YD}7?28h+iW?=?S)+^RdLY^Fu$$C*Z{;pRA(jk3aLzyftYCtpY?=SCjWOfrP|ycB-PYkhn6E?$1GO* ziEt~{P^2EXKja3wa!xOV5ds}KHSr|1t_y`tsZBC`1>~Yc$Au4&8Ruf=3Lqy-Y`&y( zh<6eyOK)a5;D}?9nZiRE@5Aa}9?XOw=>bE_3p536ZDVCA!6M-fhphb1KmYtU4rB$p zeMXWZ&uwmhzFVPtyxS-UGDp*lXhGi-FdN*&aNLLLy!TT&Voo1W@jbcoHK~fi8h4d^ ztrxsjH1tpP8dt{8U4bO`s+&J;2m`*G8U!!c9MH*w8m;+x)>T&>84BR22BF{!CD&`Z zab6^9TK}TJ98bC(p4M1ShNtGEblbLPFE1qBt`WH5z8Lk|_UV+z8j;r{9fpf< zhK($9`qL6;v86D?4%EYIl%>UVEkf-`p^|H&#f%UVitD&GVsDj*c4XbN*0PF!htolZxr!jIGG#)xYH|354t22pl()la4Zc# z(%lH5nZ~zQE54)exu`{qslACQ7<^_PXlYd3dcn9^p-$NJW7PZae&Y=2wfh}lZQYZT zkn+{Eq29LoR$k@lxgfD#-USm8x$h26R&aMie(D2lDaM;)DBT zjyVGTi~#~if=i@fff%H6%2w<{<&le*ScLdvh<7>s2WKD=q7fHCKn%lR75*N`(jttHH0~>v2y9C&ePuR#p{#A*bOlCVc zUT#kocvJE0*ze$j(&*9yLLINZ3lw-7cP!m-FzPwx&9BsB9ZB8108c~r7Kul@1 z-nsdhe853itjFkv>>_t6CubT!KH15&8oKz8U+e$0$x~V=MW~CG_AKRn*2_LJiD%f1 zG5iZ;2fXyH@6uEYW22bTCfKr(LjUa3_a9?ZF<;hO1>^vqX!XpD+e)a92YsJ$>)XqL zUn}9FO&<_I>Yx%X+&IJCs9QkvjETAB7ZG#(PHoaJPbx&lvXqLUc0X6&eyGH~@AXDA0Od5CJ0TRkRwszM-v!nP!QQ9drI-1SZjLE1xlihjxJ@tldAY$w#N9puK z(#K~>t!=J^cq`l%eFpN%!>o)A&z2q(m&`3ib7vE{$1Ag>m8}~Y6C+XM@dJ2aM=5?A z1#%X21)C@saQ>t6{@2~g!CmSKcnPwLt7(h{UZc}n`H+&VwfAW+=e2R0VvfV`3>l$Nk!SZ#k+ujBHviUCQy6xuQWS}u& zQ45Ea4OKJHp=u_Th9tX!aA2{A?1o0l;$Di#-B7{vke)yqpjb#5F3Ek5Ssh+WU4uf_ zl9zjkBXL)STO4LkC+P$zH$V%e<`}1Pskbke4%J!zC2G1b36TW?D8yv6NS9<7ac=Oq z_q$8X`7n|ecNIa)>PkkcUhXISc&kvz@s#C5y`PVs>Ya&dEiQAg1;E(9tZ<9$MqvZN z{o_Gvs`7dMeiQfF5%C+?3UMoq@XqF&LZwab`mVgGzZJ48an|h;v&8^`Js{W&UwFp*4t90OBid- zWls%$K80l@9GZ5AdNWvwwEBRG)q7`eg33c6J#iV{b)flE8;s&H>3?uD(SUrneK0@~ zLa-#7cZ>%P_Rs$Q7`D6W6EMs;MszR(3WyVtKdd6vv5agv65(fsq$rb;>)&V{wuCEV z0T`+Qaq?)0oV4`BlR9&O>y?TWjw*$JdS9yfuYn>iHD4fqy-NM|RXAvSg*tlnyMh-}}?sqFOv3w!X&;BrBdZ57Y#1tn>(T1T5w`=oWi#=F20s|n5l z8|-`zJb=|h{b{!b&p3$M(B!iN(z$huFuZ<~$bXdFw8b84Ad=^qT$0V=h&WFJ%22#5`p<0irWFioX|)s1G4TLz!@ZrE_BP9sCaVfEqI4M9CPSS zrx>qke72Vq#kVwjUXK`xB-f2eA~@~1V|RPtlwRFfg@i|5-D(+-WI$*N?l(6hyV>la z(u+~(wBSC>+y#GchJks1W;2QA7%@58} z$RI1Bgdod=NJhd;ik?`0KiT36jN>F^r17JV7{6_l;P+`uP$aW7mh%+p_+3%vuQ*3w z|0b^aT1P=Y(^Iacg-IHyto@NEe>{XQWlNAQ{Y&yW*SHFwMpOTIbebC~lqU1f*1eDP zc~|ghi-Q+o*WteYgEs48^2r+2@D@>c`Ig+0aY3^W-Tj%JDvGQ3N~a zLJb?XwHqw>Oa?OM5v>4{(6f zU7vStq@H>=?MK`6zC7$EjXngk)^S}8;=S0-gG&TM97c?0Xs-UlkbYU$}pbfvr|~At+6f{-UE~%+l3oy*jI6i&)+n`v3>3)K-N6$SDaVY3W#@ z@Y;Oh%$GE)68ZF?@-k)|$mv(9E@%F}zGas9eG-!s;~FC{Ie*M7mfzc>q`<}VbX{H= zC}fc@>-snSqe9lW=Rv0dn0kyX%u`7}-;=-%_LieaPDI$srpbVz4avIga(tz5KI->+ z$i-B;#fwu^OpOrFSQ2c}swea>EyAAl$2Od~t}!5XnTe*?h?o1X1o6?6Lr809P3FkP zn?41K2dF@?$f6A4DppBXD$0ZoqkzdwHAQk6k9-d>IDa1+y(E3 z>;a!6o)uhaQKwLnLOxqLY^0P8!1z-(EnBl7Bivmy550j}j+FxtE5~${hs+IFrJW`5 zbE_HpB870a+z=~~&O|X)+c-H10)_CyvBZL&(yg4;G?sWZPku7tPFx%E_$~t`GidTy z99q>h0lZ{Cg%oE6GBCXEi&{+jA1QpiPpwXIHKOyn53wNj&5`S7AFel?7&|}d1{EXS zJ~7Ih;4#bMmK{A|(|n}_L<^Ayxp}^~-KPc)y^431u@#Qe%9c+Ke6vcDS|7qW3C{;( z9hSz(ME=|dAgm8~fOTMWZGuEiY`la|tP$iiz`pK)bZgS<>X*Rq-CJ-IQmnbYOEW4p zNf(cKSXl7hI%QgxB5P=D|J~$a|Ma&_d40foNp?4akXWGZUHjaYs9K$t=4KAmGOH)%JbFNoAm*$#53>wbp#kB0?p)gnb-=Yzh&6TH4F8@iR)O7&V z96uvN{V{r@-$6LR8b=@J8vgI}+ReM0F&q6upbSiAv%ZA?daqX_y+ZK4eZfingb}jJ ziW03qGmz9Aq{9UhFTKy%wR^c#1{$Z;{;zZd8!500Dm(9NZvPs*AHPSjJb&fFqdz3& zrPKp??=NBF>z?P>@ld8>`YqzMbUrJXjOd~eAB4`uI37a1;GkTABbl(M@!!n(G+q~` z2?qdBXmJ!${K~aH3lo7*&Jr(K@|dt4FqeM<-Q{{o?Mtbu`(;IZaGnk2Hz(Kd57uB5&&yh;89uR375-}S=R7jp5osVqb^Jc zCmI1KnWA*UCP6SU+icJ@PzTc7R}h%!oJBiILUN^<`fr#XSHB*UdQkgt z5-id#q_o)>4ccbFT9UNy-rWrF^YNsC@|SmxjbZhkrU-Q-zO>X|9zU<%XpcmM1qlup zI3eYO@01y#C|P+U^6jGeEn2)Yz4&UN&fL+t9Q%fhX;ulbLlhf}$9Flb;%8(@Tetr0 zoBZ{?{iAhuT@C$alz;Q|+@y1@OL58Hg^2E# z6Mt!T&r@wa-fTakAu}WvOHS|%Z2u@fhDcibIByzgfMv0qVZ}4S3D0c2&G~W z<47RIw>(L;3KeoypNWwNI^E>4X2x$+5eI%IDkm%7BCXXX9ppMU;G26GfxA4%`>oWJ%LQHf>p zT?in0QuhXW5utrdvdS)Pzuw^~`qvAyuTd9BgwI%;?1b67u13CwevfOq`1+fMJs=F@l6EK8M3R!cvd-`Prf@*Doq+n?PPG#NH38sy*byg^WLpFyc_p?T?cb#N} zh^7?8>6I0ov1^b_+uvsE_CoI#s29V~*xN{sHej}5OIKT z=;-gs^M+!{We3Jr@xs_eaf0^9l*Hei?RPGPwM=hg$ zH^Z0Jf@JCLb-7LPq{4C|i>T@O$r2OJVI^%b#o zynD()@%v*|Ny6%V9vt6GINjnf7F90GlI7OA*-aF?a?^ZcdDmeN6{e*)XF!r9{Zi4f*QUP-Vj1C~}) znqdpso)-VlF^%4hIa@`y=_axoM8#HB8sieg7ZKp>MI?M!RH4^4DDKmTHJ2F$=RRQN z&sr%;p`8qcn@>mHPt_s5Bvwv={!^MT_`vo@A9T=;b005VQFT!z2pCMtcgnFN;)|NE z!&Hlmm?EPG^G=BLEA1&h0L>=0im#FhCG6|iO{LvdVTi9gUh6Gc+l~^h6kAW%UM(I} zn;hKZb-R7u{aWC3D%e5u!TOLuI3KF!SZvTLxJjJes^BfDTKff(O*5z4CU^6kZ#=Hs zC0l`$mScE*KI)DOlW{(e7jmkhH}TXf@uw+=^B;=2Bh%A+oVEyJK;W!nI?xG z^eJqgT&O9~InGcMLC21nc3LeQ;G>N?64+maOQOQcC9j)Q(I%UmDRE{i!iOkq~mbiCMtG5ob`ikFx&UayfSS5bX`xv5<0~Dw6 z`*Og`$WUIHex_YIFi9&o<;L({A5f_5pdc)yJ}z?vSyt&iJMCSZQ$5JQOaJ)C1n%=8 z)M$S`BYsW-BU)cAcLcd*;v>C6$9+8oef~aljA8dZ?vcP>!;{Tu}CvCp|>>rPxCVhZ%Y^?^0$U;6iZOKokkirItejo_iyB^djmp(2Es(7r2Bkgf zCF}fSnp|ruX6Y2l!LyIlOwzvcsGkEsuI%QvPpD)JhHyM+L;Ox1O!(>Io}vs`aNqJg zIp%a59Vy&@%FJB*q{zgj3PUqvlYdooQu$v9+m7LXjxle)V(BOmSv%+hv9!%NK<+c$ zc9>W}MiSI;AQ%Ev?Wk=p8JA_wqFb^+4TNR?0u|(H^%F>}m{k-JyoIhqEwY+UK{eck+tR2~9b}9rr zIoj49NlG(h4Z)CLAhy=)29aM(p7pcFb5;*tSUkrPRMIoqv>pl;ItD!Ao@x3OfrkS{ ziU=>w8|pTzS7em6BBWP`-$;m6cv}zF;QJZ0S3WP&?))qq>!VqEZ|n78DX05ciVeqr zzPa&`dN^BIJ8gjt?PXSzLvX*lMU5R|tYYYjQ}l>dB@#0PnO}zMp)SmS$_UdgXRB!!sj6Nv2Hl+)|Bw!rf;`cTm?#eygy4xljr#agPZ zCYzu|q^PbLD%mY+rzLd~rwWp=)MzPEr%j}S_e&!=m&2>Oiua=`;VdW&hv%RYfik4` z6L9CpY0CEWp$h%<_~8R}#cIPBM}(nH-X_aKvB|eMvmo$S^yLF7inWa_uXNpi;JDyl z50ZL*n9T4f^o9gVk|DA0AuYyd2@i04Dj;`dIt-M3rRvdOG;Aq*7Ls~&E?ExA_&%(0 zsHwrL=eS2+HK;RvrYRCf@*=WmRui{;&RgaBip%Zz05QQ5wF)MsTHm#efl)msMbnjH zA25T}+h9uBdo6o&#DZXh)a)Nuq0!yyv=g~+`$I_A!(7>Hs8Y~!djjgbDDbP6ONwIy z%`f8$RRID5IHv&4XGb@el$#Lf)e40dyd2AgJ?kyB+PZn1{BRo$g*pvl6wu~-$P&U@ z%oDwJi^mb@YJGEZGjRbtWK19x_=h#8$rt4uNK13x98Uba!Zz2QNh)BUDd%Fo1%b&8 zeNnoYz)9p>`h0zv52d+e>biS`y0FHvTBWj4vsxTacCM1)DqW>Rb%U)jwHWD_Lj>zF zB>D1c{@OzH0jbh-`<>oo$@{-o@tnrVOu}HalL!Mc#ImKRQ)`Lg5pQoZVNGQdl-hS< zb&rzrJK4J;-w+R!2OCgZ$u$YRBiun2Wz2CQ^JZ>WaSfn0M(Od}esPZZd>R_3GB@N% zmo*e(0=W!8PIFL}c<(5qbrxIDq*S>_>VYz|_E~2Y{MaZypJnMLc^eyjPi9Gc(QDlO zki8SIBa~k9>3}omlxw64@^N+NE;)yGI!LWLsEOHZU}k{G7S z-AL@JFfOnOGuy1t+3=`XovR9}rAf*d$a#&gYqS|3^=wo$iBi3uZCmjo$_cpV5+8*z z2wZQb&(KV}OQWGxosEnN3KGKu%niYF_#lJYGavd`em_#LC`mgt@Ro@tY720mOmJlF z+LY0_3s9(5#&z3a$Ek}eb2Y|Rnjo~F8k4sG^vFF|1te)Hh4~8T2YejU<}z=7s(*?! znh&*pF19K&&~li4p(YHmQ6?Rm7Qb7hKU4idzxATA>z74%8+>sJD>ikGo#4_xY5=mX zGzu4(7#30CcIYAXLjoc#^sldRe*3HRS!iCS+xTY@anw6IA{Z$J21pk)QX^(||23T& zwq;5}y~f*qsVhaS8BK-$(2x6`z<|U23k<>>C;dhhM{NMbYXk9|H_c+k+`trQ;t(&A zR3nV@)viDcW7Fr`8wL{`&c6GF!~%$Z+nn<=Y>^P7z~7$q@;twg(XaBR#udm1T=)|wqz!bW5&m+zT_PxiO-y2wocZMS}*;V|mlzyBWq0RR6rYm}kY3%dmX O0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00006VoOIv0RI600RN!9r;`8x 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%0D-6Bv5K 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 literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..a7670af --- /dev/null +++ b/public/index.html @@ -0,0 +1,644 @@ + + + + + + + + + + Steep — Let your code steep + + + + +

+ +
+
+
+ Steep — Let your code steep +
+
+
+

AI code review · built for Gitea

+

Code reviews with the full picture.

+
+
+

Steep explores pull requests inside an isolated Dev Container, then leaves focused, line-by-line feedback right where your team works.

+ +
+
+
+ +
+
+
+

Thoughtful by design

+

Useful reviews. No noisy pipeline.

+

Bring an AI reviewer into your Gitea workflow with a single comment.

+
+
+
+ +

Reviews on demand

+

Mention Steep in a pull-request comment and get an AI review streamed back to the PR. No CI hook to wire, no pipeline to learn.

+
+
+ +

Sandboxed by design

+

Every review runs in an ephemeral Dev Container with read-only tools. The network is cut after setup, and the container is removed when the review ends.

+
+
+ +

Lightweight & observable

+

A lean Rust service designed for fast reviews, with Prometheus metrics and Sentry error tracking.

+
+
+
+
+ +
+
+
+

From comment to context

+
+

Your code stays in its environment.

+

Steep gives the model tools to inspect the repository instead of sending the source code in the prompt.

+
+ Set up Steep +
+
+
+ 01 +

Steep hears your request

A Gitea pull-request comment triggers a review when you mention the bot.

+
+
+ 02 +

A disposable workspace spins up

Steep clones the pull-request head and starts the repository's Dev Container—or a safe default when none is configured.

+
+
+ 03 +

The model explores, read-only

It can inspect files with tools such as ls, read_file, and grep. Setup hooks run first; then network access is removed.

+
+
+ 04 +

Feedback lands on the changed lines

Steep posts actionable findings with severity labels, plus a concise summary of what the PR does well.

+
+
+
+
+ +
+
+
+

What’s brewing

+

The roadmap

+

From safer reviews to an assistant that can help move work forward.

+
+
    +
  • 1.2Sandboxed pull-request reviews.
  • +
  • 1.2.1A default Dev Container when a repository has no configuration.
  • +
  • 1.3Type-safe routing between review and implementation requests.
  • +
  • 2.0Implement requested changes from issues or existing PRs, then create or update a PR automatically.
  • +
  • 2.1Project instructions from AGENTS.md and separate review/implementation models.
  • +
  • 3.0Automatically investigate and fix project errors reported by Sentry.
  • +
+
+
+ +
+
+
+

A few minutes to get steeping

+

Bring Steep to your Gitea.

+

Follow these steps in order. You will need a Rust toolchain, a reachable Docker or Podman daemon, an OpenRouter API key, and a Gitea account for Steep.

+
+
+
+

1. Install and configure

+
    +
  1. + Clone the source +
    git clone https://gitea.qpismont.fr/qpismont/herald.git
    +cd herald
    +
  2. +
  3. + Create a Gitea token +

    Create a token for the account Steep should act as. Give it access to the repositories it will review, including permission to read pull requests and publish comments/reviews. Steep uses this token to identify the account; you will use that account's username when asking for a review.

    +
  4. +
  5. + Configure every required setting +
    cp .env.example .env
    +$EDITOR .env
    +

    Set all nine required variables:

    +
      +
    • GITEA_URL — base URL, e.g. https://gitea.example.com (no /api/v1)
    • +
    • GITEA_TOKEN — the token created above
    • +
    • GITEA_TIMEOUT — API timeout in seconds
    • +
    • OPEN_ROUTER_API_KEY — your OpenRouter key
    • +
    • OPEN_ROUTER_MODEL — an available OpenRouter model ID
    • +
    • OPEN_ROUTER_TIMEOUT — request timeout in seconds
    • +
    • BOT_MAX_CONCURRENT — maximum simultaneous reviews
    • +
    • HTTP_PORT — web server port; use 3001 for the commands below
    • +
    • WEBHOOK_SIG_HEADER_SECRET — generate this in the next step
    • +
    +

    Keep the example's numeric values for the timeouts and concurrency, or replace them with positive integers. Do not commit your completed .env.

    +
  6. +
  7. + Generate and configure the webhook secret +
    openssl rand -hex 32
    +

    Copy the output into WEBHOOK_SIG_HEADER_SECRET in .env. You will enter the exact same value in Gitea in the next step.

    +
  8. +
  9. + Start the daemon, build, and launch Steep +
    podman system service --time=0 &
    +export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock
    +cargo build --release -p steep-server
    +./target/release/steep-server
    +

    Keep this process running. With Docker, omit the Podman command and use its socket; the default is unix:///var/run/docker.sock. Optional settings such as metrics and Sentry can be added later.

    +
  10. +
+ Explore the source on Gitea +
+ +
+

2. Add the Gitea webhook

+
    +
  1. + Choose an address Gitea can reach +

    In the repository's Settings → Webhooks, add a Gitea webhook. Set the target to Steep's public/reachable address and port:

    +
    http://steep.example.com:3001/webhook
    Replace the host and scheme with yours. Do not use localhost unless Gitea and Steep share that network.
    +
  2. +
  3. + Select the event and set the secret +

    Enable at least Pull Request Comment. Paste the generated value into Gitea's Secret field; it must exactly match WEBHOOK_SIG_HEADER_SECRET.

    +
  4. +
  5. + Allow the connection +

    Make sure Gitea can connect to Steep on port 3001 (or your configured HTTP_PORT), including through any firewall or reverse proxy. Steep must also be able to reach Gitea's API, OpenRouter, and the container daemon.

    +
  6. +
  7. + Trigger a review +

    Open a pull request and add a comment that starts with @username, using the username of the Gitea account behind GITEA_TOKEN. For example:

    +
    @steep review this PR
    +

    The mention must be at the very start of the comment. If nothing happens, check the webhook's delivery history in Gitea and Steep's logs.

    +
  8. +
+
+
+
+
+
+ +
+ +
+ + + diff --git a/scripts/build-website.sh b/scripts/build-website.sh new file mode 100755 index 0000000..4697daf --- /dev/null +++ b/scripts/build-website.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +IMAGE="docker.io/tintounn/steep-website" + +if [ -z "${CI_COMMIT_TAG:-}" ]; then + echo "Error: CI_COMMIT_TAG is not set" >&2 + exit 1 +fi + +TAG="${CI_COMMIT_TAG}" +IMAGE_REF="${IMAGE}:${TAG}" + +echo "Building ${IMAGE_REF}..." +buildah build \ + --file public/Containerfile \ + --tag "${IMAGE_REF}" \ + public + +echo "Pushing ${IMAGE_REF}..." +buildah push "${IMAGE_REF}" + +echo "Done: ${IMAGE_REF}" From 8c763786c11151ff91c8066cc102f1210129acc2 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 6 Oct 2026 19:41:14 +0000 Subject: [PATCH 2/8] Remove apalis + redis => use postgresql instead --- .devcontainer/Dockerfile | 3 +- .env.example | 2 + .woodpecker/tests.yml | 17 +- Cargo.lock | 2945 ++++++++++++++++- Cargo.toml | 6 +- Containerfile | 12 +- README.md | 24 +- crates/steep-server/Cargo.toml | 6 +- crates/steep-server/src/api.rs | 26 +- crates/steep-server/src/bot.rs | 178 +- crates/steep-server/src/bot_actions/review.rs | 42 +- crates/steep-server/src/db.rs | 43 + crates/steep-server/src/dedup.rs | 170 +- crates/steep-server/src/env.rs | 6 +- crates/steep-server/src/gitea.rs | 22 +- crates/steep-server/src/job_queue.rs | 196 ++ crates/steep-server/src/main.rs | 48 +- crates/steep-server/src/metrics.rs | 2 +- crates/steep-server/src/schemas/job.rs | 31 + crates/steep-server/src/schemas/mod.rs | 5 + crates/steep-server/src/schemas/review.rs | 35 + crates/steep-server/src/state.rs | 8 +- crates/steep-server/toasty/history.toml | 6 + .../toasty/migrations/0000_init.sql | 28 + .../toasty/snapshots/0000_snapshot.toml | 160 + 25 files changed, 3614 insertions(+), 407 deletions(-) create mode 100644 crates/steep-server/src/db.rs create mode 100644 crates/steep-server/src/job_queue.rs create mode 100644 crates/steep-server/src/schemas/job.rs create mode 100644 crates/steep-server/src/schemas/mod.rs create mode 100644 crates/steep-server/src/schemas/review.rs create mode 100644 crates/steep-server/toasty/history.toml create mode 100644 crates/steep-server/toasty/migrations/0000_init.sql create mode 100644 crates/steep-server/toasty/snapshots/0000_snapshot.toml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 44eff67..585d054 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -15,8 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ cmake \ pkg-config \ clang \ - valkey-server \ - valkey-tools \ + postgresql \ && rm -rf /var/lib/apt/lists/* RUN groupadd --gid ${USER_GID:-1000} $USERNAME \ diff --git a/.env.example b/.env.example index 9f25ae2..1cf4a5e 100644 --- a/.env.example +++ b/.env.example @@ -22,3 +22,5 @@ METRICS_BIND_ADDR= # Sandboxed tool execution # DOCKER_HOST= SANDBOX_MAX_ITERATIONS=8 + +DATABASE_URL= diff --git a/.woodpecker/tests.yml b/.woodpecker/tests.yml index fa79918..e1eda5f 100644 --- a/.woodpecker/tests.yml +++ b/.woodpecker/tests.yml @@ -2,6 +2,14 @@ when: event: - push +services: + - name: postgres + image: postgres:18 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: steep + steps: - name: fmt image: rust:1.99 @@ -15,10 +23,17 @@ steps: - rustup component add clippy - cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: wait-for-postgres + image: postgres:18 + commands: + - until pg_isready -h postgres -U postgres; do sleep 1; done + - name: test image: rust:1.99 + environment: + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/steep commands: - - cargo test --workspace --all-targets + - cargo test --workspace --all-targets -- --include-ignored - name: container-build image: quay.io/buildah/stable diff --git a/Cargo.lock b/Cargo.lock index fe89039..d04968b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31404e1443b7b7bcaa311c1af456775cc3f668e34573f1503d7ce4844327629e" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "bitflags", + "bitflags 2.13.1", "bytes", "bytestring", "derive_more", @@ -165,6 +165,51 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aegis" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41585f248ed9406567ae6d61ca2a55ed3dc7348e65203a01229881253acb95e3" +dependencies = [ + "cc", + "softaes", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -174,6 +219,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "android_system_properties" version = "0.1.6" @@ -183,60 +240,78 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "antithesis_sdk" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08410fcac93669a476c006cd6c4512ac1e2b30fd117231a5d55d8a2c76599b82" +dependencies = [ + "libc", + "libloading", + "linkme", + "once_cell", + "rand 0.8.8", + "rustc_version_runtime", + "serde", + "serde_json", +] + [[package]] name = "anyhow" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" -[[package]] -name = "apalis" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504d52557a16b7b202941660f339c9182910d498cac40f93d15f6b31e6bef290" -dependencies = [ - "apalis-core", - "futures", - "pin-project-lite", - "serde", - "thiserror", - "tower", - "tracing", - "tracing-futures", -] - -[[package]] -name = "apalis-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3948d2051795c769e6a44a46549879af39d5e8e4fb113e0011cf99f7cd21b657" -dependencies = [ - "futures", - "futures-timer", - "pin-project-lite", - "serde", - "serde_json", - "thiserror", - "tower", - "ulid", -] - -[[package]] -name = "apalis-redis" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871367e3dfff2accda824ccc6e4f46f8f155c865065a450c466a7f2835541d55" -dependencies = [ - "apalis-core", - "chrono", - "futures", - "log", - "redis", - "serde", - "thiserror", - "tokio", -] - [[package]] name = "arc-swap" version = "1.9.2" @@ -246,6 +321,49 @@ dependencies = [ "rustversion", ] +[[package]] +name = "aristo" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdeefa800110050103e2459a2f8dbdbd560ad046e30f1f87e24ce19c2cb8bde" +dependencies = [ + "aristo-macros", +] + +[[package]] +name = "aristo-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64a66d21a80182b35b1741997a6d2456911f54b7eb1918aa4e2382fc205268d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "assoc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -310,15 +428,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -334,6 +443,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" @@ -352,12 +467,95 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bigdecimal" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fa3f3d8cbf4dffcfe4991de61d012bef509a409ecbe9dd41049bfe32b4d4653" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.119", + "which", +] + +[[package]] +name = "bit-set" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec", + "serde", +] + +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -410,12 +608,83 @@ dependencies = [ "serde_repr", ] +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "branches" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7436b21fd6195415058eb41c9090e156b556bc7e0377bb57c5c026a421521525" +dependencies = [ + "rustc_version", +] + [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.12.1" @@ -433,12 +702,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", ] [[package]] @@ -453,6 +731,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "cfg_block" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -460,11 +755,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", - "serde", + "wasm-bindgen", "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.8" @@ -472,13 +841,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", - "futures-core", "memchr", - "pin-project-lite", - "tokio", - "tokio-util", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.16.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width 0.2.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "convert_case" version = "0.10.0" @@ -504,6 +902,48 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-epoch" version = "0.9.20" @@ -513,12 +953,102 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +[[package]] +name = "crypto-bigint" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +dependencies = [ + "cpubits", + "ctutils", + "num-traits", + "rand_core 0.10.1", + "serdect", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "crypto-primes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f" +dependencies = [ + "crypto-bigint", + "rand_core 0.10.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +dependencies = [ + "cmov", +] + +[[package]] +name = "deadpool" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" +dependencies = [ + "deadpool-runtime", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae" +dependencies = [ + "tokio", +] + [[package]] name = "debugid" version = "0.8.0" @@ -530,15 +1060,70 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.8.1" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "der_derive", + "flagset", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" +dependencies = [ + "const-oid 0.10.2", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "deranged" version = "0.5.8" @@ -586,13 +1171,37 @@ dependencies = [ "tracing", ] +[[package]] +name = "dialoguer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "dispatch2" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags", + "bitflags 2.13.1", "objc2", ] @@ -613,6 +1222,18 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -622,6 +1243,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "env_filter", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -638,6 +1279,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + [[package]] name = "evmap" version = "11.0.0" @@ -649,6 +1300,35 @@ dependencies = [ "smallvec", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "siphasher", +] + [[package]] name = "fastrand" version = "2.5.0" @@ -667,9 +1347,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "findshlibs" @@ -683,6 +1363,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -720,19 +1417,10 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.34" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-channel" @@ -751,14 +1439,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] -name = "futures-executor" -version = "0.3.34" +name = "futures-intrusive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", - "futures-task", - "futures-util", + "lock_api", + "parking_lot", ] [[package]] @@ -790,19 +1478,12 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" -[[package]] -name = "futures-timer" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" - [[package]] name = "futures-util" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -813,6 +1494,21 @@ dependencies = [ "slab", ] +[[package]] +name = "genawaiter" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" +dependencies = [ + "genawaiter-macro", +] + +[[package]] +name = "genawaiter-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" + [[package]] name = "generator" version = "0.8.9" @@ -828,6 +1524,16 @@ dependencies = [ "windows-result", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -836,7 +1542,7 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -847,16 +1553,44 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "h2" version = "0.4.19" @@ -888,6 +1622,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2 0.2.21", + "equivalent", "foldhash", ] @@ -896,6 +1632,41 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2 0.2.21", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "hashlink" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" +dependencies = [ + "hashbrown 0.17.1", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hex" @@ -903,6 +1674,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "hostname" version = "0.4.2" @@ -970,6 +1759,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.1" @@ -1084,6 +1882,31 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collator" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08984ed58ac439ebf3e13d2cf26b0c46a60afcd21721c1d14087c0b240344dda" +dependencies = [ + "icu_collator_data", + "icu_collections", + "icu_locale_core", + "icu_locale_fallback", + "icu_normalizer", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_collator_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d7e54efdddeb1208c08dd5d32b53a879ac00d2d3d051b2255fd26821d16368" + [[package]] name = "icu_collections" version = "2.3.0" @@ -1098,6 +1921,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locale" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "785f595c61ef57169a467eeed7b4b6936a66f6cacbb116a96ee86da983251bf4" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_locale_fallback", + "icu_provider", + "tinystr", + "zerovec", +] + [[package]] name = "icu_locale_core" version = "2.3.0" @@ -1106,11 +1944,38 @@ checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", + "serde", "tinystr", "writeable", "zerovec", ] +[[package]] +name = "icu_locale_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37d91460e362a5cf58907cd7ce871411775ee6294e49a5cc8e6cec16dfa75ea" + +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + [[package]] name = "icu_normalizer" version = "2.3.0" @@ -1122,6 +1987,9 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] @@ -1160,6 +2028,8 @@ checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", + "serde", + "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -1194,6 +2064,12 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" +[[package]] +name = "index_vec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" + [[package]] name = "indexmap" version = "2.14.1" @@ -1204,18 +2080,183 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-uring" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3bd0ecfbb87805f538bb7b32e5239ca0763890c623e349860ecba69469f2bb" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + [[package]] name = "js-sys" version = "0.3.104" @@ -1239,6 +2280,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "left-right" version = "0.11.8" @@ -1256,6 +2303,68 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linkme" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3045e122bd98aef8ec3ad58ce84f0791f64e70163d1a02710af4aa11a4d54cc5" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77060ebe535362c3da75682cd17b0431017b6e7c5661e714fc69a7ad017d1301" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1302,6 +2411,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5" +dependencies = [ + "hashbrown 0.17.1", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1317,12 +2435,31 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metrics" version = "0.24.6" @@ -1365,18 +2502,46 @@ dependencies = [ "hashbrown 0.16.1", "metrics", "quanta", - "rand", + "rand 0.9.5", "rand_xoshiro", "rapidhash", "sketches-ddsketch", ] +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1394,7 +2559,7 @@ checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -1421,12 +2586,22 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1485,7 +2660,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags", + "bitflags 2.13.1", "objc2", "objc2-foundation", ] @@ -1506,7 +2681,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.13.1", "dispatch2", "objc2", ] @@ -1517,7 +2692,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags", + "bitflags 2.13.1", "dispatch2", "objc2", "objc2-core-foundation", @@ -1550,7 +2725,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -1568,7 +2743,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags", + "bitflags 2.13.1", "block2", "libc", "objc2", @@ -1581,7 +2756,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "objc2", "objc2-core-foundation", ] @@ -1592,19 +2767,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags", + "bitflags 2.13.1", "objc2", "objc2-core-foundation", "objc2-foundation", ] +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags", + "bitflags 2.13.1", "block2", "objc2", "objc2-cloud-kit", @@ -1644,13 +2828,25 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -1703,6 +2899,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pack1" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b7bb0ecf2e447b1f20ee94ee79ef6eed1e9d4b3c36ce1903b9dea3bf205523" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1726,6 +2943,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + [[package]] name = "pem-rfc7468" version = "1.0.0" @@ -1741,6 +2964,25 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -1767,24 +3009,123 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs1" +version = "0.8.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f485dbafe293ea6ccaa7d82bc591651a796eb8a9aec3df040b057b706073dbc3" +dependencies = [ + "der 0.8.2", + "spki 0.8.1", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.2", + "spki 0.8.1", +] + [[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "pluralizer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3eba432a00a1f6c16f39147847a870e94e2e9b992759b503e330efec778cbe" +dependencies = [ + "once_cell", + "regex", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "hmac", + "md-5", + "memchr", + "rand 0.10.3", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" +dependencies = [ + "array-init", + "bytes", + "fallible-iterator 0.2.0", + "jiff", + "postgres-protocol", + "uuid", +] + [[package]] name = "potential_utf" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ + "serde_core", + "writeable", "zerovec", ] @@ -1803,6 +3144,25 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1812,6 +3172,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "quanta" version = "0.12.6" @@ -1822,7 +3205,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -1842,14 +3225,58 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -1859,7 +3286,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -1871,13 +3307,28 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xoshiro" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -1895,32 +3346,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags", -] - -[[package]] -name = "redis" -version = "0.32.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "014cc767fefab6a3e798ca45112bccad9c6e0e218fbd49720042716c73cfef44" -dependencies = [ - "arc-swap", - "backon", - "bytes", - "cfg-if", - "combine", - "futures-channel", - "futures-util", - "itoa", - "num-bigint", - "percent-encoding", - "pin-project-lite", - "ryu", - "sha1_smol", - "socket2", - "tokio", - "tokio-util", - "url", + "bitflags 2.13.1", ] [[package]] @@ -1929,7 +3355,27 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", ] [[package]] @@ -2020,12 +3466,77 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "roaring" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bd8a37d17a58532776dcdf6041ce64929adca78e8489d5cacbafe99229d3e1" +dependencies = [ + "bytemuck", + "byteorder", +] + +[[package]] +name = "rsa" +version = "0.10.0-rc.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33405e321e16ef941ca66e254f6904c3aafd3c7ff001fb88bf11c73eb75c2b85" +dependencies = [ + "const-oid 0.10.2", + "crypto-bigint", + "crypto-primes", + "digest", + "pkcs1", + "pkcs8", + "rand_core 0.10.1", + "signature", + "spki 0.8.1", + "zeroize", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror", +] + +[[package]] +name = "rusqlite" +version = "0.40.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" +dependencies = [ + "bitflags 2.13.1", + "fallible-iterator 0.3.0", + "fallible-streaming-iterator", + "hashlink 0.12.2", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + [[package]] name = "rustc-demangle" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2035,19 +3546,77 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -2057,6 +3626,44 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -2069,6 +3676,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -2096,7 +3712,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation", "core-foundation-sys", "libc", @@ -2197,7 +3813,7 @@ version = "0.49.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d889520a375e5b93efb0a66def1630d21d168b0251eb55b286b03fa940ccfc84" dependencies = [ - "rand", + "rand 0.9.5", "sentry-types", "serde", "serde_json", @@ -2220,7 +3836,7 @@ version = "0.49.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6c1bee99be05938885266fe5c95e0e4a2f46722cae205519f26e4ebdddd733" dependencies = [ - "bitflags", + "bitflags 2.13.1", "log", "sentry-core", ] @@ -2256,7 +3872,7 @@ version = "0.49.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77796d14eedbef22c2fe78e9c69fb09f14c7ad607e624d48dce92eedc17a136e" dependencies = [ - "bitflags", + "bitflags 2.13.1", "sentry-backtrace", "sentry-core", "tracing-core", @@ -2271,7 +3887,7 @@ checksum = "fc71f5ca55942d9b2901af95d5df5c5d65c164134c22a83682cac3d0b6c7ef2d" dependencies = [ "debugid", "hex", - "rand", + "rand 0.9.5", "serde", "serde_json", "thiserror", @@ -2345,6 +3961,15 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2357,12 +3982,44 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest", +] + [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2372,12 +4029,44 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "shuttle" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" +dependencies = [ + "assoc", + "bitvec", + "cfg-if", + "generator", + "hex", + "owo-colors", + "rand 0.8.8", + "rand_core 0.6.4", + "rand_pcg", + "scoped-tls", + "smallvec", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -2388,6 +4077,47 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest", + "rand_core 0.10.1", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simsimd" +version = "6.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" +dependencies = [ + "cc", +] + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + [[package]] name = "sketches-ddsketch" version = "0.3.1" @@ -2416,6 +4146,104 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "softaes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fdfe13e1c42e45f497416afb8dc4e783bf0e25b7be9077c1c77c5fb14cb998" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" +dependencies = [ + "base64ct", + "der 0.8.2", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "sqlx-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" +dependencies = [ + "base64 0.22.1", + "bytes", + "cfg-if", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.16.1", + "hashlink 0.11.1", + "indexmap", + "log", + "memchr", + "percent-encoding", + "rustls", + "smallvec", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-mysql" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "bytes", + "digest", + "dotenvy", + "either", + "futures-core", + "futures-util", + "generic-array", + "log", + "percent-encoding", + "rand 0.10.3", + "rsa", + "sha1", + "sha2", + "sqlx-core", + "thiserror", + "time", + "tracing", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2427,16 +4255,14 @@ name = "steep-server" version = "1.3.0" dependencies = [ "anyhow", - "apalis", - "apalis-redis", "axum", "devcontainer-rs", "dotenvy", "futures-util", "hex", + "jiff", "metrics", "metrics-exporter-prometheus", - "redis", "reqwest", "ring", "sentry", @@ -2445,6 +4271,8 @@ dependencies = [ "serde_json", "tempfile", "thiserror", + "toasty", + "toasty-cli", "tokio", "tokio-util", "tower", @@ -2453,6 +4281,57 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.119" @@ -2495,6 +4374,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.46" @@ -2513,9 +4398,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2585,9 +4470,212 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "toasty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c0ad66cbf23d9aeff0d85404e3799788dcea052d835997ed7e85bb3773725c" +dependencies = [ + "async-trait", + "bit-set", + "by_address", + "deadpool", + "hashbrown 0.17.1", + "index_vec", + "indexmap", + "inventory", + "jiff", + "serde", + "serde_core", + "serde_json", + "toasty-core", + "toasty-driver-mysql", + "toasty-driver-postgresql", + "toasty-driver-sqlite", + "toasty-driver-turso", + "toasty-macros", + "tokio", + "tokio-stream", + "toml", + "toml_edit", + "tracing", + "uuid", +] + +[[package]] +name = "toasty-cli" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756d16e2809ceaad4e2e78c0f65f7241160951648574435b331ec8ec50735cbd" +dependencies = [ + "anyhow", + "clap", + "console", + "dialoguer", + "hashbrown 0.17.1", + "jiff", + "rand 0.10.3", + "serde", + "toasty", + "toasty-core", + "toml", +] + +[[package]] +name = "toasty-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ce722358481c0ad55b7b5edaaf9d0669ee5de6e9c737a73ca4c27621003732" +dependencies = [ + "async-trait", + "bit-set", + "fluent-uri", + "form_urlencoded", + "hashbrown 0.17.1", + "heck", + "indexmap", + "jiff", + "percent-encoding", + "serde", + "tokio-stream", + "toml", + "tracing", + "uuid", +] + +[[package]] +name = "toasty-driver-mysql" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa5622479dff74a1db2178c8475e014f32ab434ffb06c8cb69142b0abebd876" +dependencies = [ + "async-trait", + "jiff", + "sqlx-core", + "sqlx-mysql", + "time", + "toasty-core", + "toasty-sql", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "toasty-driver-postgresql" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cdb083715b23faddc3b667151cfda8b02feac02e0066acd933d8f8028fce4c" +dependencies = [ + "async-trait", + "fallible-iterator 0.2.0", + "hashbrown 0.17.1", + "jiff", + "lru", + "postgres-protocol", + "postgres-types", + "rustls", + "rustls-pemfile", + "rustls-platform-verifier", + "rustls-webpki", + "toasty-core", + "toasty-sql", + "tokio", + "tokio-postgres", + "tokio-postgres-rustls", + "tracing", + "uuid", +] + +[[package]] +name = "toasty-driver-sqlite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffaebb64e79526666cb2628a7de5979da5ef282bb3f36a7e3b6a5acc55936794" +dependencies = [ + "async-trait", + "rusqlite", + "serde", + "toasty-core", + "toasty-sql", + "tracing", + "uuid", +] + +[[package]] +name = "toasty-driver-turso" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f3d4680ddf5e6ec2ba584ec91763420b572d0d818e10ca105a85d312d6f10a" +dependencies = [ + "async-trait", + "serde", + "toasty-core", + "toasty-sql", + "tokio", + "tracing", + "turso", + "uuid", +] + +[[package]] +name = "toasty-macros" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5b5a87266b1cccdd184244733b26fdac9ed3997446c5aca88c2c71f5b03f61" +dependencies = [ + "heck", + "indexmap", + "pluralizer", + "proc-macro2", + "quote", + "syn 3.0.4", + "toasty-core", +] + +[[package]] +name = "toasty-sql" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b50cee2799bc54257ba202a9497f07b59df7e446b6df27fbf801cffe4b1b8a" +dependencies = [ + "jiff", + "serde", + "serde_json", + "toasty-core", +] + [[package]] name = "tokio" version = "1.53.1" @@ -2626,6 +4714,56 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-postgres" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.10.3", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-postgres-rustls" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2ad44aa0ae96db89c4742212ed41645b2f597311ff6e1945542a4d9fadc2fb" +dependencies = [ + "rustls", + "sha2", + "tokio", + "tokio-postgres", + "tokio-rustls", + "x509-cert", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.19" @@ -2651,6 +4789,60 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tower" version = "0.5.3" @@ -2662,7 +4854,6 @@ dependencies = [ "pin-project-lite", "sync_wrapper", "tokio", - "tokio-util", "tower-layer", "tower-service", "tracing", @@ -2674,7 +4865,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "futures-util", "http 1.5.0", @@ -2692,7 +4883,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "http 1.5.0", "http-body", @@ -2727,6 +4918,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -2748,15 +4952,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -2793,15 +4988,211 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "ulid" -version = "1.2.1" +name = "turso" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +checksum = "f9491d7a80312c5abe66a4409e4dce02065503a235453c94b9e4133877e39ffc" dependencies = [ - "rand", - "web-time", + "thiserror", + "tracing", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sync_sdk_kit", ] +[[package]] +name = "turso_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a833cc3bf8d4e6c101c504fa470f8ab4270c2202ff2591b61b2e373b4f20d9b" +dependencies = [ + "aegis", + "aes", + "aes-gcm", + "allocator-api2 0.4.0", + "antithesis_sdk", + "arc-swap", + "aristo", + "bigdecimal", + "bitflags 2.13.1", + "branches", + "bumpalo", + "bytemuck", + "cfg_aliases", + "cfg_block", + "chrono", + "crc32c", + "crossbeam-epoch", + "crossbeam-utils", + "either", + "fallible-iterator 0.3.0", + "fastbloom", + "hex", + "icu_collator", + "icu_locale", + "intrusive-collections", + "io-uring", + "libc", + "libloading", + "libm", + "loom", + "miette", + "num-bigint", + "num-traits", + "pack1", + "parking_lot", + "pastey", + "polling", + "rand 0.9.5", + "rapidhash", + "regex", + "regex-syntax", + "roaring", + "rustc-hash 2.1.3", + "rustix 1.1.4", + "ryu", + "serde_json", + "shuttle", + "simsimd", + "smallvec", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "tracing", + "tracing-subscriber", + "turso_ext", + "turso_macros", + "turso_parser", + "twox-hash", + "uncased", + "uuid", + "windows-sys 0.61.2", +] + +[[package]] +name = "turso_ext" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7452fe676450b6840e9eb80e512d14293265221154eba66aba3845dfc0d659f" +dependencies = [ + "chrono", + "getrandom 0.4.3", + "turso_macros", +] + +[[package]] +name = "turso_macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2825eaa6b7b893693636947f988e252c1196393fa54d4b85637c70d616d92fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "turso_parser" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e58d029f02e442df4be0b5036ce520b49c553505d9d52ef5a98fc4056e95b7" +dependencies = [ + "bitflags 2.13.1", + "memchr", + "miette", + "strum", + "strum_macros", + "thiserror", + "turso_macros", +] + +[[package]] +name = "turso_sdk_kit" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c1dc1c0304348c39b97bc6b27cdcb1d7292454ebd0de0f30b5ee3a4c61f9bb" +dependencies = [ + "bindgen", + "env_logger", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_ext", + "turso_sdk_kit_macros", +] + +[[package]] +name = "turso_sdk_kit_macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4708b5fe678b8730c85964e3d378f75e18c23c4409971360b2209f4e53e4956" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "turso_sync_engine" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8f03e430240d590d209ac874db52773b7382c627cd604a685affde77622b09" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc32c", + "genawaiter", + "http 1.5.0", + "libc", + "prost", + "roaring", + "serde", + "serde_json", + "thiserror", + "tracing", + "turso_core", + "turso_parser", + "uuid", +] + +[[package]] +name = "turso_sync_sdk_kit" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cb47d056c3ec567745761fa6f832358ca30ef9eb0435fdcfb77c558e70089" +dependencies = [ + "bindgen", + "env_logger", + "genawaiter", + "parking_lot", + "tracing", + "tracing-appender", + "tracing-subscriber", + "turso_core", + "turso_sdk_kit", + "turso_sdk_kit_macros", + "turso_sync_engine", +] + +[[package]] +name = "twox-hash" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2" +dependencies = [ + "rand 0.10.3", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "uname" version = "0.1.1" @@ -2811,24 +5202,76 @@ dependencies = [ "libc", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2842,7 +5285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ "base64 0.23.1", - "der", + "der 0.8.2", "log", "native-tls", "percent-encoding", @@ -2877,6 +5320,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-zero" version = "0.8.1" @@ -2889,14 +5338,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ + "getrandom 0.4.3", "js-sys", + "rand 0.10.3", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -2912,6 +5370,22 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2927,6 +5401,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" version = "1.0.4+wasi-0.2.12" @@ -2936,6 +5419,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] + [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -3014,16 +5506,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-root-certs" version = "1.0.9" @@ -3033,6 +5515,40 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "whoami" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3049,6 +5565,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3196,18 +5721,54 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid 0.9.6", + "der 0.7.10", + "spki 0.7.3", + "tls_codec", +] + [[package]] name = "xattr" version = "1.6.1" @@ -3215,7 +5776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix", + "rustix 1.1.4", ] [[package]] @@ -3287,6 +5848,20 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] name = "zerotrie" @@ -3297,6 +5872,7 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] @@ -3305,6 +5881,7 @@ version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", diff --git a/Cargo.toml b/Cargo.toml index ee4b5e9..2302f99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,9 +28,9 @@ hex = "0.4" bytes = "1.1" metrics = "0.24" metrics-exporter-prometheus = { version = "0.18", default-features = false, features = ["http-listener"] } -apalis = { version = "0.7", features = ["catch-panic", "limit"] } -apalis-redis = "0.7" -redis = { version = "0.32", features = ["tokio-comp", "connection-manager"] } +toasty = { version = "0.11", features = ["postgresql", "serde", "jiff"] } +toasty-cli = "0.11" +jiff = "0.2" [profile.release] debug = 1 diff --git a/Containerfile b/Containerfile index ccb90f5..c2a086e 100644 --- a/Containerfile +++ b/Containerfile @@ -10,10 +10,7 @@ RUN cargo build --release --package steep-server FROM debian:trixie-slim -# git clones the pull request. ca-certificates is what every HTTPS call needs -# (Gitea, OpenRouter, git). The shared libraries are the non-base ones the binary -# links against, as reported by `ldd target/release/steep-server`; libc, libm -# and libgcc_s come from the base image. + RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ git \ @@ -26,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /app COPY --from=builder /app/target/release/steep-server ./steep-server -# Exec form, so the binary is PID 1 and receives the SIGTERM it handles to shut -# down gracefully. -CMD ["./steep-server"] +COPY crates/steep-server/toasty ./toasty +ENV MIGRATIONS_DIR=/app/toasty + +CMD ["sh", "-c", "./steep-server migration apply && exec ./steep-server"] diff --git a/README.md b/README.md index 7a35eaf..62f20b1 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Steep reads its configuration from environment variables (a `.env` file is suppo | `GITEA_URL` | Base URL of your Gitea instance | | `GITEA_TOKEN` | Gitea API token | | `GITEA_TIMEOUT` | Gitea API request timeout in seconds | -| `REDIS_URL` | Redis connection URL used for the durable review queue (e.g. `redis://127.0.0.1:6379`) | +| `DATABASE_URL` | PostgreSQL connection URL used for the durable review queue (e.g. `postgresql://user:pass@localhost:5432/steep`) | | `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. | | `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking | | `RUST_LOG` | *(optional)* Log level, defaults to `info` | @@ -102,6 +102,26 @@ hooks have run, the container is disconnected from the network for the rest of the review. Every container command is bounded by a timeout, and the container, network and image are removed when the review ends (including on failure). +## Migrations + +The database schema is versioned with [Toasty](https://github.com/tokio-rs/toasty) migrations, stored in `crates/steep-server/toasty/` (commit them). + +After changing a model in `crates/steep-server/src/schemas/`, generate a migration: + +```sh +cargo run -p steep-server -- migration generate --name describe_change +``` + +Apply the pending migrations **before starting the service** — the server does not migrate at boot: + +```sh +cargo run -p steep-server -- migration apply +``` + +The `Containerfile` does this at startup: its entrypoint runs `steep-server migration apply` before exec'ing the server. The CLI reads the migration files from `MIGRATIONS_DIR` (defaults to the crate's `toasty/` directory; the image sets it to `/app/toasty`). + +Other subcommands: `migration snapshot`, `migration drop`, `migration reset`. They read `DATABASE_URL`. + ## Development The easiest way to get started is with the provided [Dev Container](https://containers.dev/) (VS Code or Zed with the dev container extension). @@ -129,7 +149,7 @@ Set `METRICS_BIND_ADDR` (e.g. `0.0.0.0:9100`) to enable it. The metrics are then |---|---|---| | `steep_webhooks_received_total` | counter | Total webhooks received (label: `event_type`) | | `steep_webhooks_duplicate_total` | counter | Webhooks rejected as duplicates (label: `event_type`) | -| `steep_webhooks_enqueue_failed_total` | counter | Webhooks dropped because enqueueing to Redis failed (label: `event_type`) | +| `steep_webhooks_enqueue_failed_total` | counter | Webhooks dropped because enqueueing to PostgreSQL failed (label: `event_type`) | | `steep_bot_tasks_active` | gauge | Bot tasks currently in progress | | `steep_bot_tasks_skipped_total` | counter | Reviews skipped because they were already done (label: `event_type`) | | `steep_bot_tasks_completed_total` | counter | Bot tasks completed successfully (label: `event_type`) | diff --git a/crates/steep-server/Cargo.toml b/crates/steep-server/Cargo.toml index b6a466a..bd12c40 100644 --- a/crates/steep-server/Cargo.toml +++ b/crates/steep-server/Cargo.toml @@ -26,6 +26,6 @@ metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } devcontainer-rs = { path = "../devcontainer-rs" } tempfile = "3" -apalis = { workspace = true } -apalis-redis = { workspace = true } -redis = { workspace = true } +toasty = { workspace = true } +toasty-cli = { workspace = true } +jiff = { workspace = true } diff --git a/crates/steep-server/src/api.rs b/crates/steep-server/src/api.rs index 844c4ba..4896016 100644 --- a/crates/steep-server/src/api.rs +++ b/crates/steep-server/src/api.rs @@ -14,10 +14,7 @@ use tracing::{info, instrument}; use tokio_util::sync::CancellationToken; -use apalis::prelude::Storage; - use crate::consts::{GITEA_EVENT_TYPE_HEADER_NAME, GITEA_SIG_HEADER_NAME, MAX_WEBHOOK_BODY_SIZE}; -use crate::dedup::{Dedup, WEBHOOK_TTL}; use crate::errors::AppError; use crate::gitea::WebhookType; use crate::metrics; @@ -64,20 +61,19 @@ async fn webhook( metrics::webhook_received(event_type); let event_id = wb.event_id(); - let webhook_key = Dedup::webhook_key(event_id); - if !app_state - .dedup - .claim(&webhook_key, WEBHOOK_TTL) - .await - .map_err(anyhow::Error::from)? - { - metrics::webhook_duplicate(event_type); - return Err(AppError::AlreadyProcessedErr); + match app_state.dedup.is_known(event_id).await { + Ok(true) => { + metrics::webhook_duplicate(event_type); + return Err(AppError::AlreadyProcessedErr); + } + Ok(false) => {} + Err(err) => { + metrics::webhook_enqueue_failed(event_type); + return Err(AppError::EnqueueErr(anyhow::Error::from(err))); + } } - let mut storage = app_state.storage.clone(); - if let Err(err) = storage.push(wb).await { - app_state.dedup.release(&webhook_key).await.ok(); + if let Err(err) = app_state.queue.push(wb).await { metrics::webhook_enqueue_failed(event_type); return Err(AppError::EnqueueErr(anyhow::Error::from(err))); } diff --git a/crates/steep-server/src/bot.rs b/crates/steep-server/src/bot.rs index fdb8c1c..dadc2f2 100644 --- a/crates/steep-server/src/bot.rs +++ b/crates/steep-server/src/bot.rs @@ -1,15 +1,21 @@ +use std::sync::Arc; + +use futures_util::StreamExt; +use serde::{Deserialize, Deserializer}; +use tokio::sync::Semaphore; +use tokio_util::sync::CancellationToken; +use tracing::{error, info, instrument}; + use crate::{ - dedup::{Dedup, REVIEW_TTL}, + bot_actions::review::ReviewOutcome, + dedup::{Dedup, ReviewRecord}, gitea::{GiteaAPI, WebhookType}, + job_queue::JobQueue, metrics, open_router::OpenRouterClient, sandbox::SandboxConfig, + schemas::Job, }; -use apalis::prelude::{Data, Error, Monitor, WorkerBuilder, WorkerBuilderExt, WorkerFactoryFn}; -use apalis_redis::RedisStorage; -use serde::{Deserialize, Deserializer}; -use tokio_util::sync::CancellationToken; -use tracing::{error, info, instrument}; #[derive(Deserialize, Debug)] pub struct ReviewResult { @@ -127,6 +133,7 @@ pub struct Bot { open_router_model: String, sandbox: SandboxConfig, dedup: Dedup, + queue: JobQueue, } impl Bot { @@ -137,6 +144,7 @@ impl Bot { open_router_model: String, sandbox: SandboxConfig, dedup: Dedup, + queue: JobQueue, ) -> Self { Self { gitea_api, @@ -145,40 +153,119 @@ impl Bot { open_router_model, sandbox, dedup, + queue, } } - /// Consomme la file Redis jusqu'à l'annulation de `shutdown`. + /// Consomme la file jusqu'à l'annulation de `shutdown`. /// - /// Les jobs en cours sont laissés se terminer, et ceux qui n'ont pas encore - /// été acquittés restent dans Redis : ils seront repris au prochain démarrage. - pub async fn start( - &self, - storage: RedisStorage, - shutdown: CancellationToken, - ) -> anyhow::Result<()> { + /// Un sémaphore borne la concurrence ; les jobs en cours sont laissés se + /// terminer. Les jobs non terminés restent en base et seront repris au + /// prochain démarrage. + pub async fn start(&self, shutdown: CancellationToken) -> anyhow::Result<()> { info!("Bot started"); - let worker = WorkerBuilder::new("steep-review") - .concurrency(self.max_concurrent) - .data(self.clone()) - .backend(storage) - .build_fn(handle_review); + let semaphore = Arc::new(Semaphore::new(self.max_concurrent)); + let mut tasks = tokio::task::JoinSet::new(); + let mut pending = std::pin::pin!(self.queue.pending()); - Monitor::new() - .register(worker) - .run_with_signal(async move { - shutdown.cancelled().await; - Ok(()) - }) - .await?; + loop { + let job = tokio::select! { + biased; + _ = shutdown.cancelled() => break, + job = pending.next() => match job { + Some(job) => job, + None => break, + }, + }; + + while let Some(res) = tasks.try_join_next() { + if let Err(err) = res { + error!("Task panicked: {err}"); + } + } + + let permit = semaphore.clone().acquire_owned().await?; + + if let Err(err) = self.queue.mark_processing(job.id).await { + error!(%err, "failed to mark job as processing"); + } + + let bot = self.clone(); + metrics::increment_task_active(); + + tasks.spawn(async move { + bot.process(job).await; + drop(permit); + metrics::decrement_task_active(); + }); + } + + tasks.join_all().await; info!("Bot shutting down complete"); Ok(()) } + async fn process(&self, job: Job) { + let event_type = job.payload.event_type_str(); + + match self.dedup.is_reviewed(job.event_id).await { + Ok(true) => { + metrics::task_skipped(event_type); + info!("Review already done, skipping"); + + if let Err(err) = self.queue.remove(job.id).await { + error!(%err, "failed to remove job"); + } + return; + } + Ok(false) => {} + Err(err) => error!(%err, "Dedup lookup failed, reviewing anyway"), + } + + let payload = job.payload.0.clone(); + let started = std::time::Instant::now(); + let result = self.exec(payload).await; + let duration_ms = started.elapsed().as_millis() as u64; + + let (ok, outcome) = match result { + Ok(outcome) => { + metrics::task_completed(event_type); + info!("Task completed"); + (true, outcome) + } + Err(err) => { + metrics::task_failed(event_type); + error!(%err, "Task error"); + sentry_anyhow::capture_anyhow(&err); + (false, ReviewOutcome::empty()) + } + }; + + let record = ReviewRecord { + event_id: job.event_id, + repo: job.repo.clone(), + pr: job.pr, + ok, + model: self.open_router_model.clone(), + markdown: outcome.markdown, + cost: outcome.cost, + comments: outcome.comments as u64, + duration_ms, + }; + + if let Err(err) = self.dedup.mark_reviewed(record).await { + error!(%err, "failed to record review"); + } + + if let Err(err) = self.queue.remove(job.id).await { + error!(%err, "failed to remove job"); + } + } + #[instrument(skip(self, webhook), fields(repo, pr))] - pub async fn exec(&self, webhook: WebhookType) -> anyhow::Result<()> { + pub async fn exec(&self, webhook: WebhookType) -> anyhow::Result { match &webhook { WebhookType::Review(p) => { tracing::Span::current().record("repo", &p.repository.full_name); @@ -203,40 +290,3 @@ impl Bot { } } } - -async fn handle_review(webhook: WebhookType, bot: Data) -> Result<(), Error> { - let event_type = webhook.event_type_str(); - let review_key = Dedup::review_key(webhook.event_id()); - - match bot.dedup.is_marked(&review_key).await { - Ok(true) => { - metrics::task_skipped(event_type); - info!("Review already done, skipping"); - return Ok(()); - } - Ok(false) => {} - Err(err) => error!(%err, "Dedup lookup failed, reviewing anyway"), - } - - metrics::increment_task_active(); - let result = bot.exec(webhook).await; - metrics::decrement_task_active(); - - match result { - Ok(()) => { - metrics::task_completed(event_type); - info!("Task completed"); - - if let Err(err) = bot.dedup.mark(&review_key, REVIEW_TTL).await { - error!(%err, "Failed to mark review as done"); - } - } - Err(err) => { - metrics::task_failed(event_type); - error!(%err, "Task error"); - sentry_anyhow::capture_anyhow(&err); - } - } - - Ok(()) -} diff --git a/crates/steep-server/src/bot_actions/review.rs b/crates/steep-server/src/bot_actions/review.rs index e0ac54f..e37cf08 100644 --- a/crates/steep-server/src/bot_actions/review.rs +++ b/crates/steep-server/src/bot_actions/review.rs @@ -12,6 +12,22 @@ use crate::{ text::excerpt, }; +pub struct ReviewOutcome { + pub markdown: String, + pub cost: Option, + pub comments: usize, +} + +impl ReviewOutcome { + pub fn empty() -> Self { + Self { + markdown: String::new(), + cost: None, + comments: 0, + } + } +} + #[instrument(skip(gitea_api, open_router_client, sandbox_config, tools, review_payload))] pub async fn exec_review( gitea_api: &GiteaAPI, @@ -20,7 +36,7 @@ pub async fn exec_review( sandbox_config: &SandboxConfig, tools: Vec, review_payload: ReviewPayload, -) -> anyhow::Result<()> { +) -> anyhow::Result { tracing::info!( repo = %review_payload.repository.full_name, pr = review_payload.pull_request.number, @@ -36,7 +52,7 @@ pub async fn exec_review( ) .await?; - let bot_result: Result = async { + let bot_result: Result<(ReviewResult, String), anyhow::Error> = async { let full_name = &review_payload.repository.full_name; let index = review_payload.pull_request.number; @@ -91,15 +107,21 @@ pub async fn exec_review( ) .await?; - Ok(review_result) + Ok((review_result, final_review_markdown)) } .await; match bot_result { - Ok(_) => { + Ok((review_result, markdown)) => { gitea_api .delete_comment(&review_payload.repository.full_name, new_comment.id) - .await + .await?; + + Ok(ReviewOutcome { + markdown, + cost: review_result.cost, + comments: review_result.reviews.len(), + }) } Err(e) => { gitea_api @@ -108,17 +130,13 @@ pub async fn exec_review( &review_payload.repository.full_name, new_comment.id, ) - .await + .await?; + + Err(e) } } } -/// Runs the review inside a sandbox container, letting the model explore the -/// repository with tools before answering. -/// -/// The answer of the model is parsed by [`ReviewResult::from_str`], which the -/// agent loop enforces: an answer that is not a review is sent back to the model -/// for correction. async fn run_sandboxed_review( gitea_api: &GiteaAPI, open_router_client: &OpenRouterClient, diff --git a/crates/steep-server/src/db.rs b/crates/steep-server/src/db.rs new file mode 100644 index 0000000..2eb62e6 --- /dev/null +++ b/crates/steep-server/src/db.rs @@ -0,0 +1,43 @@ +use toasty::Db; + +use crate::schemas::{Job, JobStatus}; + +pub async fn open(url: &str) -> toasty::Result { + Db::builder() + .models(toasty::models!(crate::*)) + .connect(url) + .await +} + +pub async fn connect(url: &str) -> toasty::Result { + let mut db = open(url).await?; + + Job::update_by_status(JobStatus::Processing) + .status(JobStatus::Pending) + .exec(&mut db) + .await?; + + Ok(db) +} + +#[cfg(test)] +pub(crate) async fn test_connect() -> Db { + static DB: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); + + DB.get_or_init(|| async { + let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + + let migrations = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("toasty"); + let config = toasty_cli::Config::new() + .migration(toasty_cli::MigrationConfig::new().path(migrations)); + + toasty_cli::ToastyCli::with_config(open(&url).await.expect("open pool"), config) + .parse_from(["toasty", "migration", "apply"]) + .await + .expect("apply migrations"); + + connect(&url).await.expect("connect to postgres") + }) + .await + .clone() +} diff --git a/crates/steep-server/src/dedup.rs b/crates/steep-server/src/dedup.rs index b414859..b3d83af 100644 --- a/crates/steep-server/src/dedup.rs +++ b/crates/steep-server/src/dedup.rs @@ -1,66 +1,71 @@ -use std::time::Duration; +use toasty::Db; -use redis::aio::ConnectionManager; +use crate::schemas::{Job, Review}; -pub const WEBHOOK_TTL: Duration = Duration::from_secs(24 * 60 * 60); -pub const REVIEW_TTL: Duration = Duration::from_secs(7 * 24 * 60 * 60); +pub struct ReviewRecord { + pub event_id: u64, + pub repo: String, + pub pr: u64, + pub ok: bool, + pub model: String, + pub markdown: String, + pub cost: Option, + pub comments: u64, + pub duration_ms: u64, +} #[derive(Clone)] pub struct Dedup { - conn: ConnectionManager, + db: Db, } impl Dedup { - pub fn new(conn: ConnectionManager) -> Self { - Self { conn } + pub fn new(db: Db) -> Self { + Self { db } } - pub fn webhook_key(event_id: u64) -> String { - format!("steep:webhook:{event_id}") + pub async fn is_known(&self, event_id: u64) -> toasty::Result { + let mut db = self.db.clone(); + + if Job::filter_by_event_id(event_id) + .first() + .exec(&mut db) + .await? + .is_some() + { + return Ok(true); + } + + Ok(Review::filter_by_event_id(event_id) + .first() + .exec(&mut db) + .await? + .is_some()) } - pub fn review_key(event_id: u64) -> String { - format!("steep:review:{event_id}") + pub async fn is_reviewed(&self, event_id: u64) -> toasty::Result { + let mut db = self.db.clone(); + + Ok(Review::filter_by_event_id(event_id) + .first() + .exec(&mut db) + .await? + .is_some()) } - pub async fn claim(&self, key: &str, ttl: Duration) -> redis::RedisResult { - let set: Option = redis::cmd("SET") - .arg(key) - .arg(1) - .arg("NX") - .arg("EX") - .arg(ttl.as_secs()) - .query_async(&mut self.conn.clone()) - .await?; + pub async fn mark_reviewed(&self, record: ReviewRecord) -> toasty::Result<()> { + let mut db = self.db.clone(); - Ok(set.is_some()) - } - - pub async fn mark(&self, key: &str, ttl: Duration) -> redis::RedisResult<()> { - let _: String = redis::cmd("SET") - .arg(key) - .arg(1) - .arg("EX") - .arg(ttl.as_secs()) - .query_async(&mut self.conn.clone()) - .await?; - - Ok(()) - } - - pub async fn is_marked(&self, key: &str) -> redis::RedisResult { - let exists: i64 = redis::cmd("EXISTS") - .arg(key) - .query_async(&mut self.conn.clone()) - .await?; - - Ok(exists > 0) - } - - pub async fn release(&self, key: &str) -> redis::RedisResult<()> { - let _: i64 = redis::cmd("DEL") - .arg(key) - .query_async(&mut self.conn.clone()) + Review::upsert_by_event_id(record.event_id) + .repo(record.repo) + .pr(record.pr) + .ok(record.ok) + .model(record.model) + .markdown(record.markdown) + .cost(record.cost) + .comments(record.comments) + .duration_ms(record.duration_ms) + .exec(&mut db) .await?; Ok(()) @@ -70,51 +75,42 @@ impl Dedup { #[cfg(test)] mod tests { use super::*; - - #[test] - fn webhook_key_is_namespaced() { - assert_eq!(Dedup::webhook_key(42), "steep:webhook:42"); - } - - #[test] - fn review_key_is_namespaced() { - assert_eq!(Dedup::review_key(42), "steep:review:42"); - } - - #[test] - fn the_two_keys_for_the_same_event_differ() { - assert_ne!(Dedup::webhook_key(7), Dedup::review_key(7)); - } - - async fn redis_dedup() -> Dedup { - let url = std::env::var("REDIS_URL").expect("REDIS_URL must be set"); - Dedup::new(apalis_redis::connect(url).await.expect("connect to redis")) - } + use crate::schemas::Review; #[tokio::test] - #[ignore = "requires a running Redis (REDIS_URL)"] - async fn claim_then_claim_is_a_duplicate() { - let dedup = redis_dedup().await; - let key = "steep:test:claim"; - dedup.release(key).await.unwrap(); + #[ignore = "requires DATABASE_URL"] + async fn a_recorded_review_is_known_and_reviewed() { + let db = crate::db::test_connect().await; + let dedup = Dedup::new(db.clone()); - assert!(dedup.claim(key, Duration::from_secs(30)).await.unwrap()); - assert!(!dedup.claim(key, Duration::from_secs(30)).await.unwrap()); + let event_id = 999_000_101; + Review::delete_by_event_id(&mut db.clone(), event_id) + .await + .ok(); - dedup.release(key).await.unwrap(); - } + assert!(!dedup.is_known(event_id).await.unwrap()); + assert!(!dedup.is_reviewed(event_id).await.unwrap()); - #[tokio::test] - #[ignore = "requires a running Redis (REDIS_URL)"] - async fn mark_then_is_marked() { - let dedup = redis_dedup().await; - let key = "steep:test:mark"; - dedup.release(key).await.unwrap(); + dedup + .mark_reviewed(ReviewRecord { + event_id, + repo: "owner/repo".to_string(), + pr: 1, + ok: true, + model: "test/model".to_string(), + markdown: "looks good".to_string(), + cost: Some(0.01), + comments: 2, + duration_ms: 5, + }) + .await + .unwrap(); - assert!(!dedup.is_marked(key).await.unwrap()); - dedup.mark(key, Duration::from_secs(30)).await.unwrap(); - assert!(dedup.is_marked(key).await.unwrap()); + assert!(dedup.is_reviewed(event_id).await.unwrap()); + assert!(dedup.is_known(event_id).await.unwrap()); - dedup.release(key).await.unwrap(); + Review::delete_by_event_id(&mut db.clone(), event_id) + .await + .unwrap(); } } diff --git a/crates/steep-server/src/env.rs b/crates/steep-server/src/env.rs index 771c490..adf1973 100644 --- a/crates/steep-server/src/env.rs +++ b/crates/steep-server/src/env.rs @@ -11,7 +11,7 @@ pub struct EnvConfig { pub gitea_url: String, pub gitea_token: String, pub gitea_timeout: u64, - pub redis_url: String, + pub database_url: String, pub metrics_bind_addr: Option, pub sandbox_max_iterations: usize, } @@ -26,7 +26,7 @@ pub fn load_config() -> anyhow::Result { let gitea_url = try_get_env("GITEA_URL")?; let gitea_token = try_get_env("GITEA_TOKEN")?; let gitea_timeout = try_get_env("GITEA_TIMEOUT")?.parse()?; - let redis_url = try_get_env("REDIS_URL")?; + let database_url = try_get_env("DATABASE_URL")?; let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok(); let sandbox_max_iterations = std::env::var("SANDBOX_MAX_ITERATIONS") .ok() @@ -43,7 +43,7 @@ pub fn load_config() -> anyhow::Result { gitea_url, gitea_token, gitea_timeout, - redis_url, + database_url, metrics_bind_addr, sandbox_max_iterations, }) diff --git a/crates/steep-server/src/gitea.rs b/crates/steep-server/src/gitea.rs index e3e3112..d1616f3 100644 --- a/crates/steep-server/src/gitea.rs +++ b/crates/steep-server/src/gitea.rs @@ -281,7 +281,7 @@ async fn read_capped(response: reqwest::Response) -> anyhow::Result { Ok(String::from_utf8_lossy(&buf).into_owned()) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum WebhookType { Review(ReviewPayload), } @@ -298,9 +298,21 @@ impl WebhookType { WebhookType::Review(_) => "review", } } + + pub fn repository_full_name(&self) -> &str { + match self { + WebhookType::Review(payload) => &payload.repository.full_name, + } + } + + pub fn pull_request_number(&self) -> u64 { + match self { + WebhookType::Review(payload) => payload.pull_request.number, + } + } } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct ReviewPayload { pub action: String, pub pull_request: PullRequest, @@ -308,13 +320,13 @@ pub struct ReviewPayload { pub comment: Comment, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct PullRequest { pub number: u64, pub title: String, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct Comment { pub id: u64, pub body: String, @@ -325,7 +337,7 @@ pub struct User { pub login: String, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct Repository { pub full_name: String, pub clone_url: String, diff --git a/crates/steep-server/src/job_queue.rs b/crates/steep-server/src/job_queue.rs new file mode 100644 index 0000000..d09649d --- /dev/null +++ b/crates/steep-server/src/job_queue.rs @@ -0,0 +1,196 @@ +use std::{collections::VecDeque, time::Duration}; + +use futures_util::stream::{self, Stream}; +use toasty::Db; +use tracing::error; + +use crate::{ + gitea::WebhookType, + schemas::{Job, JobStatus}, +}; + +/// Délai entre deux interrogations de la table quand la file est vide. +const POLL_INTERVAL: Duration = Duration::from_secs(1); + +/// File des reviews à faire. +#[derive(Clone)] +pub struct JobQueue { + db: Db, +} + +impl JobQueue { + pub fn new(db: Db) -> Self { + Self { db } + } + + /// Enfile une nouvelle review. + /// + /// L'unicité de `event_id` fait échouer l'insertion d'un doublon. + pub async fn push(&self, webhook: WebhookType) -> toasty::Result<()> { + let event_id = webhook.event_id(); + let repo = webhook.repository_full_name().to_string(); + let pr = webhook.pull_request_number(); + + let mut db = self.db.clone(); + + toasty::create!(Job { + event_id, + repo, + pr, + payload: webhook, + status: JobStatus::Pending, + }) + .exec(&mut db) + .await?; + + Ok(()) + } + + /// Flux des jobs en attente. + /// + /// C'est un `Stream` (et non un `Iterator`) : l'accès à la base est + /// asynchrone. Le flux interroge la table en boucle et met en tampon les + /// jobs trouvés. + pub fn pending(&self) -> impl Stream { + let db = self.db.clone(); + + stream::unfold(VecDeque::new(), move |mut buffer| { + let db = db.clone(); + + async move { + let mut db = db; + + loop { + if let Some(job) = buffer.pop_front() { + return Some((job, buffer)); + } + + match Job::filter_by_status(JobStatus::Pending) + .exec(&mut db) + .await + { + Ok(jobs) if jobs.is_empty() => { + tokio::time::sleep(POLL_INTERVAL).await; + } + Ok(jobs) => buffer.extend(jobs), + Err(err) => { + error!(%err, "failed to poll pending jobs"); + tokio::time::sleep(POLL_INTERVAL).await; + } + } + } + } + }) + } + + /// Marque un job comme en cours de traitement. + pub async fn mark_processing(&self, id: u64) -> toasty::Result<()> { + let mut db = self.db.clone(); + + Job::update_by_id(id) + .status(JobStatus::Processing) + .exec(&mut db) + .await?; + + Ok(()) + } + + /// Retire un job de la file, une fois la review enregistrée. + pub async fn remove(&self, id: u64) -> toasty::Result<()> { + let mut db = self.db.clone(); + + Job::delete_by_id(&mut db, id).await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use futures_util::StreamExt; + + use super::*; + use crate::gitea::{Comment, PullRequest, Repository, ReviewPayload}; + + fn webhook(event_id: u64) -> WebhookType { + WebhookType::Review(ReviewPayload { + action: "created".to_string(), + pull_request: PullRequest { + number: 1, + title: "Test".to_string(), + }, + repository: Repository { + full_name: "owner/repo".to_string(), + clone_url: "https://example.com/owner/repo.git".to_string(), + }, + comment: Comment { + id: event_id, + body: "@bot review".to_string(), + }, + }) + } + + async fn next_job(queue: &JobQueue, event_id: u64) -> Job { + let mut pending = std::pin::pin!(queue.pending()); + + tokio::time::timeout(Duration::from_secs(10), async { + loop { + if let Some(job) = pending.next().await + && job.event_id == event_id + { + break job; + } + } + }) + .await + .expect("timed out waiting for the job") + } + + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn a_pushed_job_is_pending_then_removed() { + let db = crate::db::test_connect().await; + let queue = JobQueue::new(db.clone()); + + let event_id = 999_000_201; + Job::delete_by_event_id(&mut db.clone(), event_id) + .await + .ok(); + + queue.push(webhook(event_id)).await.unwrap(); + + let job = next_job(&queue, event_id).await; + queue.mark_processing(job.id).await.unwrap(); + queue.remove(job.id).await.unwrap(); + + assert!( + Job::filter_by_event_id(event_id) + .first() + .exec(&mut db.clone()) + .await + .unwrap() + .is_none() + ); + } + + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn a_duplicate_event_id_is_rejected() { + let db = crate::db::test_connect().await; + let queue = JobQueue::new(db.clone()); + + let event_id = 999_000_202; + Job::delete_by_event_id(&mut db.clone(), event_id) + .await + .ok(); + + queue.push(webhook(event_id)).await.unwrap(); + assert!(queue.push(webhook(event_id)).await.is_err()); + + Job::delete_by_event_id(&mut db.clone(), event_id) + .await + .unwrap(); + } +} diff --git a/crates/steep-server/src/main.rs b/crates/steep-server/src/main.rs index 3558457..6d4231c 100644 --- a/crates/steep-server/src/main.rs +++ b/crates/steep-server/src/main.rs @@ -1,12 +1,8 @@ use std::time::Duration; use crate::{ - bot::Bot, - dedup::Dedup, - gitea::{GiteaAPI, WebhookType}, - open_router::OpenRouterClient, - sandbox::SandboxConfig, - state::AppState, + bot::Bot, dedup::Dedup, gitea::GiteaAPI, job_queue::JobQueue, open_router::OpenRouterClient, + sandbox::SandboxConfig, state::AppState, }; use dotenvy::dotenv; @@ -19,13 +15,16 @@ mod api; mod bot; mod bot_actions; mod consts; +mod db; mod dedup; mod env; mod errors; mod gitea; +mod job_queue; mod metrics; mod open_router; mod sandbox; +mod schemas; mod state; mod text; @@ -38,6 +37,11 @@ const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(60); fn main() -> anyhow::Result<()> { dotenv().ok(); + // Sous-commande de migration : `steep-server migration `. + if std::env::args().nth(1).as_deref() == Some("migration") { + return tokio::runtime::Runtime::new()?.block_on(run_migrations()); + } + tracing_subscriber::registry() .with(fmt::layer()) .with( @@ -63,6 +67,24 @@ fn main() -> anyhow::Result<()> { tokio::runtime::Runtime::new()?.block_on(run()) } +async fn run_migrations() -> anyhow::Result<()> { + let database_url = env::try_get_env("DATABASE_URL")?; + let db = db::open(&database_url).await?; + + let migrations = match std::env::var("MIGRATIONS_DIR") { + Ok(dir) => std::path::PathBuf::from(dir), + Err(_) => std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("toasty"), + }; + let config = + toasty_cli::Config::new().migration(toasty_cli::MigrationConfig::new().path(migrations)); + + toasty_cli::ToastyCli::with_config(db, config) + .parse_and_run() + .await?; + + Ok(()) +} + async fn run() -> anyhow::Result<()> { let config = env::load_config()?; @@ -101,10 +123,9 @@ async fn run() -> anyhow::Result<()> { ); } - let redis_conn = apalis_redis::connect(config.redis_url.clone()).await?; - let storage: apalis_redis::RedisStorage = - apalis_redis::RedisStorage::new(redis_conn.clone()); - let dedup = Dedup::new(redis_conn); + let db = db::connect(&config.database_url).await?; + let dedup = Dedup::new(db.clone()); + let queue = JobQueue::new(db); let bot = Bot::new( gitea_api, @@ -113,12 +134,13 @@ async fn run() -> anyhow::Result<()> { config.open_router_model.clone(), sandbox, dedup.clone(), + queue.clone(), ); let app_state = AppState { - storage: storage.clone(), - bot_name: gitea_user.login.clone(), + queue, dedup, + bot_name: gitea_user.login.clone(), config, }; @@ -143,7 +165,7 @@ async fn run() -> anyhow::Result<()> { tokio::select! { result = async { tokio::try_join!( - bot.start(storage, shutdown.clone()), + bot.start(shutdown.clone()), api::start(app_state, shutdown.clone()), signal ) diff --git a/crates/steep-server/src/metrics.rs b/crates/steep-server/src/metrics.rs index ff1771c..3f41b99 100644 --- a/crates/steep-server/src/metrics.rs +++ b/crates/steep-server/src/metrics.rs @@ -54,7 +54,7 @@ pub fn describe() { describe_counter!( "steep_webhooks_enqueue_failed_total", Unit::Count, - "Webhooks dropped because enqueueing to Redis failed" + "Webhooks dropped because enqueueing to PostgreSQL failed" ); describe_gauge!( "steep_bot_tasks_active", diff --git a/crates/steep-server/src/schemas/job.rs b/crates/steep-server/src/schemas/job.rs new file mode 100644 index 0000000..d36cf4a --- /dev/null +++ b/crates/steep-server/src/schemas/job.rs @@ -0,0 +1,31 @@ +use jiff::Timestamp; + +use crate::gitea::WebhookType; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, toasty::Embed)] +pub enum JobStatus { + Pending, + Processing, +} + +#[derive(Debug, toasty::Model)] +pub struct Job { + #[key] + #[auto] + pub id: u64, + + #[unique] + pub event_id: u64, + + pub repo: String, + pub pr: u64, + + #[column(type = jsonb)] + pub payload: toasty::Json, + + #[index] + pub status: JobStatus, + + #[auto] + pub created_at: Timestamp, +} diff --git a/crates/steep-server/src/schemas/mod.rs b/crates/steep-server/src/schemas/mod.rs new file mode 100644 index 0000000..0a80355 --- /dev/null +++ b/crates/steep-server/src/schemas/mod.rs @@ -0,0 +1,5 @@ +pub mod job; +pub mod review; + +pub use job::{Job, JobStatus}; +pub use review::Review; diff --git a/crates/steep-server/src/schemas/review.rs b/crates/steep-server/src/schemas/review.rs new file mode 100644 index 0000000..0539bfa --- /dev/null +++ b/crates/steep-server/src/schemas/review.rs @@ -0,0 +1,35 @@ +use jiff::Timestamp; + +#[derive(Debug, toasty::Model)] +pub struct Review { + #[key] + #[auto] + pub id: u64, + + #[unique] + pub event_id: u64, + + pub repo: String, + pub pr: u64, + + /// Vrai si la review a abouti. + pub ok: bool, + + /// Modèle utilisé. + pub model: String, + + /// Markdown posté sur la pull request (vide en cas d'échec). + pub markdown: String, + + /// Coût OpenRouter en USD, si connu. + pub cost: Option, + + /// Nombre de commentaires produits. + pub comments: u64, + + /// Durée du traitement en millisecondes. + pub duration_ms: u64, + + #[default(jiff::Timestamp::now())] + pub completed_at: Timestamp, +} diff --git a/crates/steep-server/src/state.rs b/crates/steep-server/src/state.rs index a6857d7..c66b4e1 100644 --- a/crates/steep-server/src/state.rs +++ b/crates/steep-server/src/state.rs @@ -1,11 +1,9 @@ -use apalis_redis::RedisStorage; - -use crate::{dedup::Dedup, env::EnvConfig, gitea::WebhookType}; +use crate::{dedup::Dedup, env::EnvConfig, job_queue::JobQueue}; #[derive(Clone)] pub struct AppState { - pub storage: RedisStorage, - pub bot_name: String, + pub queue: JobQueue, pub dedup: Dedup, + pub bot_name: String, pub config: EnvConfig, } diff --git a/crates/steep-server/toasty/history.toml b/crates/steep-server/toasty/history.toml new file mode 100644 index 0000000..c82f11c --- /dev/null +++ b/crates/steep-server/toasty/history.toml @@ -0,0 +1,6 @@ +version = 1 + +[[migrations]] +id = 3569579561409902287 +name = "0000_init.sql" +snapshot_name = "0000_snapshot.toml" diff --git a/crates/steep-server/toasty/migrations/0000_init.sql b/crates/steep-server/toasty/migrations/0000_init.sql new file mode 100644 index 0000000..5fccfab --- /dev/null +++ b/crates/steep-server/toasty/migrations/0000_init.sql @@ -0,0 +1,28 @@ +CREATE TYPE "job_status" AS ENUM ('pending', 'processing'); +CREATE TABLE "jobs" ( + "id" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "event_id" BIGINT NOT NULL, + "repo" TEXT NOT NULL, + "pr" BIGINT NOT NULL, + "payload" JSONB NOT NULL, + "status" "job_status" NOT NULL, + "created_at" TIMESTAMPTZ(6) NOT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "index_jobs_by_event_id" ON "jobs" ("event_id"); +CREATE INDEX "index_jobs_by_status" ON "jobs" ("status"); +CREATE TABLE "reviews" ( + "id" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "event_id" BIGINT NOT NULL, + "repo" TEXT NOT NULL, + "pr" BIGINT NOT NULL, + "ok" BOOLEAN NOT NULL, + "model" TEXT NOT NULL, + "markdown" TEXT NOT NULL, + "cost" DOUBLE PRECISION, + "comments" BIGINT NOT NULL, + "duration_ms" BIGINT NOT NULL, + "completed_at" TIMESTAMPTZ(6) NOT NULL, + PRIMARY KEY ("id") +); +CREATE UNIQUE INDEX "index_reviews_by_event_id" ON "reviews" ("event_id"); diff --git a/crates/steep-server/toasty/snapshots/0000_snapshot.toml b/crates/steep-server/toasty/snapshots/0000_snapshot.toml new file mode 100644 index 0000000..1a0732c --- /dev/null +++ b/crates/steep-server/toasty/snapshots/0000_snapshot.toml @@ -0,0 +1,160 @@ +version = 1 + +[schema] + +[[schema.tables]] +id = 0 +name = "jobs" +primary_key = { columns = [{ table = 0, index = 0 }], index = { table = 0, index = 0 } } + +[[schema.tables.columns]] +id = { table = 0, index = 0 } +name = "id" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } +auto_increment = true + +[[schema.tables.columns]] +id = { table = 0, index = 1 } +name = "event_id" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 0, index = 2 } +name = "repo" +ty = "String" +storage_ty = "Text" + +[[schema.tables.columns]] +id = { table = 0, index = 3 } +name = "pr" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 0, index = 4 } +name = "payload" +ty = "String" +storage_ty = "Jsonb" + +[[schema.tables.columns]] +id = { table = 0, index = 5 } +name = "status" +ty = "String" +storage_ty = { Enum = { name = "job_status", variants = [{ name = "pending" }, { name = "processing" }] } } + +[[schema.tables.columns]] +id = { table = 0, index = 6 } +name = "created_at" +ty = "Timestamp" +storage_ty = { Timestamp = 6 } + +[[schema.tables.indices]] +id = { table = 0, index = 0 } +name = "index_jobs_by_id" +on = 0 +columns = [{ column = { table = 0, index = 0 }, op = "Eq", scope = "Partition" }] +unique = true +primary_key = true + +[[schema.tables.indices]] +id = { table = 0, index = 1 } +name = "index_jobs_by_event_id" +on = 0 +columns = [{ column = { table = 0, index = 1 }, op = "Eq", scope = "Partition" }] +unique = true + +[[schema.tables.indices]] +id = { table = 0, index = 2 } +name = "index_jobs_by_status" +on = 0 +columns = [{ column = { table = 0, index = 5 }, op = "Eq", scope = "Partition" }] + +[[schema.tables]] +id = 1 +name = "reviews" +primary_key = { columns = [{ table = 1, index = 0 }], index = { table = 1, index = 0 } } + +[[schema.tables.columns]] +id = { table = 1, index = 0 } +name = "id" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } +auto_increment = true + +[[schema.tables.columns]] +id = { table = 1, index = 1 } +name = "event_id" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 1, index = 2 } +name = "repo" +ty = "String" +storage_ty = "Text" + +[[schema.tables.columns]] +id = { table = 1, index = 3 } +name = "pr" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 1, index = 4 } +name = "ok" +ty = "Bool" +storage_ty = "Boolean" + +[[schema.tables.columns]] +id = { table = 1, index = 5 } +name = "model" +ty = "String" +storage_ty = "Text" + +[[schema.tables.columns]] +id = { table = 1, index = 6 } +name = "markdown" +ty = "String" +storage_ty = "Text" + +[[schema.tables.columns]] +id = { table = 1, index = 7 } +name = "cost" +ty = "F64" +storage_ty = { Float = 8 } +nullable = true + +[[schema.tables.columns]] +id = { table = 1, index = 8 } +name = "comments" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 1, index = 9 } +name = "duration_ms" +ty = "U64" +storage_ty = { UnsignedInteger = 8 } + +[[schema.tables.columns]] +id = { table = 1, index = 10 } +name = "completed_at" +ty = "Timestamp" +storage_ty = { Timestamp = 6 } + +[[schema.tables.indices]] +id = { table = 1, index = 0 } +name = "index_reviews_by_id" +on = 1 +columns = [{ column = { table = 1, index = 0 }, op = "Eq", scope = "Partition" }] +unique = true +primary_key = true + +[[schema.tables.indices]] +id = { table = 1, index = 1 } +name = "index_reviews_by_event_id" +on = 1 +columns = [{ column = { table = 1, index = 1 }, op = "Eq", scope = "Partition" }] +unique = true From 7493de6952f988ff11c093c532962c851f1baea8 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 13 Oct 2026 16:14:48 +0000 Subject: [PATCH 3/8] Add live dashboard with SSE job events Expose /jobs, /dedup, and /events routes backed by a new EventBus that broadcasts job lifecycle and per-job progress over SSE. Serve a static dashboard from front/ that lists pending and in-progress jobs, streams agent activity, and shows completed reviews. Also add migrate-apply/migrate-generate scripts that load .env, update the Containerfile to ship the front assets and use the apply script, and refresh the roadmap. --- .devcontainer/devcontainer.json | 3 + Cargo.lock | 33 ++ Cargo.toml | 4 +- Containerfile | 7 +- README.md | 8 +- ROADMAP.md | 24 +- crates/steep-server/Cargo.toml | 1 + crates/steep-server/src/api.rs | 117 +++++- crates/steep-server/src/bot.rs | 26 +- crates/steep-server/src/bot_actions/review.rs | 15 +- crates/steep-server/src/dedup.rs | 7 + crates/steep-server/src/errors.rs | 17 + crates/steep-server/src/event_bus.rs | 335 ++++++++++++++++ crates/steep-server/src/job_queue.rs | 21 +- crates/steep-server/src/main.rs | 11 +- crates/steep-server/src/sandbox/agent.rs | 17 +- crates/steep-server/src/sandbox/instance.rs | 3 + crates/steep-server/src/schemas/job.rs | 6 +- crates/steep-server/src/schemas/review.rs | 3 +- crates/steep-server/src/state.rs | 7 +- front/css/dashboard.css | 366 ++++++++++++++++++ front/favicon.png | Bin 0 -> 210651 bytes front/index.html | 46 +++ front/js/api.js | 78 ++++ front/js/dashboard.js | 212 ++++++++++ front/js/job-card.js | 61 +++ front/js/job-detail.js | 150 +++++++ front/js/job-list.js | 58 +++ front/js/theme-toggle.js | 59 +++ public/index.html | 6 +- scripts/migrate-apply.sh | 19 + scripts/migrate-generate.sh | 22 ++ 32 files changed, 1681 insertions(+), 61 deletions(-) create mode 100644 crates/steep-server/src/event_bus.rs create mode 100644 front/css/dashboard.css create mode 100644 front/favicon.png create mode 100644 front/index.html create mode 100644 front/js/api.js create mode 100644 front/js/dashboard.js create mode 100644 front/js/job-card.js create mode 100644 front/js/job-detail.js create mode 100644 front/js/job-list.js create mode 100644 front/js/theme-toggle.js create mode 100755 scripts/migrate-apply.sh create mode 100755 scripts/migrate-generate.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dff506e..2dbce3e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,6 +19,9 @@ }, "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/steep,type=bind", "workspaceFolder": "/workspaces/steep", + "mounts": [ + "source=/run/user/1000/podman/podman.sock,target=/var/run/docker.sock,type=bind" + ], "runArgs": ["--userns=keep-id", "--security-opt", "label=disable"], "appPort": [3000] } diff --git a/Cargo.lock b/Cargo.lock index d04968b..49775d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1747,6 +1747,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -2536,6 +2542,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4274,6 +4290,7 @@ dependencies = [ "toasty", "toasty-cli", "tokio", + "tokio-stream", "tokio-util", "tower", "tower-http 0.7.1", @@ -4773,6 +4790,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -4885,10 +4903,19 @@ checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ "bitflags 2.13.1", "bytes", + "futures-core", + "futures-util", "http 1.5.0", "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -5211,6 +5238,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" + [[package]] name = "unicode-bidi" version = "0.3.18" diff --git a/Cargo.toml b/Cargo.toml index 2302f99..26e20b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ sentry = { version = "0.49", features = ["tower-axum-matched-path"] } sentry-anyhow = { version = "0.49", features = ["backtrace"] } dotenvy = "0.15" tower = "0.5" -tower-http = { version = "0.7", features = ["trace"] } +tower-http = { version = "0.7", features = ["trace", "fs"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } axum = "0.8" @@ -30,7 +30,7 @@ metrics = "0.24" metrics-exporter-prometheus = { version = "0.18", default-features = false, features = ["http-listener"] } toasty = { version = "0.11", features = ["postgresql", "serde", "jiff"] } toasty-cli = "0.11" -jiff = "0.2" +jiff = { version = "0.2", features = ["serde"] } [profile.release] debug = 1 diff --git a/Containerfile b/Containerfile index c2a086e..ea5eacf 100644 --- a/Containerfile +++ b/Containerfile @@ -23,7 +23,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /app COPY --from=builder /app/target/release/steep-server ./steep-server +COPY front ./front + COPY crates/steep-server/toasty ./toasty ENV MIGRATIONS_DIR=/app/toasty -CMD ["sh", "-c", "./steep-server migration apply && exec ./steep-server"] +COPY scripts/migrate-apply.sh ./scripts/migrate-apply.sh +RUN chmod +x ./scripts/migrate-apply.sh + +CMD ["sh", "-c", "./scripts/migrate-apply.sh && exec ./steep-server"] diff --git a/README.md b/README.md index 62f20b1..45f19b5 100644 --- a/README.md +++ b/README.md @@ -109,16 +109,18 @@ The database schema is versioned with [Toasty](https://github.com/tokio-rs/toast After changing a model in `crates/steep-server/src/schemas/`, generate a migration: ```sh -cargo run -p steep-server -- migration generate --name describe_change +./scripts/migrate-generate.sh describe_change ``` Apply the pending migrations **before starting the service** — the server does not migrate at boot: ```sh -cargo run -p steep-server -- migration apply +./scripts/migrate-apply.sh ``` -The `Containerfile` does this at startup: its entrypoint runs `steep-server migration apply` before exec'ing the server. The CLI reads the migration files from `MIGRATIONS_DIR` (defaults to the crate's `toasty/` directory; the image sets it to `/app/toasty`). +Both scripts load the `.env` file at the project root (so `DATABASE_URL` is picked up). Generation runs through `cargo run -p steep-server --`; applying uses the compiled `steep-server` binary when present (as in the image), falling back to `cargo run` otherwise. + +The `Containerfile` does this at startup: its entrypoint runs `scripts/migrate-apply.sh` before exec'ing the server. The CLI reads the migration files from `MIGRATIONS_DIR` (defaults to the crate's `toasty/` directory; the image sets it to `/app/toasty`). Other subcommands: `migration snapshot`, `migration drop`, `migration reset`. They read `DATABASE_URL`. diff --git a/ROADMAP.md b/ROADMAP.md index 5cd299a..2bf276e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,15 +11,22 @@ - If a repository has no Dev Container configuration, run its review in a default container based on `debian:stable-slim`. - Use `/workspace` as the workspace path for the default container and as the fallback when `workspaceFolder` is not specified. -## 1.3 — Typed action routing — planned +## 1.3 — Durable jobs & resumption — planned + +- Persist review jobs and their progress in PostgreSQL so in-flight work survives a restart and can be resumed instead of being lost. +- Expose an API to retrieve the list of jobs by state: running, pending, and review done. +- Stream job events over Server-Sent Events (SSE) so clients see status changes in real time. + +## 1.4 — YAML configuration — planned + +- Add a YAML configuration file as the primary way to configure Steep, replacing part of the current environment variables. +- Move non-secret settings such as the model, timeouts, concurrency limit, and sandbox iterations into the file, and keep secrets (API keys, tokens, webhook secret, database URL) in environment variables. + +## 2.0 — Typed action routing and implementation requests — planned - Introduce a type-safe model output for the action Steep should take, such as `Review` or `Implementation`. - Route review requests (for example, “@steep, review this PR”) to the existing review flow. -- Recognize implementation requests (for example, “@steep, rename method `XXX` to `YYY`”) and establish their dispatch path. Execution of implementation requests is planned for 2.0. - -## 2.0 — Implementation requests — planned - -- Use the typed action from 1.3 to route explicit implementation requests to an implementation workflow. +- Recognize implementation requests (for example, “@steep, rename method `XXX` to `YYY`”) and route them to the implementation workflow. - For a request on an issue, clone the repository from its default branch and implement the requested features on a new branch. - For a request on an existing pull request, clone its branch and implement the requested modifications there. - When the agent's iteration loop ends, the orchestration layer automatically pushes the resulting changes: it opens a pull request for issue-based work, or updates the existing pull request branch. The agent does not receive or invoke a push tool. @@ -31,6 +38,11 @@ - Allow configuring a dedicated model for pull request reviews and another for implementation requests. - Route each action type to its configured model, with a clear fallback when only one model is configured. +## 2.2 — Sub-agents — planned + +- Let the agent delegate well-scoped parts of a task to sub-agents that run their own iteration loop. +- Run independent sub-agents in parallel and have the main agent consolidate their results. + ## 3.0 — Autonomous Sentry fixes — planned - Automatically discover project errors reported by Sentry, without waiting for a user comment. diff --git a/crates/steep-server/Cargo.toml b/crates/steep-server/Cargo.toml index bd12c40..e7e19a1 100644 --- a/crates/steep-server/Cargo.toml +++ b/crates/steep-server/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" reqwest = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } +tokio-stream = { workspace = true, features = ["sync"] } futures-util = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } diff --git a/crates/steep-server/src/api.rs b/crates/steep-server/src/api.rs index 4896016..158b5ff 100644 --- a/crates/steep-server/src/api.rs +++ b/crates/steep-server/src/api.rs @@ -1,31 +1,48 @@ use axum::body::{Bytes, to_bytes}; -use axum::extract::{FromRef, FromRequest, State}; +use axum::extract::{FromRef, FromRequest, Path, State}; use axum::http::Request; use axum::response::IntoResponse; +use axum::response::sse::{Event, KeepAlive, Sse}; use axum::routing::{get, post}; use axum::{Json, Router}; +use futures_util::stream::{self, Stream, StreamExt}; use reqwest::StatusCode; use ring::hmac; use sentry::integrations::tower::{NewSentryLayer, SentryHttpLayer}; -use serde_json::Value; +use serde_json::{Value, json}; +use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError}; use tower::ServiceBuilder; +use tower_http::services::ServeDir; use tower_http::trace::TraceLayer; -use tracing::{info, instrument}; - -use tokio_util::sync::CancellationToken; +use tracing::{info, instrument, warn}; use crate::consts::{GITEA_EVENT_TYPE_HEADER_NAME, GITEA_SIG_HEADER_NAME, MAX_WEBHOOK_BODY_SIZE}; use crate::errors::AppError; +use crate::event_bus::JobEvent; use crate::gitea::WebhookType; use crate::metrics; +use crate::schemas::JobStatus; use crate::state::AppState; -pub async fn start(app_state: AppState, shutdown: CancellationToken) -> anyhow::Result<()> { +pub async fn start(app_state: AppState) -> anyhow::Result<()> { let http_port = app_state.config.http_port; + let shutdown = app_state.shutdown.clone(); + + // Le dashboard est servi depuis un dossier statique ; les routes API + // ci-dessous restent prioritaires (le dossier n'est que le fallback). + let front_dir = std::env::var("FRONT_DIR").unwrap_or_else(|_| String::from("front")); + if !std::path::Path::new(&front_dir).is_dir() { + warn!(dir = %front_dir, "front directory not found, the dashboard will 404"); + } + let dashboard = ServeDir::new(&front_dir); let app = Router::new() - .route("/", get(root)) .route("/webhook", post(webhook)) + .route("/dedup", get(dedup_list)) + .route("/jobs", get(jobs_list)) + .route("/events", get(lifecycle_events)) + .route("/jobs/{id}/events", get(job_events)) + .fallback_service(dashboard) .layer( ServiceBuilder::new() .layer(NewSentryLayer::>::new_from_top()) @@ -46,8 +63,73 @@ pub async fn start(app_state: AppState, shutdown: CancellationToken) -> anyhow:: Ok(()) } -async fn root() -> &'static str { - "Hi, I'm Steep :)" +async fn dedup_list(State(app_state): State) -> Result { + let reviews = app_state.dedup.list_reviews().await?; + + Ok(Json(json!({ + "reviews": reviews + }))) +} + +async fn jobs_list(State(app_state): State) -> Result { + let pending = app_state.queue.list(JobStatus::Pending).await?; + let processing = app_state.queue.list(JobStatus::Processing).await?; + + Ok(Json(json!({ + "pending": pending, + "processing": processing, + }))) +} + +/// Flux SSE des évènements de cycle de vie global (liste des jobs). +/// +/// Le flux est clos dès que l'arrêt gracieux est lancé, sans quoi une connexion +/// SSE vivante empêcherait le serveur de s'arrêter. +async fn lifecycle_events( + State(app_state): State, +) -> Sse>> { + let rx = app_state.event_bus.subscribe_lifecycle(); + let shutdown = app_state.shutdown.cancelled_owned(); + + let stream = BroadcastStream::new(rx).map(|result| match result { + Ok(event) => sse_json("lifecycle", &event), + Err(BroadcastStreamRecvError::Lagged(skipped)) => sse_gap(skipped), + }); + + Sse::new(stream.take_until(shutdown)).keep_alive(KeepAlive::default()) +} + +async fn job_events( + State(app_state): State, + Path(id): Path, +) -> Result>>, AppError> { + let subscription = app_state + .event_bus + .subscribe_job(id) + .ok_or(AppError::JobNotFoundErr(id))?; + let shutdown = app_state.shutdown.cancelled_owned(); + + let history = subscription + .history + .into_iter() + .map(|event| sse_json("progress", &event)); + + let live = BroadcastStream::new(subscription.live).map(|result| match result { + Ok(event) => sse_json("progress", &event), + Err(BroadcastStreamRecvError::Lagged(skipped)) => sse_gap(skipped), + }); + + let stream = stream::iter(history).chain(live).take_until(shutdown); + + Ok(Sse::new(stream).keep_alive(KeepAlive::default())) +} + +fn sse_json(name: &'static str, payload: &T) -> Result { + Event::default().event(name).json_data(payload) +} + +fn sse_gap(skipped: u64) -> Result { + Ok(Event::default().event("gap").data(skipped.to_string())) } #[instrument(skip(app_state), fields(webhook_type), err)] @@ -73,10 +155,19 @@ async fn webhook( } } - if let Err(err) = app_state.queue.push(wb).await { - metrics::webhook_enqueue_failed(event_type); - return Err(AppError::EnqueueErr(anyhow::Error::from(err))); - } + let job = match app_state.queue.push(wb).await { + Ok(job) => job, + Err(err) => { + metrics::webhook_enqueue_failed(event_type); + return Err(AppError::EnqueueErr(anyhow::Error::from(err))); + } + }; + + app_state.event_bus.publish(JobEvent::Queued { + id: job.id, + repo: job.repo, + pr: job.pr, + }); Ok((StatusCode::CREATED, "Task started")) } diff --git a/crates/steep-server/src/bot.rs b/crates/steep-server/src/bot.rs index dadc2f2..c7172cd 100644 --- a/crates/steep-server/src/bot.rs +++ b/crates/steep-server/src/bot.rs @@ -9,6 +9,7 @@ use tracing::{error, info, instrument}; use crate::{ bot_actions::review::ReviewOutcome, dedup::{Dedup, ReviewRecord}, + event_bus::{EventBus, Reporter}, gitea::{GiteaAPI, WebhookType}, job_queue::JobQueue, metrics, @@ -134,9 +135,11 @@ pub struct Bot { sandbox: SandboxConfig, dedup: Dedup, queue: JobQueue, + event_bus: EventBus, } impl Bot { + #[allow(clippy::too_many_arguments)] pub fn new( gitea_api: GiteaAPI, open_router_client: OpenRouterClient, @@ -145,6 +148,7 @@ impl Bot { sandbox: SandboxConfig, dedup: Dedup, queue: JobQueue, + event_bus: EventBus, ) -> Self { Self { gitea_api, @@ -154,6 +158,7 @@ impl Bot { sandbox, dedup, queue, + event_bus, } } @@ -214,6 +219,7 @@ impl Bot { Ok(true) => { metrics::task_skipped(event_type); info!("Review already done, skipping"); + self.event_bus.skip(job.id, &job.repo, job.pr); if let Err(err) = self.queue.remove(job.id).await { error!(%err, "failed to remove job"); @@ -224,9 +230,11 @@ impl Bot { Err(err) => error!(%err, "Dedup lookup failed, reviewing anyway"), } + let reporter = self.event_bus.start(job.id, job.repo.clone(), job.pr); + let payload = job.payload.0.clone(); let started = std::time::Instant::now(); - let result = self.exec(payload).await; + let result = self.exec(payload, &reporter).await; let duration_ms = started.elapsed().as_millis() as u64; let (ok, outcome) = match result { @@ -243,6 +251,8 @@ impl Bot { } }; + let comments = outcome.comments as u64; + let record = ReviewRecord { event_id: job.event_id, repo: job.repo.clone(), @@ -251,7 +261,7 @@ impl Bot { model: self.open_router_model.clone(), markdown: outcome.markdown, cost: outcome.cost, - comments: outcome.comments as u64, + comments, duration_ms, }; @@ -259,13 +269,20 @@ impl Bot { error!(%err, "failed to record review"); } + self.event_bus + .finish(job.id, &job.repo, job.pr, ok, comments, duration_ms); + if let Err(err) = self.queue.remove(job.id).await { error!(%err, "failed to remove job"); } } - #[instrument(skip(self, webhook), fields(repo, pr))] - pub async fn exec(&self, webhook: WebhookType) -> anyhow::Result { + #[instrument(skip(self, webhook, reporter), fields(repo, pr))] + pub async fn exec( + &self, + webhook: WebhookType, + reporter: &Reporter, + ) -> anyhow::Result { match &webhook { WebhookType::Review(p) => { tracing::Span::current().record("repo", &p.repository.full_name); @@ -284,6 +301,7 @@ impl Bot { &self.sandbox, tools, review_payload, + reporter, ) .await } diff --git a/crates/steep-server/src/bot_actions/review.rs b/crates/steep-server/src/bot_actions/review.rs index e37cf08..3fe8148 100644 --- a/crates/steep-server/src/bot_actions/review.rs +++ b/crates/steep-server/src/bot_actions/review.rs @@ -5,6 +5,7 @@ use tracing::{info, instrument, warn}; use crate::{ bot::{ReviewItem, ReviewResult, ReviewSeverity, ReviewSide}, consts::{BOT_PROCESS_MSG, REVIEW_PROMPT, SANDBOX_SYSTEM_PROMPT}, + event_bus::Reporter, gitea::{GiteaAPI, PullRequestFile, ReviewPayload}, metrics, open_router::{OpenRouterClient, Tool}, @@ -28,7 +29,14 @@ impl ReviewOutcome { } } -#[instrument(skip(gitea_api, open_router_client, sandbox_config, tools, review_payload))] +#[instrument(skip( + gitea_api, + open_router_client, + sandbox_config, + tools, + review_payload, + reporter +))] pub async fn exec_review( gitea_api: &GiteaAPI, open_router_client: &OpenRouterClient, @@ -36,6 +44,7 @@ pub async fn exec_review( sandbox_config: &SandboxConfig, tools: Vec, review_payload: ReviewPayload, + reporter: &Reporter, ) -> anyhow::Result { tracing::info!( repo = %review_payload.repository.full_name, @@ -86,6 +95,7 @@ pub async fn exec_review( tools, &review_payload, &bot_request, + reporter, ) .await?; @@ -144,12 +154,14 @@ async fn run_sandboxed_review( tools: Vec, review_payload: &ReviewPayload, bot_request: &str, + reporter: &Reporter, ) -> anyhow::Result<(ReviewResult, Option)> { let sandbox = Sandbox::create( &sandbox_config.runtime, &review_payload.repository.clone_url, gitea_api.token(), review_payload.pull_request.number, + reporter, ) .await?; @@ -160,6 +172,7 @@ async fn run_sandboxed_review( SANDBOX_SYSTEM_PROMPT, bot_request, sandbox_config.max_iterations, + reporter, ) .await; diff --git a/crates/steep-server/src/dedup.rs b/crates/steep-server/src/dedup.rs index b3d83af..51be893 100644 --- a/crates/steep-server/src/dedup.rs +++ b/crates/steep-server/src/dedup.rs @@ -70,6 +70,13 @@ impl Dedup { Ok(()) } + + pub async fn list_reviews(&self) -> toasty::Result> { + let mut db = self.db.clone(); + let reviews = Review::all().exec(&mut db).await?; + + Ok(reviews) + } } #[cfg(test)] diff --git a/crates/steep-server/src/errors.rs b/crates/steep-server/src/errors.rs index b06bda2..2cee8af 100644 --- a/crates/steep-server/src/errors.rs +++ b/crates/steep-server/src/errors.rs @@ -1,5 +1,6 @@ use axum::response::IntoResponse; use reqwest::StatusCode; +use sentry::capture_error; #[derive(thiserror::Error, Debug)] pub enum AppError { @@ -27,9 +28,15 @@ pub enum AppError { #[error("Already processed")] AlreadyProcessedErr, + #[error("Job {0} is not running")] + JobNotFoundErr(u64), + #[error(transparent)] BadJsonStructErr(#[from] serde_json::Error), + #[error("Other toasty error")] + OtherToastyErr(#[from] toasty::Error), + #[error(transparent)] Other(#[from] anyhow::Error), } @@ -61,6 +68,9 @@ impl IntoResponse for AppError { "WebHook sig header is invalid".to_string(), ), AppError::AlreadyProcessedErr => (StatusCode::OK, "Already processed".to_string()), + AppError::JobNotFoundErr(id) => { + (StatusCode::NOT_FOUND, format!("job {id} is not running")) + } AppError::EnqueueErr(err) => { sentry_anyhow::capture_anyhow(&err); ( @@ -75,6 +85,13 @@ impl IntoResponse for AppError { "Internal server error".to_string(), ) } + AppError::OtherToastyErr(err) => { + capture_error(&err); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + ) + } } .into_response() } diff --git a/crates/steep-server/src/event_bus.rs b/crates/steep-server/src/event_bus.rs new file mode 100644 index 0000000..c9c051e --- /dev/null +++ b/crates/steep-server/src/event_bus.rs @@ -0,0 +1,335 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::{Arc, Mutex, RwLock}; + +use serde::Serialize; +use tokio::sync::broadcast; + +use crate::text::excerpt; + +const LIFECYCLE_CAPACITY: usize = 256; +const PROGRESS_CAPACITY: usize = 256; +const HISTORY_CAPACITY: usize = 256; +const MAX_EVENT_ARGUMENTS: usize = 200; + +/// Évènement de cycle de vie d'un job, diffusé globalement. +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum JobEvent { + Queued { + id: u64, + repo: String, + pr: u64, + }, + Started { + id: u64, + repo: String, + pr: u64, + }, + ReviewDone { + id: u64, + repo: String, + pr: u64, + ok: bool, + comments: u64, + duration_ms: u64, + }, + Failed { + id: u64, + repo: String, + pr: u64, + }, + Skipped { + id: u64, + repo: String, + pr: u64, + }, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum Progress { + ToolCall { + iteration: usize, + name: String, + arguments: String, + }, + ToolResult { + iteration: usize, + name: String, + truncated: bool, + output: String, + }, + Finished { + ok: bool, + }, +} + +#[derive(Debug)] +struct JobChannel { + tx: broadcast::Sender, + history: Mutex>, +} + +impl JobChannel { + fn subscribe(&self) -> (Vec, broadcast::Receiver) { + let history = self.history.lock().expect("job history lock poisoned"); + let rx = self.tx.subscribe(); + + (history.iter().cloned().collect(), rx) + } + + fn emit(&self, event: Progress) { + let mut history = self.history.lock().expect("job history lock poisoned"); + + if history.len() >= HISTORY_CAPACITY { + history.pop_front(); + } + + let _ = self.tx.send(event.clone()); + history.push_back(event); + } +} + +pub struct JobSubscription { + pub history: Vec, + pub live: broadcast::Receiver, +} + +#[derive(Clone)] +pub struct EventBus { + lifecycle: broadcast::Sender, + jobs: Arc>>>, +} + +impl EventBus { + pub fn new() -> Self { + let (lifecycle, _rx) = broadcast::channel(LIFECYCLE_CAPACITY); + + Self { + lifecycle, + jobs: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub fn publish(&self, event: JobEvent) { + let _ = self.lifecycle.send(event); + } + + pub fn subscribe_lifecycle(&self) -> broadcast::Receiver { + self.lifecycle.subscribe() + } + + pub fn start(&self, id: u64, repo: impl Into, pr: u64) -> Reporter { + let (tx, _rx) = broadcast::channel(PROGRESS_CAPACITY); + let channel = Arc::new(JobChannel { + tx, + history: Mutex::new(VecDeque::new()), + }); + + self.jobs + .write() + .expect("job registry lock poisoned") + .insert(id, channel.clone()); + + self.publish(JobEvent::Started { + id, + repo: repo.into(), + pr, + }); + + Reporter { channel } + } + + pub fn finish(&self, id: u64, repo: &str, pr: u64, ok: bool, comments: u64, duration_ms: u64) { + let event = if ok { + JobEvent::ReviewDone { + id, + repo: repo.to_string(), + pr, + ok, + comments, + duration_ms, + } + } else { + JobEvent::Failed { + id, + repo: repo.to_string(), + pr, + } + }; + + self.publish(event); + + if let Some(channel) = self + .jobs + .write() + .expect("job registry lock poisoned") + .remove(&id) + { + let _ = channel.tx.send(Progress::Finished { ok }); + } + } + + pub fn skip(&self, id: u64, repo: &str, pr: u64) { + self.publish(JobEvent::Skipped { + id, + repo: repo.to_string(), + pr, + }); + } + + pub fn subscribe_job(&self, id: u64) -> Option { + let channel = self + .jobs + .read() + .expect("job registry lock poisoned") + .get(&id) + .cloned()?; + + let (history, live) = channel.subscribe(); + + Some(JobSubscription { history, live }) + } +} + +impl Default for EventBus { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Debug)] +pub struct Reporter { + channel: Arc, +} + +impl Reporter { + pub fn tool_call(&self, iteration: usize, name: &str, arguments: &str) { + self.channel.emit(Progress::ToolCall { + iteration, + name: name.to_string(), + arguments: excerpt(arguments, MAX_EVENT_ARGUMENTS), + }); + } + + /// Signale la fin d'un appel d'outil. + pub fn tool_result(&self, iteration: usize, name: &str, truncated: bool, output: &str) { + self.channel.emit(Progress::ToolResult { + iteration, + name: name.to_string(), + truncated, + output: output.to_string(), + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_started_job_is_announced_and_subscribable() { + let bus = EventBus::new(); + let mut lifecycle = bus.subscribe_lifecycle(); + + let _reporter = bus.start(1, "owner/repo", 7); + + assert!(matches!( + lifecycle.try_recv().unwrap(), + JobEvent::Started { id: 1, pr: 7, .. } + )); + assert!(bus.subscribe_job(1).is_some()); + } + + #[test] + fn progress_emitted_before_subscribing_is_replayed() { + let bus = EventBus::new(); + let reporter = bus.start(1, "owner/repo", 7); + + reporter.tool_call(1, "ls", "{}"); + reporter.tool_result(1, "ls", false, "src\nmain.rs"); + + let subscription = bus.subscribe_job(1).unwrap(); + + assert_eq!(subscription.history.len(), 2); + assert!(matches!( + subscription.history[0], + Progress::ToolCall { iteration: 1, .. } + )); + assert!(matches!( + &subscription.history[1], + Progress::ToolResult { name, .. } if name == "ls" + )); + } + + #[test] + fn progress_emitted_after_subscribing_arrives_live() { + let bus = EventBus::new(); + let reporter = bus.start(1, "owner/repo", 7); + let mut subscription = bus.subscribe_job(1).unwrap(); + + reporter.tool_call(2, "grep", r#"{"pattern":"fn main"}"#); + + assert!(matches!( + subscription.live.try_recv().unwrap(), + Progress::ToolCall { iteration: 2, name, .. } if name == "grep" + )); + } + + #[test] + fn finish_closes_the_channel_and_announces_the_review() { + let bus = EventBus::new(); + let mut lifecycle = bus.subscribe_lifecycle(); + let _reporter = bus.start(1, "owner/repo", 7); + let mut subscription = bus.subscribe_job(1).unwrap(); + + bus.finish(1, "owner/repo", 7, true, 3, 120); + + assert!(bus.subscribe_job(1).is_none()); + assert!(matches!( + subscription.live.try_recv().unwrap(), + Progress::Finished { ok: true } + )); + + let _started = lifecycle.try_recv().unwrap(); + assert!(matches!( + lifecycle.try_recv().unwrap(), + JobEvent::ReviewDone { + id: 1, + ok: true, + comments: 3, + duration_ms: 120, + .. + } + )); + } + + #[test] + fn a_failed_job_is_announced_as_failed() { + let bus = EventBus::new(); + let mut lifecycle = bus.subscribe_lifecycle(); + let _reporter = bus.start(1, "owner/repo", 7); + + bus.finish(1, "owner/repo", 7, false, 0, 42); + + let _started = lifecycle.try_recv().unwrap(); + assert!(matches!( + lifecycle.try_recv().unwrap(), + JobEvent::Failed { id: 1, .. } + )); + } + + #[test] + fn a_skipped_job_is_announced_without_a_channel() { + let bus = EventBus::new(); + let mut lifecycle = bus.subscribe_lifecycle(); + + bus.skip(1, "owner/repo", 7); + + assert!(matches!( + lifecycle.try_recv().unwrap(), + JobEvent::Skipped { id: 1, pr: 7, .. } + )); + assert!(bus.subscribe_job(1).is_none()); + } +} diff --git a/crates/steep-server/src/job_queue.rs b/crates/steep-server/src/job_queue.rs index d09649d..1b65737 100644 --- a/crates/steep-server/src/job_queue.rs +++ b/crates/steep-server/src/job_queue.rs @@ -23,17 +23,14 @@ impl JobQueue { Self { db } } - /// Enfile une nouvelle review. - /// - /// L'unicité de `event_id` fait échouer l'insertion d'un doublon. - pub async fn push(&self, webhook: WebhookType) -> toasty::Result<()> { + pub async fn push(&self, webhook: WebhookType) -> toasty::Result { let event_id = webhook.event_id(); let repo = webhook.repository_full_name().to_string(); let pr = webhook.pull_request_number(); let mut db = self.db.clone(); - toasty::create!(Job { + let job = toasty::create!(Job { event_id, repo, pr, @@ -43,14 +40,9 @@ impl JobQueue { .exec(&mut db) .await?; - Ok(()) + Ok(job) } - /// Flux des jobs en attente. - /// - /// C'est un `Stream` (et non un `Iterator`) : l'accès à la base est - /// asynchrone. Le flux interroge la table en boucle et met en tampon les - /// jobs trouvés. pub fn pending(&self) -> impl Stream { let db = self.db.clone(); @@ -103,6 +95,13 @@ impl JobQueue { Ok(()) } + + pub async fn list(&self, status: JobStatus) -> toasty::Result> { + let mut db = self.db.clone(); + let jobs = Job::filter_by_status(status).exec(&mut db).await?; + + Ok(jobs) + } } #[cfg(test)] diff --git a/crates/steep-server/src/main.rs b/crates/steep-server/src/main.rs index 6d4231c..c639505 100644 --- a/crates/steep-server/src/main.rs +++ b/crates/steep-server/src/main.rs @@ -1,8 +1,8 @@ use std::time::Duration; use crate::{ - bot::Bot, dedup::Dedup, gitea::GiteaAPI, job_queue::JobQueue, open_router::OpenRouterClient, - sandbox::SandboxConfig, state::AppState, + bot::Bot, dedup::Dedup, event_bus::EventBus, gitea::GiteaAPI, job_queue::JobQueue, + open_router::OpenRouterClient, sandbox::SandboxConfig, state::AppState, }; use dotenvy::dotenv; @@ -19,6 +19,7 @@ mod db; mod dedup; mod env; mod errors; +mod event_bus; mod gitea; mod job_queue; mod metrics; @@ -126,6 +127,7 @@ async fn run() -> anyhow::Result<()> { let db = db::connect(&config.database_url).await?; let dedup = Dedup::new(db.clone()); let queue = JobQueue::new(db); + let event_bus = EventBus::new(); let bot = Bot::new( gitea_api, @@ -135,13 +137,16 @@ async fn run() -> anyhow::Result<()> { sandbox, dedup.clone(), queue.clone(), + event_bus.clone(), ); let app_state = AppState { queue, dedup, + event_bus, bot_name: gitea_user.login.clone(), config, + shutdown: shutdown.clone(), }; let signal = async { @@ -166,7 +171,7 @@ async fn run() -> anyhow::Result<()> { result = async { tokio::try_join!( bot.start(shutdown.clone()), - api::start(app_state, shutdown.clone()), + api::start(app_state), signal ) } => { diff --git a/crates/steep-server/src/sandbox/agent.rs b/crates/steep-server/src/sandbox/agent.rs index 7de2702..a2c1768 100644 --- a/crates/steep-server/src/sandbox/agent.rs +++ b/crates/steep-server/src/sandbox/agent.rs @@ -13,6 +13,7 @@ use serde_json::Value; use tracing::{info, warn}; use crate::{ + event_bus::Reporter, open_router::{Message, OpenRouterClient, Role, Tool, ToolCall, ToolChoice}, sandbox::{Sandbox, tools}, text::{excerpt, truncate_bytes}, @@ -61,17 +62,6 @@ pub struct AgentResult { pub iterations: usize, } -/// Runs the tool-calling loop until the model answers or `max_iterations` is -/// reached. -/// -/// `tool_definitions` is the set of tools the model may call; it is selected by -/// the caller based on the webhook action (see [`tools::for_webhook`]). -/// -/// The answer of the model is parsed into `A`, which is how the caller states the -/// format it expects: while [`FromStr`] rejects the answer, the error is sent back -/// to the model so that it can correct itself, which costs an iteration. The -/// parsing happens here because that is where the conversation and the remaining -/// budget are at hand. pub async fn run( open_router: &OpenRouterClient, sandbox: &Sandbox, @@ -79,6 +69,7 @@ pub async fn run( system_prompt: &str, user_prompt: &str, max_iterations: usize, + reporter: &Reporter, ) -> anyhow::Result> where A: FromStr, @@ -177,9 +168,13 @@ where )); for call in &response.tool_calls { + reporter.tool_call(iteration, call.name(), call.arguments_json()); + let started = Instant::now(); let (content, truncated) = execute(sandbox, call).await; + reporter.tool_result(iteration, call.name(), truncated, &content); + info!( iteration, tool = call.name(), diff --git a/crates/steep-server/src/sandbox/instance.rs b/crates/steep-server/src/sandbox/instance.rs index 880166c..8478c58 100644 --- a/crates/steep-server/src/sandbox/instance.rs +++ b/crates/steep-server/src/sandbox/instance.rs @@ -11,6 +11,8 @@ use devcontainer_rs::{Container, ContainerRuntime, ExecOutput}; use tempfile::TempDir; use tracing::{info, instrument}; +use crate::event_bus::Reporter; + /// Devcontainer locations recognized within a repository, in priority order. const DEVCONTAINER_PATHS: [&str; 2] = [".devcontainer/devcontainer.json", ".devcontainer.json"]; @@ -43,6 +45,7 @@ impl Sandbox { clone_url: &str, token: &str, pull_request_number: u64, + reporter: &Reporter, ) -> anyhow::Result { let workspace = tempfile::tempdir().context("failed to create sandbox workspace")?; let repo_dir = workspace.path().join("repo"); diff --git a/crates/steep-server/src/schemas/job.rs b/crates/steep-server/src/schemas/job.rs index d36cf4a..174df5f 100644 --- a/crates/steep-server/src/schemas/job.rs +++ b/crates/steep-server/src/schemas/job.rs @@ -1,14 +1,16 @@ use jiff::Timestamp; +use serde::Serialize; use crate::gitea::WebhookType; -#[derive(Debug, Clone, Copy, PartialEq, Eq, toasty::Embed)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, toasty::Embed, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] pub enum JobStatus { Pending, Processing, } -#[derive(Debug, toasty::Model)] +#[derive(Debug, toasty::Model, Clone, Serialize)] pub struct Job { #[key] #[auto] diff --git a/crates/steep-server/src/schemas/review.rs b/crates/steep-server/src/schemas/review.rs index 0539bfa..0715a07 100644 --- a/crates/steep-server/src/schemas/review.rs +++ b/crates/steep-server/src/schemas/review.rs @@ -1,6 +1,7 @@ use jiff::Timestamp; +use serde::Serialize; -#[derive(Debug, toasty::Model)] +#[derive(Debug, toasty::Model, Serialize, Clone)] pub struct Review { #[key] #[auto] diff --git a/crates/steep-server/src/state.rs b/crates/steep-server/src/state.rs index c66b4e1..263ffaf 100644 --- a/crates/steep-server/src/state.rs +++ b/crates/steep-server/src/state.rs @@ -1,9 +1,14 @@ -use crate::{dedup::Dedup, env::EnvConfig, job_queue::JobQueue}; +use tokio_util::sync::CancellationToken; + +use crate::{dedup::Dedup, env::EnvConfig, event_bus::EventBus, job_queue::JobQueue}; #[derive(Clone)] pub struct AppState { pub queue: JobQueue, pub dedup: Dedup, + pub event_bus: EventBus, pub bot_name: String, pub config: EnvConfig, + /// Jeton annulé au démarrage de l'arrêt gracieux : il sert à clore les flux SSE. + pub shutdown: CancellationToken, } diff --git a/front/css/dashboard.css b/front/css/dashboard.css new file mode 100644 index 0000000..ec12438 --- /dev/null +++ b/front/css/dashboard.css @@ -0,0 +1,366 @@ +/* Dashboard Steep — reprend le design system du site public + (mêmes jetons de couleur, mêmes composants d'en-tête/pied) et ajoute les + styles propres au tableau de bord. */ + +:root { + color-scheme: dark; + --paper: #0b1520; + --surface: #111f2d; + --ink: #edf4f7; + --ink-soft: #c2cfd9; + --muted: #94a8b8; + --line: #263b4d; + --green: #087b5b; + --green-dark: #5bd4a7; + --green-pale: #153a32; + --blue-pale: #1a3044; + --shadow: 0 18px 50px rgba(0, 0, 0, 0.24); + --button-hover: #066449; + --brand-mark-bg: #149b72; + --brand-mark-fg: #07111b; + --step-line: #23604c; + --setup-bg: #07111b; + --radius: 20px; + --content: 1160px; + --red: #d1667f; + --amber: #d7a35b; +} + +:root[data-theme="light"] { + color-scheme: light; + --paper: #f7f8f5; + --surface: #ffffff; + --ink: #102f50; + --ink-soft: #38536c; + --muted: #718092; + --line: #e1e8e5; + --green: #149b72; + --green-dark: #087b5b; + --green-pale: #e8f5ef; + --blue-pale: #edf3f8; + --shadow: 0 18px 50px rgba(16, 47, 80, 0.08); + --button-hover: #087b5b; + --brand-mark-bg: #102f50; + --brand-mark-fg: #ffffff; + --step-line: #cce8da; + --setup-bg: #102f50; + --red: #b04a63; + --amber: #a9741f; +} + +*, *::before, *::after { box-sizing: border-box; } +html { scroll-behavior: smooth; scroll-padding-top: 90px; } +body { + margin: 0; + background: var(--paper); + color: var(--ink); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + line-height: 1.65; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +a { color: inherit; } +img { display: block; max-width: 100%; } +.container { width: min(var(--content), calc(100% - 48px)); margin-inline: auto; } +.mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; } +.muted { color: var(--muted); } + +.site-header { + position: sticky; + z-index: 5; + top: 0; + border-bottom: 1px solid var(--line); + background: var(--paper); + backdrop-filter: blur(16px); +} +.nav { + min-height: 76px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 28px; +} +.wordmark { + display: inline-flex; + align-items: center; + gap: 11px; + color: var(--ink); + text-decoration: none; + font-size: 18px; + font-weight: 850; + letter-spacing: 0.13em; +} +.wordmark-mark { + width: 34px; + height: 34px; + display: grid; + place-items: center; + border-radius: 11px; + background: var(--brand-mark-bg); + color: var(--brand-mark-fg); + font-size: 17px; + letter-spacing: 0; +} +.nav-links { display: flex; align-items: center; gap: 22px; } +.nav-links a { + color: var(--ink-soft); + font-size: 14px; + font-weight: 650; + text-decoration: none; + transition: color 160ms ease; +} +.nav-links a:hover { color: var(--green-dark); } +.nav-links .nav-cta { + padding: 10px 16px; + border: 1px solid var(--line); + border-radius: 999px; + background: var(--surface); + color: var(--ink); +} +.nav-links .nav-cta:hover { border-color: var(--green); color: var(--green-dark); } + +.theme-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 9px 13px; + border: 1px solid var(--line); + border-radius: 999px; + background: var(--surface); + color: var(--ink-soft); + font: inherit; + font-size: 13px; + font-weight: 700; + cursor: pointer; + transition: border-color 160ms ease, color 160ms ease, background 160ms ease; +} +.theme-toggle:hover { border-color: var(--green-dark); color: var(--green-dark); } +.theme-icon { color: var(--green-dark); font-size: 16px; line-height: 1; } + +.section { padding: 46px 0 82px; } +.section-heading { max-width: 720px; margin-bottom: 26px; } +.section-heading h1 { + margin-bottom: 12px; + font-size: clamp(30px, 4vw, 44px); + line-height: 1.12; + letter-spacing: -0.045em; +} +.section-heading p { margin-bottom: 0; color: var(--muted); font-size: 17px; } +.eyebrow { + display: inline-flex; + align-items: center; + gap: 9px; + margin: 0 0 16px; + color: var(--green-dark); + font-size: 12px; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; +} +.eyebrow::before { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--green); + content: ""; +} + +.button { + display: inline-flex; + min-height: 42px; + align-items: center; + justify-content: center; + gap: 9px; + padding: 9px 16px; + border: 1px solid transparent; + border-radius: 999px; + background: var(--green); + color: white; + font: inherit; + font-size: 14px; + font-weight: 750; + text-decoration: none; + cursor: pointer; + transition: transform 160ms ease, background 160ms ease, box-shadow 160ms ease; +} +.button:hover { transform: translateY(-2px); background: var(--button-hover); box-shadow: 0 8px 20px rgba(20, 155, 114, 0.2); } +.button-secondary { border-color: var(--line); background: var(--surface); color: var(--ink); } +.button-secondary:hover { border-color: #b8d8ca; background: var(--green-pale); color: var(--green-dark); box-shadow: none; } +.arrow { font-size: 17px; line-height: 1; } + +/* ---- Tableau de bord ---- */ + +.toolbar { + display: flex; + align-items: center; + justify-content: flex-end; + margin-bottom: 22px; +} + +.board { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; + margin-bottom: 18px; +} + +.panel { + padding: 22px; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); + box-shadow: 0 5px 20px rgba(16, 47, 80, 0.025); +} +.panel-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} +.panel-head h2 { margin: 0; font-size: 17px; letter-spacing: -0.02em; } +.count { + min-width: 26px; + padding: 3px 9px; + border-radius: 999px; + background: var(--blue-pale); + color: var(--ink); + font-size: 12px; + font-weight: 850; + text-align: center; +} +.detail-panel { margin-bottom: 18px; } + +steep-job-list { display: grid; gap: 10px; } + +.job { + border: 1px solid var(--line); + border-radius: 14px; + background: var(--paper); + transition: border-color 140ms ease, background 140ms ease; +} +.job:hover { border-color: var(--green); } +.job[data-selected="true"] { border-color: var(--green); background: var(--green-pale); } + +.job-head { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: baseline; + gap: 10px; + padding: 13px 15px; + cursor: pointer; + list-style: none; +} +.job-head::-webkit-details-marker { display: none; } +.job-repo { font-weight: 700; font-size: 14px; overflow-wrap: anywhere; } +.job-pr { color: var(--green-dark); font-weight: 800; font-size: 13px; white-space: nowrap; } + +.job-info { display: grid; gap: 12px; padding: 0 15px 15px; } +.job-info dl { display: grid; gap: 6px; margin: 0; } +.job-info dl > div { display: grid; grid-template-columns: 74px 1fr; gap: 10px; } +.job-info dt { color: var(--muted); font-size: 12px; } +.job-info dd { margin: 0; font-size: 12px; overflow-wrap: anywhere; } +.job-watch { justify-self: start; } + +/* Chevron partagé par tous les
repliables. */ +.chev::before { color: var(--muted); content: "▸"; font-size: 11px; } +details[open] .chev::before { content: "▾"; } + +.empty { margin: 0; padding: 14px 2px; color: var(--muted); font-size: 14px; } + +.pill { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 800; + text-transform: lowercase; +} +.pill.done { background: var(--green-pale); color: var(--green-dark); } +.pill.failed { background: rgba(209, 102, 127, 0.16); color: var(--red); } + +.reviews { overflow-x: auto; } +.reviews-table { width: 100%; border-collapse: collapse; font-size: 13px; } +.reviews-table th { + padding: 0 12px 10px 0; + border-bottom: 1px solid var(--line); + color: var(--muted); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; + text-align: left; +} +.reviews-table td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--line); vertical-align: top; } +.reviews-table tr:last-child td { border-bottom: 0; } + +/* Journal d'activité d'un job */ +.log { + max-height: 360px; + overflow-y: auto; + padding: 4px 2px; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + font-size: 12.5px; + line-height: 1.6; +} +.log-entry { display: grid; grid-template-columns: auto 1fr; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--line); } +.log-entry:last-child { border-bottom: 0; } + +/* Résultat d'outil replié par défaut : le résumé tient sur une ligne, le corps + complet s'affiche au dépliage. */ +.log-result { padding: 6px 0; border-bottom: 1px solid var(--line); } +.log-result:last-child { border-bottom: 0; } +.log-result > summary { + display: grid; + grid-template-columns: auto auto 1fr; + gap: 10px; + align-items: baseline; + cursor: pointer; + list-style: none; +} +.log-result > summary::-webkit-details-marker { display: none; } +.log-result > .log-body { padding-top: 6px; } +.log-preview { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.log-tag { + align-self: start; + padding: 2px 8px; + border-radius: 7px; + background: var(--blue-pale); + color: var(--ink); + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} +.log-entry[data-kind="tool_call"] .log-tag { background: var(--green-pale); color: var(--green-dark); } +.log-entry[data-kind="finished"] .log-tag { background: var(--green-pale); color: var(--green-dark); } +.log-entry[data-kind="gap"] .log-tag { background: rgba(215, 163, 91, 0.18); color: var(--amber); } +.log-body { overflow-wrap: anywhere; white-space: pre-wrap; } +.log-args { color: var(--muted); } +.log-truncated { color: var(--amber); font-style: italic; } + +footer { padding: 26px 0; background: #0b233b; color: #b6c5d3; font-size: 13px; } +.footer-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; } +footer a { color: white; text-decoration: none; } +footer a:hover { color: #8be0bc; } +.footer-brand { color: white; font-weight: 800; letter-spacing: 0.1em; } + +:focus-visible { outline: 3px solid #35bb8e; outline-offset: 4px; } + +@media (max-width: 820px) { + .board { grid-template-columns: 1fr; } +} +@media (max-width: 600px) { + .container { width: min(var(--content), calc(100% - 32px)); } + .nav { min-height: 66px; gap: 12px; } + .nav-links { gap: 12px; } + .nav-links .nav-cta { display: none; } + .section { padding: 30px 0 62px; } + .toolbar { flex-direction: column; align-items: stretch; } + .footer-inner { align-items: flex-start; flex-direction: column; gap: 6px; } +} +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } +} diff --git a/front/favicon.png b/front/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..e7017a458d63236442d8ebdc522e839d686b6bd4 GIT binary patch literal 210651 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%0D-6Bv5K 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 literal 0 HcmV?d00001 diff --git a/front/index.html b/front/index.html new file mode 100644 index 0000000..02e90ff --- /dev/null +++ b/front/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + Steep — Dashboard + + + + + + +
+ +
+ +
+ +
+ + + + diff --git a/front/js/api.js b/front/js/api.js new file mode 100644 index 0000000..2f6ac45 --- /dev/null +++ b/front/js/api.js @@ -0,0 +1,78 @@ +// Accès aux routes exposées par le serveur axum et petits utilitaires partagés +// par les web components du dashboard. + +export const endpoints = { + jobs: "/jobs", + reviews: "/dedup", + lifecycle: "/events", + jobEvents: (id) => `/jobs/${id}/events`, +}; + +async function getJson(url) { + const response = await fetch(url, { headers: { accept: "application/json" } }); + if (!response.ok) { + throw new Error(`${url} → ${response.status}`); + } + return response.json(); +} + +export const fetchJobs = () => getJson(endpoints.jobs); +export const fetchReviews = () => getJson(endpoints.reviews); + +// Ouvre un flux SSE et route les évènements nommés vers les callbacks fournis. +// Les évènements du serveur sont nommés (`lifecycle`, `progress`, `gap`), donc +// `addEventListener("message")` ne suffit pas : il faut écouter par nom. +export function subscribe(url, handlers = {}) { + const source = new EventSource(url); + + const onData = (name, handler) => { + if (handler) { + source.addEventListener(name, (event) => handler(JSON.parse(event.data))); + } + }; + + if (handlers.onOpen) source.addEventListener("open", () => handlers.onOpen()); + if (handlers.onError) source.addEventListener("error", () => handlers.onError()); + onData("lifecycle", handlers.onLifecycle); + onData("progress", handlers.onProgress); + if (handlers.onGap) { + source.addEventListener("gap", (event) => handlers.onGap(Number(event.data))); + } + + return source; +} + +export function escapeHtml(value) { + return String(value ?? "").replace(/[&<>"']/g, (char) => ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + })[char]); +} + +// Horodatage relatif (« il y a 3 min »), tolérant sur les valeurs absentes. +export function relativeTime(value) { + if (!value) return "—"; + const then = Date.parse(value); + if (Number.isNaN(then)) return "—"; + + const seconds = Math.round((then - Date.now()) / 1000); + const steps = [ + ["year", 60 * 60 * 24 * 365], + ["month", 60 * 60 * 24 * 30], + ["day", 60 * 60 * 24], + ["hour", 60 * 60], + ["minute", 60], + ["second", 1], + ]; + + const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); + for (const [unit, size] of steps) { + if (Math.abs(seconds) >= size || unit === "second") { + return formatter.format(Math.round(seconds / size), unit); + } + } + return "—"; +} diff --git a/front/js/dashboard.js b/front/js/dashboard.js new file mode 100644 index 0000000..5829184 --- /dev/null +++ b/front/js/dashboard.js @@ -0,0 +1,212 @@ +// : assemble le tableau de bord. +// +// Au montage, il charge l'état initial via REST (`/jobs`, `/dedup`), puis +// s'abonne au flux global `/events` pour tenir les listes à jour en direct. +// La sélection d'un job ouvre son flux fin via . + +import { + endpoints, + escapeHtml, + fetchJobs, + fetchReviews, + relativeTime, + subscribe, +} from "./api.js"; +import "./theme-toggle.js"; +import "./job-list.js"; +import "./job-detail.js"; + +class Dashboard extends HTMLElement { + connectedCallback() { + this._jobs = { pending: [], processing: [] }; + this._reviews = []; + this._selectedId = null; + + this.render(); + this.refresh(); + this.connect(); + } + + disconnectedCallback() { + this._source?.close(); + if (this._refreshTimer) clearTimeout(this._refreshTimer); + } + + // ---- Chargement et flux ------------------------------------------------- + + async refresh() { + try { + const [jobs, reviews] = await Promise.all([fetchJobs(), fetchReviews()]); + this._jobs = { + pending: jobs.pending ?? [], + processing: jobs.processing ?? [], + }; + this._reviews = reviews.reviews ?? []; + this.update(); + } catch (error) { + console.error("failed to refresh the dashboard", error); + } + } + + connect() { + this._source = subscribe(endpoints.lifecycle, { + onLifecycle: (event) => this.applyLifecycle(event), + // En cas de gap (évènements ratés), on recharge l'état depuis le REST. + onGap: () => this.refresh(), + }); + } + + // Recharge la liste des reviews peu après une fin de job, pour récupérer les + // champs que l'évènement ne porte pas (markdown, coût, modèle). + scheduleRefresh() { + if (this._refreshTimer) clearTimeout(this._refreshTimer); + this._refreshTimer = setTimeout(() => this.refresh(), 400); + } + + applyLifecycle(event) { + switch (event.type) { + case "queued": + if (!this._jobs.pending.some((job) => job.id === event.id)) { + this._jobs.pending.push(this.jobFromEvent(event)); + } + break; + case "started": { + // On garde le job déjà connu s'il existe, pour conserver les champs que + // l'évènement `started` ne porte pas (event_id, created_at). + const known = this._jobs.pending.find((job) => job.id === event.id); + this.removeJob(event.id); + this._jobs.processing.push(known ?? this.jobFromEvent(event)); + break; + } + case "review_done": + case "failed": + this.removeJob(event.id); + this.scheduleRefresh(); + break; + case "skipped": + this.removeJob(event.id); + break; + default: + return; + } + this.update(); + } + + removeJob(id) { + this._jobs.pending = this._jobs.pending.filter((job) => job.id !== id); + this._jobs.processing = this._jobs.processing.filter((job) => job.id !== id); + } + + // L'évènement de cycle de vie porte assez de champs pour l'affichage ; le + // rafraîchissement REST fournira le reste (created_at, payload…). + jobFromEvent(event) { + return { + id: event.id, + event_id: event.event_id, + repo: event.repo, + pr: event.pr, + created_at: event.created_at, + }; + } + + // ---- Rendu -------------------------------------------------------------- + + render() { + this.innerHTML = ` +
+
+

Live operations

+

Review dashboard

+

Pending jobs, work in progress, and completed reviews — kept in sync over SSE.

+
+ +
+ +
+ +
+
+

Pending

0
+ +
+
+

In progress

0
+ +
+
+ +
+

Activity

+ +
+ +
+

Completed reviews

0
+
+
+
`; + + this._pending = this.querySelector("#pending"); + this._processing = this.querySelector("#processing"); + this._detail = this.querySelector("#detail"); + this._reviewsEl = this.querySelector("#reviews"); + + this.querySelector("#refresh").addEventListener("click", () => this.refresh()); + this.addEventListener("job-select", (event) => this.select(event.detail.id)); + + this.update(); + } + + update() { + this._pending.jobs = this._jobs.pending; + this._pending.selected = this._selectedId; + this._processing.jobs = this._jobs.processing; + this._processing.selected = this._selectedId; + + this.querySelector("#pending-count").textContent = String(this._jobs.pending.length); + this.querySelector("#processing-count").textContent = String(this._jobs.processing.length); + this.querySelector("#reviews-count").textContent = String(this._reviews.length); + + this._reviewsEl.innerHTML = this.reviewsHtml(); + } + + select(id) { + this._selectedId = id; + this._detail.jobId = id; + this.update(); + this._detail.scrollIntoView({ behavior: "smooth", block: "nearest" }); + } + + reviewsHtml() { + if (this._reviews.length === 0) { + return `

No review completed yet.

`; + } + + const rows = [...this._reviews] + .sort((a, b) => String(b.completed_at ?? "").localeCompare(String(a.completed_at ?? ""))) + .map( + (review) => ` + + ${review.ok ? "done" : "failed"} + ${escapeHtml(review.repo)}#${review.pr} + ${review.comments} + ${review.duration_ms} ms + ${escapeHtml(review.model ?? "—")} + ${relativeTime(review.completed_at)} + `, + ) + .join(""); + + return ` + + + + + + + ${rows} +
StatusPull requestCommentsDurationModelCompleted
`; + } +} + +customElements.define("steep-dashboard", Dashboard); diff --git a/front/js/job-card.js b/front/js/job-card.js new file mode 100644 index 0000000..6853d4d --- /dev/null +++ b/front/js/job-card.js @@ -0,0 +1,61 @@ +// Carte d'un job. Le détail (id, event, date) est replié par défaut ; le +// bouton « Watch activity » remonte un évènement `job-select` que le dashboard +// écoute pour ouvrir le flux d'activité du job. + +import { escapeHtml, relativeTime } from "./api.js"; + +class JobCard extends HTMLElement { + set job(value) { + this._job = value; + this.render(); + } + + get job() { + return this._job; + } + + set selected(value) { + this._selected = Boolean(value); + this.render(); + } + + connectedCallback() { + this.render(); + } + + render() { + if (!this._job) return; + const job = this._job; + + // Préserve l'état replié/déplié à travers les re-rendus (mises à jour SSE). + const open = this._details?.open ? " open" : ""; + + this.innerHTML = ` +
+ + + ${escapeHtml(job.repo)} + #${job.pr} + +
+
+
Job
${job.id}
+ ${job.event_id != null ? `
Event
${job.event_id}
` : ""} +
Created
${escapeHtml(relativeTime(job.created_at))}
+
+ +
+
`; + + this._details = this.querySelector("details"); + this.querySelector(".job-watch").addEventListener("click", () => this.select()); + } + + select() { + this.dispatchEvent( + new CustomEvent("job-select", { detail: { id: this._job.id }, bubbles: true }), + ); + } +} + +customElements.define("steep-job-card", JobCard); diff --git a/front/js/job-detail.js b/front/js/job-detail.js new file mode 100644 index 0000000..abe149e --- /dev/null +++ b/front/js/job-detail.js @@ -0,0 +1,150 @@ +// Panneau d'activité d'un job : ouvre un flux SSE vers `/jobs/{id}/events`, +// rejoue l'historique renvoyé par le serveur puis affiche le direct. + +import { endpoints, escapeHtml, subscribe } from "./api.js"; + +class JobDetail extends HTMLElement { + set jobId(value) { + const id = value ?? null; + if (this._jobId === id) return; + this._jobId = id; + this.open(); + } + + connectedCallback() { + if (!this._jobId) this.renderPlaceholder(); + } + + disconnectedCallback() { + this.close(); + } + + close() { + if (this._source) { + this._source.close(); + this._source = null; + } + } + + open() { + this.close(); + this._finished = false; + + if (!this._jobId) { + this.renderPlaceholder(); + return; + } + + this.innerHTML = ` +
`; + this._log = this.querySelector("#log"); + + this._source = subscribe(endpoints.jobEvents(this._jobId), { + onProgress: (event) => { + this.append(event); + if (event.type === "finished") { + this._finished = true; + this.close(); + } + }, + onGap: (skipped) => + this.append({ type: "gap", note: `missed ${skipped} events, refresh to resync` }), + // Un `error` survient aussi quand le serveur ferme le flux (job terminé). + // On ferme alors la source pour éviter la boucle de reconnexion (le job + // n'est plus en cours, la route renverrait 404). + onError: () => { + if (this._finished) return; + this.append({ type: "gap", note: "stream closed" }); + this.close(); + }, + }); + } + + renderPlaceholder() { + this.innerHTML = `

Select a job in progress to watch the agent work.

`; + } + + append(event) { + if (!this._log) return; + this._log.append(this.entry(event)); + this._log.scrollTop = this._log.scrollHeight; + } + + entry(event) { + return event.type === "tool_result" ? this.resultEntry(event) : this.lineEntry(event); + } + + // Ligne simple : appel d'outil, fin de review, gap. + lineEntry(event) { + const row = document.createElement("div"); + row.className = "log-entry"; + row.dataset.kind = event.type; + + const { tag, body } = this.describe(event); + + const tagEl = document.createElement("span"); + tagEl.className = "log-tag"; + tagEl.textContent = tag; + + const bodyEl = document.createElement("span"); + bodyEl.className = "log-body"; + bodyEl.innerHTML = body; + + row.append(tagEl, bodyEl); + return row; + } + + // Résultat d'outil : replié par défaut, avec un aperçu sur une ligne. + resultEntry(event) { + const details = document.createElement("details"); + details.className = "log-result"; + + const summary = document.createElement("summary"); + const chev = document.createElement("span"); + chev.className = "chev"; + chev.setAttribute("aria-hidden", "true"); + const tag = document.createElement("span"); + tag.className = "log-tag"; + tag.textContent = "result"; + const preview = document.createElement("span"); + preview.className = "log-preview"; + preview.textContent = this.preview(event.output); + summary.append(chev, tag, preview); + + const body = document.createElement("div"); + body.className = "log-body"; + const truncated = event.truncated + ? ' (truncated)' + : ""; + body.innerHTML = `${escapeHtml(event.output)}${truncated}`; + + details.append(summary, body); + return details; + } + + preview(text) { + const line = String(text ?? "").replace(/\s+/g, " ").trim(); + return line.length > 80 ? `${line.slice(0, 80)}…` : line; + } + + describe(event) { + switch (event.type) { + case "tool_call": + return { + tag: "call", + body: `${escapeHtml(event.name)} ${escapeHtml(event.arguments)}`, + }; + case "finished": + return { + tag: "done", + body: event.ok ? "review finished" : "review failed", + }; + case "gap": + return { tag: "gap", body: escapeHtml(event.note ?? "gap") }; + default: + return { tag: escapeHtml(event.type ?? "event"), body: "" }; + } + } +} + +customElements.define("steep-job-detail", JobDetail); diff --git a/front/js/job-list.js b/front/js/job-list.js new file mode 100644 index 0000000..f79221c --- /dev/null +++ b/front/js/job-list.js @@ -0,0 +1,58 @@ +// Liste de jobs (une colonne du tableau). Reçoit un tableau de jobs via la +// propriété `jobs` et l'id sélectionné via `selected`. + +import "./job-card.js"; + +class JobList extends HTMLElement { + set jobs(value) { + this._jobs = Array.isArray(value) ? value : []; + this.render(); + } + + get jobs() { + return this._jobs ?? []; + } + + set selected(value) { + this._selected = value; + this.render(); + } + + connectedCallback() { + if (this._mounted) return; + this._mounted = true; + this.render(); + } + + render() { + const jobs = this.jobs; + + if (jobs.length === 0) { + this.replaceChildren(); + const empty = document.createElement("p"); + empty.className = "empty"; + empty.textContent = this.getAttribute("empty") || "Nothing here."; + this.append(empty); + return; + } + + // Réconciliation par id : on réutilise les cartes existantes pour préserver + // l'état replié/déplié de chacune à travers les mises à jour. + const known = new Map(); + for (const card of this.querySelectorAll("steep-job-card")) { + if (card.job) known.set(card.job.id, card); + } + + for (const job of jobs) { + const card = known.get(job.id) ?? document.createElement("steep-job-card"); + known.delete(job.id); + card.job = job; + card.selected = job.id === this._selected; + this.append(card); + } + + for (const card of known.values()) card.remove(); + } +} + +customElements.define("steep-job-list", JobList); diff --git a/front/js/theme-toggle.js b/front/js/theme-toggle.js new file mode 100644 index 0000000..774ec53 --- /dev/null +++ b/front/js/theme-toggle.js @@ -0,0 +1,59 @@ +// Bascule de thème, identique à celle du site public (même clé localStorage, +// même rendu), empaquetée en web component. + +const STORAGE_KEY = "steep-theme"; + +export function currentTheme() { + return document.documentElement.dataset.theme === "light" ? "light" : "dark"; +} + +export function applyTheme(theme, persist = true) { + document.documentElement.dataset.theme = theme; + + const meta = document.querySelector('meta[name="theme-color"]'); + if (meta) meta.content = theme === "dark" ? "#0b1520" : "#f7f8f5"; + + if (persist) { + try { + localStorage.setItem(STORAGE_KEY, theme); + } catch (_) { + // stockage indisponible : on garde juste le thème en mémoire. + } + } + + window.dispatchEvent(new CustomEvent("steep:theme", { detail: theme })); +} + +class ThemeToggle extends HTMLElement { + connectedCallback() { + this.innerHTML = ` + `; + + this._button = this.querySelector("button"); + this._icon = this.querySelector(".theme-icon"); + this._label = this.querySelector(".theme-label"); + this._onTheme = () => this.render(); + + this._button.addEventListener("click", () => { + applyTheme(currentTheme() === "dark" ? "light" : "dark"); + }); + window.addEventListener("steep:theme", this._onTheme); + + this.render(); + } + + disconnectedCallback() { + window.removeEventListener("steep:theme", this._onTheme); + } + + render() { + const theme = currentTheme(); + this._icon.textContent = theme === "dark" ? "☾" : "☀"; + this._label.textContent = theme === "dark" ? "Dark theme" : "Light theme"; + this._button.setAttribute("aria-checked", String(theme === "dark")); + } +} + +customElements.define("steep-theme-toggle", ThemeToggle); diff --git a/public/index.html b/public/index.html index a7670af..c29f29e 100644 --- a/public/index.html +++ b/public/index.html @@ -519,9 +519,11 @@
  • 1.2Sandboxed pull-request reviews.
  • 1.2.1A default Dev Container when a repository has no configuration.
  • -
  • 1.3Type-safe routing between review and implementation requests.
  • -
  • 2.0Implement requested changes from issues or existing PRs, then create or update a PR automatically.
  • +
  • 1.3Durable reviews: persist jobs in PostgreSQL, list them by state (running, pending, review done), and stream live updates over SSE.
  • +
  • 1.4Configuration from a YAML file, replacing part of the environment variables.
  • +
  • 2.0Type-safe action routing between reviews and implementation requests, implement requested changes from issues or existing PRs, then create or update a PR automatically.
  • 2.1Project instructions from AGENTS.md and separate review/implementation models.
  • +
  • 2.2Sub-agents that take on well-scoped parts of a task and run in parallel.
  • 3.0Automatically investigate and fix project errors reported by Sentry.
diff --git a/scripts/migrate-apply.sh b/scripts/migrate-apply.sh new file mode 100755 index 0000000..5dff092 --- /dev/null +++ b/scripts/migrate-apply.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -f "${ROOT_DIR}/.env" ]; then + set -a + # shellcheck disable=SC1091 + . "${ROOT_DIR}/.env" + set +a +fi + +cd "${ROOT_DIR}" + +if [ -x "${ROOT_DIR}/steep-server" ]; then + exec "${ROOT_DIR}/steep-server" migration apply "$@" +else + exec cargo run -p steep-server -- migration apply "$@" +fi diff --git a/scripts/migrate-generate.sh b/scripts/migrate-generate.sh new file mode 100755 index 0000000..bbce364 --- /dev/null +++ b/scripts/migrate-generate.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ -f "${ROOT_DIR}/.env" ]; then + set -a + . "${ROOT_DIR}/.env" + set +a +fi + +cd "${ROOT_DIR}" + +if [ "$#" -eq 0 ]; then + echo "Usage: $0 " >&2 + echo "Exemple: $0 add_users_table" >&2 + exit 1 +fi + +NAME="$1" + +exec cargo run -p steep-server -- migration generate --name "${NAME}" From 3078ba570582302869d203409b2afa9a326ea483 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 13 Oct 2026 19:57:24 +0000 Subject: [PATCH 4/8] clean project structure --- .devcontainer/Dockerfile | 1 - .woodpecker/tests.yml | 6 + Cargo.lock | 108 +- Cargo.toml | 24 +- Containerfile | 3 - README.md | 20 +- crates/devcontainer-rs/Cargo.toml | 7 +- crates/devcontainer-rs/src/consts.rs | 17 +- crates/devcontainer-rs/src/container.rs | 22 +- crates/devcontainer-rs/src/context.rs | 28 +- crates/devcontainer-rs/src/devcontainer.rs | 18 +- crates/devcontainer-rs/src/errors.rs | 11 +- crates/devcontainer-rs/src/exec.rs | 6 +- crates/devcontainer-rs/src/lib.rs | 37 +- crates/devcontainer-rs/src/path.rs | 6 +- crates/devcontainer-rs/src/runtime.rs | 50 +- crates/devcontainer-rs/src/schema.rs | 32 +- crates/steep-server/Cargo.toml | 4 +- crates/steep-server/src/api.rs | 24 +- crates/steep-server/src/bot.rs | 291 ++-- crates/steep-server/src/bot_actions/review.rs | 1192 ----------------- .../src/bot_actions/review/anchor.rs | 177 +++ .../src/bot_actions/review/diff.rs | 389 ++++++ .../src/bot_actions/review/fixtures.rs | 119 ++ .../src/bot_actions/review/mod.rs | 210 +++ .../src/bot_actions/review/prompt.rs | 125 ++ .../src/bot_actions/review/render.rs | 110 ++ crates/steep-server/src/db.rs | 62 +- crates/steep-server/src/domain/mod.rs | 1 + crates/steep-server/src/domain/review.rs | 247 ++++ crates/steep-server/src/env.rs | 129 +- crates/steep-server/src/event_bus.rs | 4 +- crates/steep-server/src/gitea.rs | 48 +- crates/steep-server/src/job_queue.rs | 236 ++-- crates/steep-server/src/main.rs | 54 +- crates/steep-server/src/open_router.rs | 11 +- .../src/{dedup.rs => review_store.rs} | 57 +- crates/steep-server/src/sandbox/agent.rs | 10 +- crates/steep-server/src/sandbox/instance.rs | 36 +- crates/steep-server/src/sandbox/tools.rs | 12 +- crates/steep-server/src/schemas/review.rs | 12 +- crates/steep-server/src/state.rs | 6 +- deny.toml | 53 + front/js/api.js | 2 +- front/js/dashboard.js | 2 +- 45 files changed, 2139 insertions(+), 1880 deletions(-) delete mode 100644 crates/steep-server/src/bot_actions/review.rs create mode 100644 crates/steep-server/src/bot_actions/review/anchor.rs create mode 100644 crates/steep-server/src/bot_actions/review/diff.rs create mode 100644 crates/steep-server/src/bot_actions/review/fixtures.rs create mode 100644 crates/steep-server/src/bot_actions/review/mod.rs create mode 100644 crates/steep-server/src/bot_actions/review/prompt.rs create mode 100644 crates/steep-server/src/bot_actions/review/render.rs create mode 100644 crates/steep-server/src/domain/mod.rs create mode 100644 crates/steep-server/src/domain/review.rs rename crates/steep-server/src/{dedup.rs => review_store.rs} (64%) create mode 100644 deny.toml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 585d054..1b950a1 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -11,7 +11,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ git \ build-essential \ - libssl-dev \ cmake \ pkg-config \ clang \ diff --git a/.woodpecker/tests.yml b/.woodpecker/tests.yml index e1eda5f..f05e763 100644 --- a/.woodpecker/tests.yml +++ b/.woodpecker/tests.yml @@ -23,6 +23,12 @@ steps: - rustup component add clippy - cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: deny + image: rust:1.99 + commands: + - cargo install cargo-deny --locked --version 0.18.9 + - cargo deny check + - name: wait-for-postgres image: postgres:18 commands: diff --git a/Cargo.lock b/Cargo.lock index 49775d3..1a2406d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1392,21 +1392,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1811,18 +1796,17 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "hyper-rustls" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ - "bytes", - "http-body-util", + "http 1.5.0", "hyper", "hyper-util", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", ] @@ -2579,23 +2563,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.31.3" @@ -2856,49 +2823,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_info" version = "3.15.0" @@ -3444,19 +3374,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-tls", + "hyper-rustls", "hyper-util", "js-sys", "log", - "native-tls", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", "tower-http 0.6.11", @@ -3604,6 +3535,7 @@ version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -3759,8 +3691,8 @@ checksum = "76a88b65feb368d9dde5d531a2b59b25016e36fd6e4978432371a3ee77746ca2" dependencies = [ "cfg_aliases", "httpdate", - "native-tls", "reqwest", + "rustls", "sentry-actix", "sentry-backtrace", "sentry-contexts", @@ -4281,6 +4213,7 @@ dependencies = [ "metrics-exporter-prometheus", "reqwest", "ring", + "rustls", "sentry", "sentry-anyhow", "serde", @@ -4721,16 +4654,6 @@ dependencies = [ "syn 3.0.4", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-postgres" version = "0.7.18" @@ -5318,14 +5241,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ "base64 0.23.1", - "der 0.8.2", "log", - "native-tls", "percent-encoding", + "rustls", "rustls-pki-types", "ureq-proto", "utf8-zero", - "webpki-root-certs", + "webpki-roots", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 26e20b9..e5e4798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,31 @@ members = [ resolver = "3" [workspace.dependencies] -reqwest = { version = "0.13", default-features = false, features = ["json", "native-tls", "stream"] } +# TLS is pure Rust (rustls + ring): no libssl is needed at build or run time. +# `rustls-no-provider` leaves the crypto provider to the binary, which installs +# `ring` at startup (see `main`), so that a single provider is ever compiled in. +reqwest = { version = "0.13", default-features = false, features = ["json", "rustls-no-provider", "stream"] } +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } tokio = { version = "1.53", features = ["full"] } tokio-stream = "0.1" tokio-util = "0.7" futures-util = "0.3" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -sentry = { version = "0.49", features = ["tower-axum-matched-path"] } +# Sentry's default `transport` feature pulls `native-tls`; the same set of features +# is spelled out here with rustls instead. +sentry = { version = "0.49", default-features = false, features = [ + "backtrace", + "contexts", + "debug-images", + "logs", + "metrics", + "panic", + "release-health", + "reqwest", + "rustls-no-provider", + "tower-axum-matched-path", +] } sentry-anyhow = { version = "0.49", features = ["backtrace"] } dotenvy = "0.15" tower = "0.5" @@ -31,6 +48,9 @@ metrics-exporter-prometheus = { version = "0.18", default-features = false, feat toasty = { version = "0.11", features = ["postgresql", "serde", "jiff"] } toasty-cli = "0.11" jiff = { version = "0.2", features = ["serde"] } +tempfile = "3" +bollard = "0.21" +tar = "0.4" [profile.release] debug = 1 diff --git a/Containerfile b/Containerfile index ea5eacf..bda6797 100644 --- a/Containerfile +++ b/Containerfile @@ -14,9 +14,6 @@ FROM debian:trixie-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ git \ - libssl3t64 \ - libzstd1 \ - zlib1g \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 45f19b5..44cd51a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Steep reads its configuration from environment variables (a `.env` file is suppo | `METRICS_BIND_ADDR` | *(optional)* Bind address for the Prometheus metrics endpoint (e.g. `0.0.0.0:9100`). If unset, the metrics exporter is disabled. | | `SENTRY_DSN` | *(optional)* Sentry DSN for error tracking | | `RUST_LOG` | *(optional)* Log level, defaults to `info` | -| `SANDBOX_MAX_ITERATIONS` | *(optional)* Maximum number of tool-calling iterations per sandboxed review. Defaults to `8` | +| `SANDBOX_MAX_ITERATIONS` | *(optional)* Maximum number of tool-calling iterations per sandboxed review. Defaults to `8`; a value that is set but is not a number stops the startup | | `DOCKER_HOST` | *(optional)* Container daemon socket Steep drives, e.g. `unix:///run/user/1000/podman/podman.sock` for a rootless podman. Defaults to `unix:///var/run/docker.sock` | ## Sandboxed reviews @@ -139,6 +139,24 @@ cargo run -p steep-server Copy `.env.example` to `.env` and fill in your values before running. +## Job queue + +Steep is meant to run as a **single instance**. Webhooks are stored in the `jobs` table +of PostgreSQL, and the worker is woken up as soon as a job is pushed (no polling). +A job still `processing` at startup was interrupted by the previous process: it is put +back in the queue and reviewed again. Do not run two instances on the same database, +they would both take over each other's running jobs. + +## Dependencies + +TLS is handled by [rustls](https://github.com/rustls/rustls) (with `ring`), so neither +the build nor the image needs OpenSSL. The `Containerfile` runtime image only +installs `ca-certificates` and `git`. + +`cargo deny check` audits the dependencies (security advisories, licenses, banned +crates, sources) with the policy of `deny.toml`. It runs in the CI, and it also fails +if `openssl-sys` or `native-tls` comes back into the dependency tree. + ## Metrics Steep optionally exposes a Prometheus metrics endpoint, useful for scraping with an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) or any Prometheus-compatible scraper. diff --git a/crates/devcontainer-rs/Cargo.toml b/crates/devcontainer-rs/Cargo.toml index 883b958..6b3f3bf 100644 --- a/crates/devcontainer-rs/Cargo.toml +++ b/crates/devcontainer-rs/Cargo.toml @@ -2,12 +2,13 @@ name = "devcontainer-rs" version = "0.1.0" edition = "2024" +publish = false [dependencies] -bollard = "0.21" +bollard = { workspace = true } bytes = { workspace = true } futures-util = { workspace = true } -tar = "0.4" +tar = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tracing = { workspace = true } @@ -17,4 +18,4 @@ anyhow = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -tempfile = "3" +tempfile = { workspace = true } diff --git a/crates/devcontainer-rs/src/consts.rs b/crates/devcontainer-rs/src/consts.rs index 9aceac6..4e95d70 100644 --- a/crates/devcontainer-rs/src/consts.rs +++ b/crates/devcontainer-rs/src/consts.rs @@ -1,25 +1,24 @@ -//! Constantes de la crate : endpoint du daemon, timeouts et découpage du -//! contexte de build. +//! Crate constants: daemon endpoint, timeouts and build context chunking. use std::time::Duration; -/// Endpoint affiché dans les logs quand `DOCKER_HOST` n'est pas défini. +/// Endpoint shown in the logs when `DOCKER_HOST` is not set. pub(crate) const DEFAULT_ENDPOINT: &str = "unix:///var/run/docker.sock"; -/// Timeout appliqué aux opérations de build/run/stop/remove. +/// Timeout applied to build/run/stop/remove operations. pub(crate) const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(600); -/// Timeout appliqué aux commandes exécutées dans un container en cours d'exécution. +/// Timeout applied to commands run inside a running container. pub(crate) const DEFAULT_EXEC_TIMEOUT: Duration = Duration::from_secs(60); -/// Taille des morceaux du contexte de build envoyés au daemon. +/// Size of the build context chunks sent to the daemon. pub(crate) const CONTEXT_CHUNK_SIZE: usize = 64 * 1024; -/// Nombre de morceaux pouvant attendre dans le canal : c'est ce qui borne la -/// mémoire occupée par le contexte, quelle que soit sa taille. +/// Number of chunks that can wait in the channel: this is what bounds the +/// memory held by the context, regardless of its size. pub(crate) const CONTEXT_CHUNKS: usize = 4; -/// Lectures successives du code de sortie d'un exec, et attente entre elles. +/// Successive reads of an exec's exit code, and the delay between them. pub(crate) const EXIT_CODE_ATTEMPTS: usize = 10; pub(crate) const EXIT_CODE_DELAY: Duration = Duration::from_millis(20); diff --git a/crates/devcontainer-rs/src/container.rs b/crates/devcontainer-rs/src/container.rs index 7236af8..cf56fdd 100644 --- a/crates/devcontainer-rs/src/container.rs +++ b/crates/devcontainer-rs/src/container.rs @@ -1,4 +1,4 @@ -//! Un devcontainer en cours d'exécution : inspection et exécution de commandes. +//! A running devcontainer: inspection and command execution. use std::time::Duration; @@ -7,7 +7,7 @@ use crate::{ runtime::ContainerRuntime, }; -/// Un devcontainer en cours d'exécution. +/// A running devcontainer. #[derive(Debug, Clone)] pub struct Container { runtime: ContainerRuntime, @@ -19,8 +19,8 @@ pub struct Container { } impl Container { - /// Assemble un container démarré, avec son réseau et, le cas échéant, l'image - /// construite pour lui et à nettoyer. + /// Assembles a started container, with its network and, if any, the image + /// built for it that must be cleaned up. pub(crate) fn new( runtime: ContainerRuntime, name: String, @@ -47,10 +47,10 @@ impl Container { &self.workspace_folder } - /// Exécute une commande dans le container et renvoie sa sortie. + /// Runs a command in the container and returns its output. /// - /// La commande est transmise sous forme de vecteur d'arguments (argv, sans shell), - /// donc aucun échappement ni interpolation n'est effectué. + /// The command is passed as an argument vector (argv, no shell), so no + /// escaping or interpolation is performed. pub async fn exec(&self, cmd: &[&str]) -> Result { self.exec_with_timeout(cmd, DEFAULT_EXEC_TIMEOUT).await } @@ -65,19 +65,19 @@ impl Container { .await } - /// Exécute un script shell dans le container via `sh -c`. + /// Runs a shell script in the container via `sh -c`. pub async fn exec_shell(&self, script: &str) -> Result { self.exec(&["sh", "-c", script]).await } - /// Arrête le container. + /// Stops the container. pub async fn stop(&self) -> Result<(), ContainerError> { self.runtime.stop_container(&self.name).await } - /// Supprime le container, son réseau et son image. + /// Removes the container, its network and its image. /// - /// La suppression du réseau et de l'image est best-effort : ils peuvent déjà être absents. + /// Removing the network and the image is best-effort: they may already be gone. pub async fn remove(&self) -> Result<(), ContainerError> { self.runtime.remove_container(&self.name).await?; diff --git a/crates/devcontainer-rs/src/context.rs b/crates/devcontainer-rs/src/context.rs index 8c80d46..f964d7c 100644 --- a/crates/devcontainer-rs/src/context.rs +++ b/crates/devcontainer-rs/src/context.rs @@ -1,4 +1,4 @@ -//! Empaquetage en flux du contexte de build envoyé au daemon. +//! Streaming packaging of the build context sent to the daemon. use std::{ io::{BufWriter, Write}, @@ -12,13 +12,13 @@ use tokio_stream::wrappers::ReceiverStream; use crate::consts::{CONTEXT_CHUNK_SIZE, CONTEXT_CHUNKS}; -/// Empaquette le contexte de build dans un tar, **en flux**. +/// Packs the build context into a tar, **as a stream**. /// -/// Le CLI `docker build` empaquette le contexte puis l'envoie ; le faire d'un coup -/// chargerait tout le dossier en mémoire, ce qui devient intenable dès que le -/// `.devcontainer` embarque des binaires. Ici l'archive est écrite par une tâche -/// bloquante et poussée morceau par morceau, avec la contre-pression du canal : -/// si le daemon lit lentement, l'empaquetage ralentit. +/// The `docker build` CLI packs the context and then sends it; doing that in one go +/// would load the whole folder into memory, which becomes untenable as soon as the +/// `.devcontainer` ships binaries. Here the archive is written by a blocking task +/// and pushed chunk by chunk, with the channel's backpressure: if the daemon reads +/// slowly, packing slows down. pub(crate) fn tar_directory_stream( dir: &Path, ) -> impl Stream> + Send + 'static { @@ -33,13 +33,13 @@ pub(crate) fn tar_directory_stream( let result = builder .append_dir_all(".", &dir) .and_then(|()| builder.finish()) - // `finish` écrit les blocs de fin d'archive ; il reste à vider le tampon - // pour que le daemon reçoive tout, y compris une archive vide. + // `finish` writes the end-of-archive blocks; the buffer still has to be + // flushed so the daemon receives everything, even an empty archive. .and_then(|()| builder.get_mut().flush()); if let Err(error) = result { - // L'échec est remonté au daemon par le flux : sinon il ne verrait qu'un - // tar tronqué et signalerait une erreur incompréhensible. + // The failure is reported to the daemon through the stream: otherwise it + // would only see a truncated tar and report a confusing error. let _ = failures.blocking_send(Err(error)); } }); @@ -47,9 +47,9 @@ pub(crate) fn tar_directory_stream( ReceiverStream::new(receiver) } -/// Écrit les morceaux du tar dans un canal, en attendant qu'il se vide. +/// Writes the tar chunks into a channel, waiting for it to drain. /// -/// Utilisé depuis une tâche bloquante, seul contexte où `blocking_send` est autorisé. +/// Used from a blocking task, the only context where `blocking_send` is allowed. struct ChannelWriter { sender: mpsc::Sender>, } @@ -80,7 +80,7 @@ mod tests { use futures_util::StreamExt; - /// Rassemble le flux du contexte en un tar complet, pour l'inspecter. + /// Collects the context stream into a complete tar, for inspection. async fn context_tar(dir: &Path) -> Vec { let mut tar = Vec::new(); let mut chunks = tar_directory_stream(dir); diff --git a/crates/devcontainer-rs/src/devcontainer.rs b/crates/devcontainer-rs/src/devcontainer.rs index 4d59d61..a543720 100644 --- a/crates/devcontainer-rs/src/devcontainer.rs +++ b/crates/devcontainer-rs/src/devcontainer.rs @@ -1,5 +1,5 @@ -//! Représentation analysée d'un `devcontainer.json` et son cycle de vie : -//! nommage, build, démarrage et hooks. +//! Parsed representation of a `devcontainer.json` and its lifecycle: naming, +//! build, startup and hooks. use std::{ collections::HashMap, @@ -29,29 +29,29 @@ pub struct DevContainer { } impl DevContainer { - /// Dossier de workspace dans le container, par défaut `/workspace`. + /// Workspace folder inside the container, `/workspace` by default. pub fn workspace_folder(&self) -> String { self.workspace_folder .clone() .unwrap_or_else(|| "/workspace".to_string()) } - /// Tag de l'image construite localement à partir du Dockerfile. + /// Tag of the image built locally from the Dockerfile. fn local_image_tag(&self, id: &str) -> String { format!("devcontainer-rs/{}:{}", self.sanitized_name(), id) } - /// Nom du container de cette sandbox. + /// Name of this sandbox's container. fn container_name(&self, id: &str) -> String { format!("devcontainer-rs-{}-{}", self.sanitized_name(), id) } - /// Nom du devcontainer nettoyé pour servir de nom d'image/container. + /// Devcontainer name, sanitized to be usable as an image/container name. fn sanitized_name(&self) -> String { sanitize(self.name.as_deref().unwrap_or("devcontainer")) } - /// Construit l'image devcontainer sous `image_tag`. + /// Builds the devcontainer image as `image_tag`. async fn build( &self, runtime: &ContainerRuntime, @@ -239,7 +239,7 @@ impl Default for DevContainer { } } -/// Nettoie une chaîne pour qu'elle puisse servir de nom d'image/container docker. +/// Sanitizes a string so it can be used as a docker image/container name. fn sanitize(input: &str) -> String { let sanitized: String = input .chars() @@ -260,7 +260,7 @@ fn sanitize(input: &str) -> String { } } -/// Identifiant unique à une exécution de sandbox, combinant l'identifiant de processus et un horodatage. +/// Identifier unique to a sandbox run, combining the process id and a timestamp. fn unique_id() -> String { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/crates/devcontainer-rs/src/errors.rs b/crates/devcontainer-rs/src/errors.rs index b6a451d..5968bb8 100644 --- a/crates/devcontainer-rs/src/errors.rs +++ b/crates/devcontainer-rs/src/errors.rs @@ -1,5 +1,4 @@ -//! Erreurs de la crate : échecs du daemon de containers et échecs d'analyse du -//! `devcontainer.json`. +//! Crate errors: container daemon failures and `devcontainer.json` parsing failures. use std::{path::PathBuf, time::Duration}; @@ -7,25 +6,25 @@ use bollard::errors::Error as BollardError; #[derive(Debug, thiserror::Error)] pub enum ContainerError { - /// Le daemon est injoignable, ou a refusé une requête. + /// The daemon is unreachable, or rejected a request. #[error("container daemon request failed: {source}")] Request { #[from] source: BollardError, }, - /// Une opération a dépassé son timeout. + /// An operation exceeded its timeout. #[error("`{operation}` timed out after {timeout:?}")] Timeout { operation: String, timeout: Duration, }, - /// La construction de l'image a échoué. + /// The image build failed. #[error("image build failed: {message}")] Build { message: String }, - /// Le daemon a répondu autre chose que ce qui était attendu. + /// The daemon replied with something other than what was expected. #[error("{0}")] Unexpected(String), } diff --git a/crates/devcontainer-rs/src/exec.rs b/crates/devcontainer-rs/src/exec.rs index 58d2615..d1fc254 100644 --- a/crates/devcontainer-rs/src/exec.rs +++ b/crates/devcontainer-rs/src/exec.rs @@ -1,9 +1,9 @@ -//! Résultat d'une commande exécutée dans un container. +//! Result of a command run in a container. -/// Résultat d'une commande exécutée dans un container. +/// Result of a command run in a container. #[derive(Debug, Clone)] pub struct ExecOutput { - /// Code de sortie, ou `-1` si le processus a été terminé par un signal. + /// Exit code, or `-1` if the process was terminated by a signal. pub status: i32, pub stdout: String, pub stderr: String, diff --git a/crates/devcontainer-rs/src/lib.rs b/crates/devcontainer-rs/src/lib.rs index b0ccb44..fe2050f 100644 --- a/crates/devcontainer-rs/src/lib.rs +++ b/crates/devcontainer-rs/src/lib.rs @@ -1,28 +1,27 @@ -//! Primitives de cycle de vie de container pour un [`DevContainer`] analysé. +//! Container lifecycle primitives for a parsed [`DevContainer`]. //! -//! Cette crate pilote l'API du daemon de containers pour préparer l'image -//! devcontainer (construite depuis un Dockerfile, ou tirée d'un registre), -//! démarrer un container et y copier le workspace, exécuter les -//! hooks `postCreateCommand` / `postStartCommand` et lancer des commandes à -//! l'intérieur du container en cours d'exécution. +//! This crate drives the container daemon API to prepare the devcontainer image +//! (built from a Dockerfile, or pulled from a registry), start a container and +//! copy the workspace into it, run the `postCreateCommand` / `postStartCommand` +//! hooks, and run commands inside the running container. //! -//! Le daemon est joint par son **socket** (celui de l'hôte, monté dans le -//! container de Steep) : [`ContainerRuntime::connect`] suit `DOCKER_HOST` comme -//! le fait le CLI `docker`, et retombe sur le socket local. Aucun binaire de -//! runtime n'est donc requis dans l'image, et `podman` fonctionne de la même -//! façon dès lors qu'il expose son socket compatible Docker. +//! The daemon is reached through its **socket** (the host's, mounted into the +//! Steep container): [`ContainerRuntime::connect`] follows `DOCKER_HOST` like the +//! `docker` CLI does, and falls back to the local socket. No runtime binary is +//! therefore required in the image, and `podman` works the same way as long as it +//! exposes its Docker-compatible socket. //! //! # Isolation //! -//! Chaque sandbox dispose de son propre tag d'image, de son propre container et de -//! son propre réseau. Le container démarre attaché à ce réseau afin que les hooks -//! `postCreateCommand` / `postStartCommand` puissent récupérer des dépendances -//! (par ex. `npm install`) ; une fois les hooks exécutés, le container est déconnecté -//! du réseau pour le reste de sa durée de vie. Chaque commande est bornée par un timeout. +//! Each sandbox has its own image tag, its own container and its own network. The +//! container starts attached to that network so that the `postCreateCommand` / +//! `postStartCommand` hooks can fetch dependencies (e.g. `npm install`); once the +//! hooks have run, the container is disconnected from the network for the rest of +//! its lifetime. Every command is bounded by a timeout. //! -//! Les `runArgs` du `devcontainer.json` sont ignorés : ils viennent du dépôt, donc -//! d'une pull request non fiable, et pourraient rattacher le container à un autre -//! réseau ou lui donner des privilèges qui annuleraient cette isolation. +//! The `devcontainer.json` `runArgs` are ignored: they come from the repository, +//! hence from an untrusted pull request, and could attach the container to another +//! network or grant it privileges that would defeat this isolation. mod consts; mod container; diff --git a/crates/devcontainer-rs/src/path.rs b/crates/devcontainer-rs/src/path.rs index 4524c0e..3292aae 100644 --- a/crates/devcontainer-rs/src/path.rs +++ b/crates/devcontainer-rs/src/path.rs @@ -1,9 +1,9 @@ -//! Normalisation lexicale de chemins, sans accès au système de fichiers. +//! Lexical path normalization, without accessing the filesystem. use std::path::{Path, PathBuf}; -/// Normalise lexicalement un chemin, en résolvant `.` et `..` sans toucher au -/// système de fichiers. Renvoie `None` si le chemin sort de sa racine. +/// Lexically normalizes a path, resolving `.` and `..` without touching the +/// filesystem. Returns `None` if the path escapes its root. pub fn normalize(path: &Path) -> Option { use std::path::Component; diff --git a/crates/devcontainer-rs/src/runtime.rs b/crates/devcontainer-rs/src/runtime.rs index 63028c8..4fb7f3c 100644 --- a/crates/devcontainer-rs/src/runtime.rs +++ b/crates/devcontainer-rs/src/runtime.rs @@ -1,4 +1,4 @@ -//! Client de l'API du daemon de containers et opérations de cycle de vie. +//! Container daemon API client and lifecycle operations. use std::{collections::HashMap, path::Path, time::Duration}; @@ -24,7 +24,7 @@ use crate::{ exec::ExecOutput, }; -/// Un client de l'API du daemon de containers. +/// A container daemon API client. #[derive(Debug, Clone)] pub struct ContainerRuntime { docker: Docker, @@ -33,7 +33,7 @@ pub struct ContainerRuntime { } impl ContainerRuntime { - /// Se connecte au daemon désigné par `DOCKER_HOST`, ou au socket local par défaut. + /// Connects to the daemon designated by `DOCKER_HOST`, or to the default local socket. pub fn connect() -> Result { let endpoint = std::env::var("DOCKER_HOST").unwrap_or_else(|_| String::from(DEFAULT_ENDPOINT)); @@ -45,13 +45,13 @@ impl ContainerRuntime { }) } - /// Remplace le timeout appliqué aux opérations de build/run/stop/remove. + /// Replaces the timeout applied to build/run/stop/remove operations. pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } - /// Endpoint du daemon, pour les logs. + /// Daemon endpoint, for logging. pub fn endpoint(&self) -> &str { &self.endpoint } @@ -60,12 +60,12 @@ impl ContainerRuntime { self.timeout } - /// Vérifie que le daemon est joignable et répond. + /// Checks that the daemon is reachable and responding. pub async fn available(&self) -> bool { self.docker.ping().await.is_ok() } - /// Exécute une requête du daemon en appliquant le timeout des commandes. + /// Runs a daemon request, applying the command timeout. async fn request( &self, operation: &str, @@ -81,7 +81,7 @@ impl ContainerRuntime { } } - /// Construit l'image `tag` depuis le contexte `context_dir`. + /// Builds the image `tag` from the `context_dir` context. pub(crate) async fn build_image( &self, context_dir: &Path, @@ -102,8 +102,8 @@ impl ContainerRuntime { .docker .build_image(options, None, Some(body_try_stream(context))); - // Le flux porte la progression du build : la dernière erreur signalée par le - // daemon fait échouer l'opération. + // The stream carries the build progress: the last error reported by the + // daemon makes the operation fail. let consume = async { let mut failure = None; @@ -162,7 +162,7 @@ impl ContainerRuntime { } } - /// Crée un container, sans le démarrer. + /// Creates a container without starting it. pub(crate) async fn create_container( &self, name: &str, @@ -217,7 +217,7 @@ impl ContainerRuntime { Ok(()) } - /// Exécute une commande dans un container et renvoie sa sortie. + /// Runs a command in a container and returns its output. pub(crate) async fn exec( &self, container: &str, @@ -254,8 +254,8 @@ impl ContainerRuntime { let mut stdout = String::new(); let mut stderr = String::new(); - // Le timeout couvre l'exécution de la commande elle-même, pas seulement sa - // mise en place. + // The timeout covers the execution of the command itself, not just its + // setup. let collect = async { while let Some(message) = output.next().await { match message? { @@ -276,10 +276,10 @@ impl ContainerRuntime { Ok(Ok(())) => {} Ok(Err(source)) => return Err(ContainerError::Request { source }), Err(_) => { - // Le processus continue de tourner dans le container : sans arrêt, il - // consommerait CPU et mémoire et pourrait encore modifier le workspace - // pendant toute la durée de vie de la sandbox. L'API n'offre pas de - // moyen de tuer un exec, on arrête donc le container qui le porte. + // The process keeps running in the container: left alone, it would + // consume CPU and memory and could still modify the workspace for the + // whole lifetime of the sandbox. The API offers no way to kill an exec, + // so we stop the container that hosts it. let _ = self.stop_container(container).await; return Err(ContainerError::Timeout { @@ -289,10 +289,10 @@ impl ContainerRuntime { } } - // Le code de sortie n'est pas forcément publié au moment où le flux se ferme : - // on laisse au daemon le temps de le renseigner, sans bloquer indéfiniment. - // Sans cela, une commande réussie peut être rapportée en échec (code -1) et - // l'outil renvoie une erreur au modèle à la place du contenu. + // The exit code is not necessarily published by the time the stream closes: + // we give the daemon time to fill it in, without blocking indefinitely. + // Without this, a successful command can be reported as failed (code -1) and + // the tool returns an error to the model instead of the content. let mut inspected = self .request("inspect exec", self.docker.inspect_exec(&created.id)) .await?; @@ -331,7 +331,7 @@ impl ContainerRuntime { Ok(()) } - /// Supprime un container, ses volumes anonymes et le processus qui y tourne. + /// Removes a container, its anonymous volumes and the process running in it. pub(crate) async fn remove_container(&self, name: &str) -> Result<(), ContainerError> { let options = RemoveContainerOptions { force: true, @@ -363,7 +363,7 @@ impl ContainerRuntime { Ok(()) } - /// Crée le réseau dédié d'une sandbox. + /// Creates a sandbox's dedicated network. pub(crate) async fn create_network(&self, name: &str) -> Result<(), ContainerError> { let request = NetworkCreateRequest { name: String::from(name), @@ -376,7 +376,7 @@ impl ContainerRuntime { Ok(()) } - /// Détache un container d'un réseau, ce qui coupe sa connectivité. + /// Detaches a container from a network, which cuts off its connectivity. pub(crate) async fn disconnect_network( &self, network: &str, diff --git a/crates/devcontainer-rs/src/schema.rs b/crates/devcontainer-rs/src/schema.rs index de7d5e1..7d60fb8 100644 --- a/crates/devcontainer-rs/src/schema.rs +++ b/crates/devcontainer-rs/src/schema.rs @@ -1,5 +1,5 @@ -//! Schéma du `devcontainer.json`, analyse du fichier et résolution des -//! références `${localEnv:…}`. +//! `devcontainer.json` schema, file parsing and resolution of `${localEnv:…}` +//! references. use std::{ collections::HashMap, @@ -23,7 +23,7 @@ struct DevContainerSchema { #[serde(default)] name: Option, - /// Image de base tirée du registre, alternative à `build`. + /// Base image pulled from the registry, alternative to `build`. #[serde(default)] image: Option, @@ -106,16 +106,16 @@ impl TryFrom<(DevContainerSchema, PathBuf)> for DevContainer { } } -/// Résout les références `${localEnv:VAR}` et `${localEnv:VAR:default}` d'un +/// Resolves the `${localEnv:VAR}` and `${localEnv:VAR:default}` references of a /// `devcontainer.json`. /// -/// La spécification veut que `${localEnv:VAR}` soit lu dans l'environnement du -/// client ; ici le `devcontainer.json` vient d'une **pull request**, donc de code -/// non fiable : lire l'environnement du processus lui permettrait de récupérer -/// `GITEA_TOKEN`, `OPEN_ROUTER_API_KEY` ou n'importe quel autre secret de Steep et -/// de l'exfiltrer depuis un `build.args`, un `containerEnv`, son Dockerfile ou un -/// hook. L'environnement n'est donc **jamais** consulté : seule la valeur par -/// défaut est utilisée, et une référence sans défaut devient une chaîne vide. +/// The specification says `${localEnv:VAR}` is read from the client's environment; +/// here the `devcontainer.json` comes from a **pull request**, hence from untrusted +/// code: reading the process environment would let it grab `GITEA_TOKEN`, +/// `OPEN_ROUTER_API_KEY` or any other Steep secret and exfiltrate it through a +/// `build.args`, a `containerEnv`, its Dockerfile or a hook. The environment is +/// therefore **never** consulted: only the default value is used, and a reference +/// without a default becomes an empty string. fn substitute_local_env(input: &str) -> String { const PREFIX: &str = "${localEnv:"; @@ -129,7 +129,7 @@ fn substitute_local_env(input: &str) -> String { match after.find('}') { Some(end) => { let inner = &after[..end]; - // La clé est lue pour délimiter la référence, pas pour la résoudre. + // The key is read to delimit the reference, not to resolve it. let (_key, default) = match inner.split_once(':') { Some((key, default)) => (key, Some(default)), None => (inner, None), @@ -239,8 +239,8 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let devcontainer_path = dir.path().join("devcontainer.json"); - // `runArgs` vient d'une pull request non fiable : il doit être accepté par - // l'analyse mais jamais repris dans le devcontainer. + // `runArgs` comes from an untrusted pull request: it must be accepted by + // parsing but never carried over into the devcontainer. fs::write( &devcontainer_path, r#"{ @@ -271,8 +271,8 @@ mod tests { fn local_env_references_are_never_read_from_the_environment() { unsafe { std::env::set_var("DEVCONTAINER_TEST_SECRET", "s3cret") }; - // Le `devcontainer.json` vient d'une pull request : résoudre la référence - // depuis l'environnement de Steep y exposerait ses jetons. + // The `devcontainer.json` comes from a pull request: resolving the reference + // from Steep's environment would expose its tokens to it. assert_eq!( substitute_local_env("${localEnv:DEVCONTAINER_TEST_SECRET}"), "" diff --git a/crates/steep-server/Cargo.toml b/crates/steep-server/Cargo.toml index e7e19a1..9d2b74a 100644 --- a/crates/steep-server/Cargo.toml +++ b/crates/steep-server/Cargo.toml @@ -2,6 +2,7 @@ name = "steep-server" version = "1.3.0" edition = "2024" +publish = false [dependencies] reqwest = { workspace = true } @@ -26,7 +27,8 @@ hex = { workspace = true } metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } devcontainer-rs = { path = "../devcontainer-rs" } -tempfile = "3" +tempfile = { workspace = true } +rustls = { workspace = true } toasty = { workspace = true } toasty-cli = { workspace = true } jiff = { workspace = true } diff --git a/crates/steep-server/src/api.rs b/crates/steep-server/src/api.rs index 158b5ff..c99ff82 100644 --- a/crates/steep-server/src/api.rs +++ b/crates/steep-server/src/api.rs @@ -28,8 +28,8 @@ pub async fn start(app_state: AppState) -> anyhow::Result<()> { let http_port = app_state.config.http_port; let shutdown = app_state.shutdown.clone(); - // Le dashboard est servi depuis un dossier statique ; les routes API - // ci-dessous restent prioritaires (le dossier n'est que le fallback). + // The dashboard is served from a static directory; the API routes below take + // precedence (the directory is only the fallback). let front_dir = std::env::var("FRONT_DIR").unwrap_or_else(|_| String::from("front")); if !std::path::Path::new(&front_dir).is_dir() { warn!(dir = %front_dir, "front directory not found, the dashboard will 404"); @@ -38,7 +38,7 @@ pub async fn start(app_state: AppState) -> anyhow::Result<()> { let app = Router::new() .route("/webhook", post(webhook)) - .route("/dedup", get(dedup_list)) + .route("/reviews", get(reviews_list)) .route("/jobs", get(jobs_list)) .route("/events", get(lifecycle_events)) .route("/jobs/{id}/events", get(job_events)) @@ -63,8 +63,8 @@ pub async fn start(app_state: AppState) -> anyhow::Result<()> { Ok(()) } -async fn dedup_list(State(app_state): State) -> Result { - let reviews = app_state.dedup.list_reviews().await?; +async fn reviews_list(State(app_state): State) -> Result { + let reviews = app_state.reviews.list().await?; Ok(Json(json!({ "reviews": reviews @@ -81,10 +81,10 @@ async fn jobs_list(State(app_state): State) -> Result, ) -> Sse>> { @@ -143,7 +143,7 @@ async fn webhook( metrics::webhook_received(event_type); let event_id = wb.event_id(); - match app_state.dedup.is_known(event_id).await { + match is_known(&app_state, event_id).await { Ok(true) => { metrics::webhook_duplicate(event_type); return Err(AppError::AlreadyProcessedErr); @@ -172,6 +172,12 @@ async fn webhook( Ok((StatusCode::CREATED, "Task started")) } +/// Whether an event is already queued, or already reviewed. +async fn is_known(app_state: &AppState, event_id: u64) -> toasty::Result { + Ok(app_state.queue.is_queued(event_id).await? + || app_state.reviews.is_reviewed(event_id).await?) +} + pub struct WebhookExtract(pub WebhookType); impl FromRequest for WebhookExtract diff --git a/crates/steep-server/src/bot.rs b/crates/steep-server/src/bot.rs index c7172cd..94deff8 100644 --- a/crates/steep-server/src/bot.rs +++ b/crates/steep-server/src/bot.rs @@ -1,200 +1,95 @@ -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; -use futures_util::StreamExt; -use serde::{Deserialize, Deserializer}; -use tokio::sync::Semaphore; +use tokio::{sync::Semaphore, task::JoinSet}; use tokio_util::sync::CancellationToken; use tracing::{error, info, instrument}; use crate::{ - bot_actions::review::ReviewOutcome, - dedup::{Dedup, ReviewRecord}, + bot_actions::review::{self, ReviewContext, ReviewOutcome}, event_bus::{EventBus, Reporter}, gitea::{GiteaAPI, WebhookType}, job_queue::JobQueue, metrics, open_router::OpenRouterClient, - sandbox::SandboxConfig, + review_store::{ReviewRecord, ReviewStore}, + sandbox::{SandboxConfig, tools}, schemas::Job, }; -#[derive(Deserialize, Debug)] -pub struct ReviewResult { - pub reviews: Vec, - pub comment: String, - pub cost: Option, -} +/// Delay before claiming again after the queue could not be reached. +const CLAIM_RETRY_DELAY: Duration = Duration::from_secs(1); -#[derive(Deserialize, Debug)] -pub struct ReviewItem { - pub filename: String, - pub line: Option, - #[serde(default, deserialize_with = "deserialize_side")] - pub side: Option, - #[serde(default, deserialize_with = "deserialize_severity")] - pub severity: Option, - pub message: String, -} - -/// Which version of the file a review comment is anchored on. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ReviewSide { - /// The line was added by the pull request: `line` is a line number of the - /// new version of the file. - Added, - /// The line was removed by the pull request: `line` is a line number of the - /// old version of the file. - Removed, -} - -impl ReviewSide { - /// Reads the side the model asked for, tolerating casing and synonyms. - fn parse(raw: &str) -> Option { - match raw.trim().to_ascii_lowercase().as_str() { - "added" | "add" | "new" | "right" => Some(Self::Added), - "removed" | "remove" | "deleted" | "delete" | "old" | "left" => Some(Self::Removed), - _ => None, - } - } -} - -/// What kind of problem a review reports. -/// -/// The categories are the ones the prompt asks for; a review whose severity is -/// missing or unreadable falls back to [`ReviewSeverity::Maintainability`], the -/// least alarming bucket, instead of failing the whole review. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ReviewSeverity { - /// Wrong behaviour. - Bug, - /// A vulnerability. - Security, - /// A resource problem. - Performance, - /// Everything else: readability, structure, naming, tests. - Maintainability, -} - -impl ReviewSeverity { - /// Reads the severity the model asked for, tolerating casing and synonyms. - fn parse(raw: &str) -> Option { - match raw.trim().to_ascii_lowercase().as_str() { - "bug" | "bugs" | "correctness" | "error" => Some(Self::Bug), - "security" | "vulnerability" => Some(Self::Security), - "performance" | "perf" => Some(Self::Performance), - "maintainability" | "quality" | "style" => Some(Self::Maintainability), - _ => None, - } - } - - /// Lower-case label used in the review markdown. - pub fn label(self) -> &'static str { - match self { - Self::Bug => "bug", - Self::Security => "security", - Self::Performance => "performance", - Self::Maintainability => "maintainability", - } - } -} - -/// Reads the side the model asked for. An unreadable value is ignored instead of -/// failing the whole review: the side is then resolved from the changed lines. -fn deserialize_side<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let raw = Option::::deserialize(deserializer)?; - - Ok(raw - .as_ref() - .and_then(serde_json::Value::as_str) - .and_then(ReviewSide::parse)) -} - -/// Reads the severity the model asked for, ignoring an unreadable value: the -/// review is then reported as [`ReviewSeverity::Maintainability`]. -fn deserialize_severity<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let raw = Option::::deserialize(deserializer)?; - - Ok(raw - .as_ref() - .and_then(serde_json::Value::as_str) - .and_then(ReviewSeverity::parse)) +/// Everything the bot works with: the external services and the stores. +#[derive(Clone)] +pub struct BotDeps { + pub gitea: GiteaAPI, + pub open_router: OpenRouterClient, + pub sandbox: SandboxConfig, + pub reviews: ReviewStore, + pub queue: JobQueue, + pub event_bus: EventBus, } #[derive(Clone)] pub struct Bot { - gitea_api: GiteaAPI, - open_router_client: OpenRouterClient, + deps: BotDeps, max_concurrent: usize, - open_router_model: String, - sandbox: SandboxConfig, - dedup: Dedup, - queue: JobQueue, - event_bus: EventBus, } impl Bot { - #[allow(clippy::too_many_arguments)] - pub fn new( - gitea_api: GiteaAPI, - open_router_client: OpenRouterClient, - max_concurrent: usize, - open_router_model: String, - sandbox: SandboxConfig, - dedup: Dedup, - queue: JobQueue, - event_bus: EventBus, - ) -> Self { + pub fn new(deps: BotDeps, max_concurrent: usize) -> Self { Self { - gitea_api, - open_router_client, + deps, max_concurrent, - open_router_model, - sandbox, - dedup, - queue, - event_bus, } } - /// Consomme la file jusqu'à l'annulation de `shutdown`. + /// Consumes the queue until `shutdown` is cancelled. /// - /// Un sémaphore borne la concurrence ; les jobs en cours sont laissés se - /// terminer. Les jobs non terminés restent en base et seront repris au - /// prochain démarrage. + /// A semaphore bounds the concurrency, and a job is only claimed once a slot is + /// free, so the jobs waiting for one stay `pending`. Running jobs are left to + /// finish; the unfinished ones stay `processing` and are requeued at the next + /// startup. pub async fn start(&self, shutdown: CancellationToken) -> anyhow::Result<()> { info!("Bot started"); let semaphore = Arc::new(Semaphore::new(self.max_concurrent)); - let mut tasks = tokio::task::JoinSet::new(); - let mut pending = std::pin::pin!(self.queue.pending()); + let mut tasks = JoinSet::new(); - loop { - let job = tokio::select! { + 'consume: loop { + let permit = tokio::select! { biased; - _ = shutdown.cancelled() => break, - job = pending.next() => match job { - Some(job) => job, - None => break, - }, + () = shutdown.cancelled() => break, + permit = semaphore.clone().acquire_owned() => permit?, }; - while let Some(res) = tasks.try_join_next() { - if let Err(err) = res { - error!("Task panicked: {err}"); + let job = loop { + while let Some(res) = tasks.try_join_next() { + if let Err(err) = res { + error!("Task panicked: {err}"); + } } - } - let permit = semaphore.clone().acquire_owned().await?; + match self.deps.queue.claim_next().await { + Ok(Some(job)) => break job, + Ok(None) => { + tokio::select! { + biased; + () = shutdown.cancelled() => break 'consume, + () = self.deps.queue.wait_for_work() => {} + } + } + Err(err) => { + error!(%err, "failed to claim a job"); - if let Err(err) = self.queue.mark_processing(job.id).await { - error!(%err, "failed to mark job as processing"); - } + tokio::select! { + biased; + () = shutdown.cancelled() => break 'consume, + () = tokio::time::sleep(CLAIM_RETRY_DELAY) => {} + } + } + } + }; let bot = self.clone(); metrics::increment_task_active(); @@ -212,30 +107,35 @@ impl Bot { Ok(()) } + /// Runs a claimed job and records its outcome. async fn process(&self, job: Job) { + self.run_job(&job).await; + + if let Err(err) = self.deps.queue.remove(job.id).await { + error!(%err, "failed to remove job"); + } + } + + async fn run_job(&self, job: &Job) { + let deps = &self.deps; let event_type = job.payload.event_type_str(); - match self.dedup.is_reviewed(job.event_id).await { + match deps.reviews.is_reviewed(job.event_id).await { Ok(true) => { metrics::task_skipped(event_type); info!("Review already done, skipping"); - self.event_bus.skip(job.id, &job.repo, job.pr); - - if let Err(err) = self.queue.remove(job.id).await { - error!(%err, "failed to remove job"); - } + deps.event_bus.skip(job.id, &job.repo, job.pr); return; } Ok(false) => {} - Err(err) => error!(%err, "Dedup lookup failed, reviewing anyway"), + Err(err) => error!(%err, "Review lookup failed, reviewing anyway"), } - let reporter = self.event_bus.start(job.id, job.repo.clone(), job.pr); + let reporter = deps.event_bus.start(job.id, job.repo.clone(), job.pr); - let payload = job.payload.0.clone(); let started = std::time::Instant::now(); - let result = self.exec(payload, &reporter).await; - let duration_ms = started.elapsed().as_millis() as u64; + let result = self.exec(&job.payload, &reporter).await; + let duration_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX); let (ok, outcome) = match result { Ok(outcome) => { @@ -251,60 +151,47 @@ impl Bot { } }; - let comments = outcome.comments as u64; - let record = ReviewRecord { event_id: job.event_id, repo: job.repo.clone(), pr: job.pr, ok, - model: self.open_router_model.clone(), + model: deps.open_router.model().to_string(), markdown: outcome.markdown, cost: outcome.cost, - comments, + comments: outcome.comments, duration_ms, }; - if let Err(err) = self.dedup.mark_reviewed(record).await { + if let Err(err) = deps.reviews.record(record).await { error!(%err, "failed to record review"); } - self.event_bus - .finish(job.id, &job.repo, job.pr, ok, comments, duration_ms); - - if let Err(err) = self.queue.remove(job.id).await { - error!(%err, "failed to remove job"); - } + deps.event_bus + .finish(job.id, &job.repo, job.pr, ok, outcome.comments, duration_ms); } - #[instrument(skip(self, webhook, reporter), fields(repo, pr))] - pub async fn exec( + /// Dispatches a webhook to the action it asks for. + #[instrument( + skip_all, + fields(repo = %webhook.repository_full_name(), pr = webhook.pull_request_number()) + )] + async fn exec( &self, - webhook: WebhookType, + webhook: &WebhookType, reporter: &Reporter, ) -> anyhow::Result { - match &webhook { - WebhookType::Review(p) => { - tracing::Span::current().record("repo", &p.repository.full_name); - tracing::Span::current().record("pr", p.pull_request.number); - } + let deps = &self.deps; + let ctx = ReviewContext { + gitea: &deps.gitea, + open_router: &deps.open_router, + sandbox: &deps.sandbox, + reporter, }; - - let tools = crate::sandbox::tools::for_webhook(&webhook); + let tools = tools::for_webhook(webhook); match webhook { - WebhookType::Review(review_payload) => { - crate::bot_actions::review::exec_review( - &self.gitea_api, - &self.open_router_client, - &self.open_router_model, - &self.sandbox, - tools, - review_payload, - reporter, - ) - .await - } + WebhookType::Review(payload) => review::exec_review(&ctx, tools, payload).await, } } } diff --git a/crates/steep-server/src/bot_actions/review.rs b/crates/steep-server/src/bot_actions/review.rs deleted file mode 100644 index 3fe8148..0000000 --- a/crates/steep-server/src/bot_actions/review.rs +++ /dev/null @@ -1,1192 +0,0 @@ -use std::str::FromStr; - -use tracing::{info, instrument, warn}; - -use crate::{ - bot::{ReviewItem, ReviewResult, ReviewSeverity, ReviewSide}, - consts::{BOT_PROCESS_MSG, REVIEW_PROMPT, SANDBOX_SYSTEM_PROMPT}, - event_bus::Reporter, - gitea::{GiteaAPI, PullRequestFile, ReviewPayload}, - metrics, - open_router::{OpenRouterClient, Tool}, - sandbox::{Sandbox, SandboxConfig, agent}, - text::excerpt, -}; - -pub struct ReviewOutcome { - pub markdown: String, - pub cost: Option, - pub comments: usize, -} - -impl ReviewOutcome { - pub fn empty() -> Self { - Self { - markdown: String::new(), - cost: None, - comments: 0, - } - } -} - -#[instrument(skip( - gitea_api, - open_router_client, - sandbox_config, - tools, - review_payload, - reporter -))] -pub async fn exec_review( - gitea_api: &GiteaAPI, - open_router_client: &OpenRouterClient, - model: &str, - sandbox_config: &SandboxConfig, - tools: Vec, - review_payload: ReviewPayload, - reporter: &Reporter, -) -> anyhow::Result { - tracing::info!( - repo = %review_payload.repository.full_name, - pr = review_payload.pull_request.number, - action = %review_payload.action, - "Starting review" - ); - - let new_comment = gitea_api - .comment( - &BOT_PROCESS_MSG.replace("{model}", model), - &review_payload.repository.full_name, - review_payload.pull_request.number, - ) - .await?; - - let bot_result: Result<(ReviewResult, String), anyhow::Error> = async { - let full_name = &review_payload.repository.full_name; - let index = review_payload.pull_request.number; - - let git_diff = gitea_api.pull_request_diff(full_name, index).await?; - - // The file list only refines the paths and describes the changes: a - // failure is not fatal, the diff is the source of truth for the lines. - let files = match gitea_api.pull_request_files(full_name, index).await { - Ok(files) => files, - Err(err) => { - warn!(%err, "Failed to list the pull request files"); - Vec::new() - } - }; - - let mut changed_lines = parse_changed_lines(&git_diff); - drop_generated_files(&mut changed_lines); - resolve_filenames(&mut changed_lines, &files); - - let changes = format_changes(&files, &changed_lines); - - let bot_request = REVIEW_PROMPT - .replace("{subject}", &review_payload.pull_request.title) - .replace("{comment}", &review_payload.comment.body) - .replace("{changes}", &changes); - - let (mut review_result, cost) = run_sandboxed_review( - gitea_api, - open_router_client, - sandbox_config, - tools, - &review_payload, - &bot_request, - reporter, - ) - .await?; - - resolve_review_sides(&mut review_result, &changed_lines); - - review_result.cost = cost; - if let Some(cost) = review_result.cost { - metrics::openrouter_cost_usd(cost); - } - - let final_review_markdown = review_result_to_markdown(&review_result); - - gitea_api - .post_pull_request_review( - &review_result, - &final_review_markdown, - &review_payload.repository.full_name, - review_payload.pull_request.number, - ) - .await?; - - Ok((review_result, final_review_markdown)) - } - .await; - - match bot_result { - Ok((review_result, markdown)) => { - gitea_api - .delete_comment(&review_payload.repository.full_name, new_comment.id) - .await?; - - Ok(ReviewOutcome { - markdown, - cost: review_result.cost, - comments: review_result.reviews.len(), - }) - } - Err(e) => { - gitea_api - .edit_comment( - &format!("Error while reviewing: {}", e), - &review_payload.repository.full_name, - new_comment.id, - ) - .await?; - - Err(e) - } - } -} - -async fn run_sandboxed_review( - gitea_api: &GiteaAPI, - open_router_client: &OpenRouterClient, - sandbox_config: &SandboxConfig, - tools: Vec, - review_payload: &ReviewPayload, - bot_request: &str, - reporter: &Reporter, -) -> anyhow::Result<(ReviewResult, Option)> { - let sandbox = Sandbox::create( - &sandbox_config.runtime, - &review_payload.repository.clone_url, - gitea_api.token(), - review_payload.pull_request.number, - reporter, - ) - .await?; - - let result = agent::run( - open_router_client, - &sandbox, - tools, - SANDBOX_SYSTEM_PROMPT, - bot_request, - sandbox_config.max_iterations, - reporter, - ) - .await; - - if let Err(err) = sandbox.cleanup().await { - warn!(%err, "Failed to clean up sandbox container"); - } - - let result = result?; - info!( - iterations = result.iterations, - cost = ?result.cost, - "Sandboxed review finished" - ); - - Ok((result.answer, result.cost)) -} - -fn review_result_to_markdown(review_result: &ReviewResult) -> String { - if review_result.reviews.is_empty() { - return String::from("No issues found. ✅"); - } - - let mut md = String::from("## Review Feedback\n\n"); - - md.push_str(&format!( - "### {} issues found.\n\n", - review_result.reviews.len() - )); - - let breakdown = severity_breakdown(&review_result.reviews); - if !breakdown.is_empty() { - md.push_str(&breakdown); - md.push('\n'); - } - - if !review_result.comment.is_empty() { - md.push_str("\n---\n\n"); - md.push_str("### Summary\n\n"); - md.push_str(&review_result.comment); - md.push('\n'); - } - - if let Some(cost) = review_result.cost { - md.push_str("\n---\n\n"); - md.push_str(&format!("### Cost: ${}", cost)); - md.push('\n'); - } - - md -} - -/// Counts the reviews per severity, most severe first. -/// -/// A review whose severity the model omitted or wrote unreadably is counted as -/// [`ReviewSeverity::Maintainability`], the same fallback used when posting it. -fn severity_breakdown(reviews: &[ReviewItem]) -> String { - [ - ReviewSeverity::Bug, - ReviewSeverity::Security, - ReviewSeverity::Performance, - ReviewSeverity::Maintainability, - ] - .into_iter() - .filter_map(|severity| { - let count = reviews - .iter() - .filter(|review| review.severity.unwrap_or(ReviewSeverity::Maintainability) == severity) - .count(); - - (count > 0).then(|| format!("- {} {}", count, severity.label())) - }) - .collect::>() - .join("\n") -} - -/// The lines a pull request changed, per file. -/// -/// Line numbers are the ones Gitea expects to place a review comment: -/// [`ReviewSide::Added`] numbers refer to the new version of the file (sent as -/// `new_position`), [`ReviewSide::Removed`] numbers to the old version (sent as -/// `old_position`). -type ChangedLines = Vec; - -struct ChangedFile { - filename: String, - added: Vec, - removed: Vec, -} - -impl ChangedFile { - fn new(filename: &str) -> Self { - Self { - filename: String::from(filename), - added: Vec::new(), - removed: Vec::new(), - } - } - - /// Lines that can be commented on for the given side. - fn lines(&self, side: ReviewSide) -> &[u64] { - match side { - ReviewSide::Added => &self.added, - ReviewSide::Removed => &self.removed, - } - } - - /// Side a line belongs to, used when the model did not state one. - fn side_of(&self, line: u64) -> Option { - if self.added.contains(&line) { - Some(ReviewSide::Added) - } else if self.removed.contains(&line) { - Some(ReviewSide::Removed) - } else { - None - } - } -} - -/// Lists the lines changed by the diff, per file, on both sides. -/// -/// Only the line numbers are kept: the model reads the code itself through the -/// sandbox tools. -fn parse_changed_lines(git_diff: &str) -> ChangedLines { - let mut files = Vec::new(); - let mut current_file: Option = None; - let mut in_hunk = false; - let mut old_line: u64 = 0; - let mut new_line: u64 = 0; - - for line in git_diff.lines() { - if line.starts_with("diff --git ") { - current_file = None; - in_hunk = false; - continue; - } - - // `--- a/x` and `+++ b/x` only appear before the first hunk of a file. - // Inside a hunk, a line may legitimately start with them: removing - // `--x` gives `---x`, adding `++i;` gives `+++i;`. - if !in_hunk && (line.starts_with("--- ") || line.starts_with("+++ ")) { - // A header is never a content line: `+++ /dev/null` on a deleted - // file keeps the path of the other side in place. - if let Some(path) = header_file_path(line) { - current_file = Some(path); - } - continue; - } - - if line.starts_with("@@") { - if let Some((old_start, new_start)) = parse_hunk_starts(line) { - old_line = old_start; - new_line = new_start; - in_hunk = true; - } - continue; - } - - let Some(filename) = current_file.as_deref() else { - continue; - }; - - match line.as_bytes().first() { - Some(b' ') => { - old_line += 1; - new_line += 1; - } - Some(b'-') => { - changed_file(&mut files, filename).removed.push(old_line); - old_line += 1; - } - Some(b'+') => { - changed_file(&mut files, filename).added.push(new_line); - new_line += 1; - } - // `\ No newline at end of file`, and anything unexpected: a line - // that advances neither side. - _ => {} - } - } - - files -} - -/// Path of the file on one side of the diff, from a `--- a/` or -/// `+++ b/` header line. -/// -/// These lines are the only unambiguous source for the path: the `diff --git` -/// line is cut at the first space, and it names the old path of a renamed file. -/// `None` for `/dev/null`, which leaves the path of the other side in place. -fn header_file_path(line: &str) -> Option { - let (prefix, raw) = match line.strip_prefix("--- ") { - Some(raw) => ("a/", raw), - None => ("b/", line.strip_prefix("+++ ")?), - }; - - let path = decode_git_path(raw); - - Some(String::from(path.strip_prefix(prefix)?)) -} - -/// Decodes a path as git writes it in a diff header: git wraps it in quotes and -/// escapes the bytes that need it (`\303\251` for `é`) when the path contains -/// non-printable or non-ASCII characters. -fn decode_git_path(raw: &str) -> String { - let Some(quoted) = raw.strip_prefix('"').and_then(|raw| raw.strip_suffix('"')) else { - return String::from(raw); - }; - - let bytes = quoted.as_bytes(); - let mut decoded = Vec::with_capacity(bytes.len()); - let mut index = 0; - - while let Some(byte) = bytes.get(index) { - index += 1; - - if *byte != b'\\' { - decoded.push(*byte); - continue; - } - - match bytes.get(index) { - // Octal escapes are the ones that matter for a path: git uses them - // for every non-ASCII byte. - Some(digit @ b'0'..=b'7') => { - let mut value = u32::from(digit - b'0'); - let mut digits = 1; - - while digits < 3 { - match bytes.get(index + digits) { - Some(next @ b'0'..=b'7') => { - value = value * 8 + u32::from(next - b'0'); - digits += 1; - } - _ => break, - } - } - - decoded.push(u8::try_from(value).unwrap_or(b'?')); - index += digits; - } - Some(escaped) => { - decoded.push(match escaped { - b't' => b'\t', - b'n' => b'\n', - b'r' => b'\r', - other => *other, - }); - index += 1; - } - None => decoded.push(b'\\'), - } - } - - String::from_utf8_lossy(&decoded).into_owned() -} - -/// Replaces the paths parsed from the diff with the exact paths reported by the -/// API, which are the ones the model sees in the sandbox. -fn resolve_filenames(changed_lines: &mut ChangedLines, files: &[PullRequestFile]) { - for changed in changed_lines.iter_mut() { - let parsed = changed.filename.as_str(); - - let Some(file) = files.iter().find(|file| file.filename == parsed) else { - if !files.is_empty() { - warn!(path = %parsed, "Changed file is not in the pull request file list"); - } - continue; - }; - - changed.filename = file.filename.clone(); - } -} - -/// Basenames of generated files that are never reviewed. -/// -/// Their diffs are machine-written dependency churn: they run to thousands of -/// lines, flood the prompt with line numbers and drown the code the model should -/// look at. A lockfile is also never a place where a review comment belongs. -const IGNORED_FILE_NAMES: [&str; 16] = [ - "Cargo.lock", - "package-lock.json", - "npm-shrinkwrap.json", - "yarn.lock", - "pnpm-lock.yaml", - "bun.lock", - "bun.lockb", - "composer.lock", - "Gemfile.lock", - "poetry.lock", - "uv.lock", - "Pipfile.lock", - "go.sum", - "packages.lock.json", - "flake.lock", - "pubspec.lock", -]; - -/// Whether a changed path is a generated file that is never reviewed. -/// -/// The comparison is on the basename, so a lockfile nested in a workspace (for -/// example `crates/foo/Cargo.lock`) is matched too. -fn is_ignored(filename: &str) -> bool { - let basename = filename.rsplit('/').next().unwrap_or(filename); - - IGNORED_FILE_NAMES.contains(&basename) -} - -/// Drops the generated files from the changed lines. -/// -/// Removing them here keeps the prompt free of their line numbers, and also makes -/// [`resolve_review_sides`] reject any review the model anchors on one of them. -fn drop_generated_files(changed_lines: &mut ChangedLines) { - changed_lines.retain(|file| !is_ignored(&file.filename)); -} - -/// Renders the changes for the model: the files the pull request touches, then -/// the lines to review per file. -fn format_changes(files: &[PullRequestFile], changed_lines: &ChangedLines) -> String { - let mut sections = Vec::new(); - - let described = files - .iter() - .filter(|file| !is_ignored(&file.filename)) - .map(describe_file) - .collect::>() - .join(", "); - - if !described.is_empty() { - sections.push(format!("Files changed by the pull request: {described}")); - } - - sections.push(format!( - "Lines to review, per file:\n{}", - format_changed_lines(changed_lines) - )); - - sections.join("\n\n") -} - -/// Describes a changed file for the model, including how it changed. -fn describe_file(file: &PullRequestFile) -> String { - match &file.previous_filename { - Some(previous) => format!("{} ({} from {})", file.filename, file.status, previous), - None => format!("{} ({})", file.filename, file.status), - } -} - -/// Borrows the entry of `files` for a file, creating it on its first change. -fn changed_file<'a>(files: &'a mut ChangedLines, filename: &str) -> &'a mut ChangedFile { - let index = match files.iter().position(|file| file.filename == filename) { - Some(index) => index, - None => { - files.push(ChangedFile::new(filename)); - files.len() - 1 - } - }; - - &mut files[index] -} - -/// Renders the changed lines as `filename: added 1, 2 / removed 3`, one file per -/// line, keeping only the sides the file actually has. -fn format_changed_lines(changed_lines: &ChangedLines) -> String { - changed_lines - .iter() - .map(|file| { - let mut sides = Vec::new(); - - for (label, lines) in [("added", &file.added), ("removed", &file.removed)] { - if !lines.is_empty() { - sides.push(format!("{label} {}", format_line_numbers(lines))); - } - } - - format!("{}: {}", file.filename, sides.join(" / ")) - }) - .collect::>() - .join("\n") -} - -fn format_line_numbers(lines: &[u64]) -> String { - lines - .iter() - .map(u64::to_string) - .collect::>() - .join(", ") -} - -/// Number of characters of a model answer kept in the logs when it cannot be -/// parsed. -const MAX_LOGGED_ANSWER: usize = 500; - -impl FromStr for ReviewResult { - type Err = anyhow::Error; - - /// Parses the review the model answered with. - /// - /// This is the contract the agent loop enforces: a rejected answer is sent - /// back to the model, with the reason, so that it can correct itself. - /// - /// The contract is a raw JSON object, but models sometimes wrap it in a - /// markdown code fence or surround it with a sentence: the object is then - /// extracted from the answer before failing, and the answer is logged so a - /// breach of the contract can be diagnosed. - fn from_str(message: &str) -> Result { - let error = match serde_json::from_str::(message) { - Ok(review_result) => return Ok(review_result), - Err(error) => error, - }; - - // A markdown code fence or a sentence around the object is tolerated, with - // a warning: the contract asks for a raw JSON object. - if let Some(json) = json_object(message) - && let Ok(review_result) = serde_json::from_str::(json) - { - warn!( - "Model answer is not a raw JSON object, it was extracted from the surrounding text" - ); - - return Ok(review_result); - } - - // The reason of the rejection is logged along with the answer: without it, - // a broken answer is impossible to diagnose. - warn!( - answer = %excerpt(message, MAX_LOGGED_ANSWER), - reason = %error, - "Model answer is not the expected JSON" - ); - - anyhow::bail!("the answer is not valid JSON: {error}") - } -} - -/// Returns the outermost `{...}` of an answer, which ignores a markdown code -/// fence or any text around it. -fn json_object(message: &str) -> Option<&str> { - let start = message.find('{')?; - let end = message.rfind('}')?; - - (start < end).then(|| &message[start..=end]) -} - -/// Resolves the side each review is anchored on and drops the reviews that do -/// not match a line the pull request changes. -/// -/// The model is asked to pick a line and a side from the provided lists, but -/// nothing forces it to, and Gitea accepts any position: a wrong one places the -/// comment on an unrelated line of the file instead of failing. A review that -/// omits its side is resolved from the lists, and one that matches no changed -/// line is dropped. -fn resolve_review_sides(review_result: &mut ReviewResult, changed_lines: &ChangedLines) { - let mut dropped = Vec::new(); - - review_result.reviews.retain_mut(|review| { - let side = review.line.and_then(|line| { - let file = changed_lines - .iter() - .find(|file| file.filename == review.filename)?; - - let side = review.side.or_else(|| file.side_of(line))?; - - file.lines(side).contains(&line).then_some(side) - }); - - match side { - Some(side) => { - review.side = Some(side); - true - } - None => { - dropped.push(match review.line { - Some(line) => format!("{}:{line}", review.filename), - None => format!("{}:no line", review.filename), - }); - false - } - } - }); - - if !dropped.is_empty() { - warn!( - dropped = dropped.len(), - reviews = %dropped.join(", "), - "Dropped reviews that are not anchored on a changed line" - ); - } -} - -/// Extracts the old and new starting line numbers of a hunk header such as -/// `@@ -12,3 +12,5 @@`. The counts are optional and git may append a section -/// heading after the closing `@@`. -fn parse_hunk_starts(hunk_header: &str) -> Option<(u64, u64)> { - let body = hunk_header.strip_prefix("@@ ")?; - let body = body.split(" @@").next()?; - - let (old, new) = body.split_once(" +")?; - let old = old.strip_prefix('-')?.split(',').next()?; - let new = new.split(',').next()?; - - Some((old.parse().ok()?, new.parse().ok()?)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::bot::ReviewItem; - - /// Additions, a removal and a line changed on both sides. - const DIFF: &str = concat!( - "diff --git a/src/foo.rs b/src/foo.rs\n", - "--- a/src/foo.rs\n", - "+++ b/src/foo.rs\n", - "@@ -1,4 +1,6 @@\n", - " fn main() {\n", - "+ let x = 1;\n", - "- let removed = 0;\n", - " println!(\"hello\");\n", - "+ let y = 2;\n", - "+ let z = 3;\n", - " }\n", - "diff --git a/src/bar.rs b/src/bar.rs\n", - "--- a/src/bar.rs\n", - "+++ b/src/bar.rs\n", - "@@ -10,4 +10,6 @@\n", - " old context\n", - "+ let a = 10;\n", - " more context\n", - "+ let b = 20;\n", - ); - - /// A pull request that only deletes a file. - const DELETION_ONLY: &str = concat!( - "diff --git a/src/old.rs b/src/old.rs\n", - "deleted file mode 100644\n", - "--- a/src/old.rs\n", - "+++ /dev/null\n", - "@@ -1,3 +0,0 @@\n", - "-fn a() {}\n", - "-fn b() {}\n", - "-fn c() {}\n", - ); - - /// A hunk whose content starts with `+++` / `---`, which must not be taken - /// for the file headers. - const TRICKY_CONTENT: &str = concat!( - "diff --git a/src/tricky.js b/src/tricky.js\n", - "--- a/src/tricky.js\n", - "+++ b/src/tricky.js\n", - "@@ -1,4 +1,4 @@\n", - " let i = 0;\n", - "+++i;\n", - "---x;\n", - " console.log(i);\n", - ); - - /// A pull request that renames a file. - const RENAMED: &str = concat!( - "diff --git a/src/old.rs b/src/new.rs\n", - "similarity index 50%\n", - "rename from src/old.rs\n", - "rename to src/new.rs\n", - "--- a/src/old.rs\n", - "+++ b/src/new.rs\n", - "@@ -1,1 +1,1 @@\n", - "-fn old() {}\n", - "+fn new() {}\n", - ); - - /// A file whose path contains a space, which the `diff --git` line cannot - /// express without ambiguity. - const PATH_WITH_SPACE: &str = concat!( - "diff --git a/src/my file.rs b/src/my file.rs\n", - "--- a/src/my file.rs\n", - "+++ b/src/my file.rs\n", - "@@ -1,1 +1,2 @@\n", - " fn a() {}\n", - "+fn b() {}\n", - ); - - /// A file whose path git quotes and escapes (`caf\\303\\251.md` is - /// `caf\u{e9}.md`). - const QUOTED_PATH: &str = concat!( - "diff --git \"a/docs/caf\\303\\251.md\" \"b/docs/caf\\303\\251.md\"\n", - "--- \"a/docs/caf\\303\\251.md\"\n", - "+++ \"b/docs/caf\\303\\251.md\"\n", - "@@ -1,1 +1,2 @@\n", - " intro\n", - "+ajout\n", - ); - - fn review(filename: &str, line: Option, side: Option) -> ReviewItem { - ReviewItem { - filename: String::from(filename), - line, - side, - severity: None, - message: String::from("message"), - } - } - - fn review_result(reviews: Vec) -> ReviewResult { - ReviewResult { - reviews, - comment: String::new(), - cost: None, - } - } - - fn pull_request_file( - filename: &str, - previous_filename: Option<&str>, - status: &str, - ) -> PullRequestFile { - PullRequestFile { - filename: String::from(filename), - previous_filename: previous_filename.map(String::from), - status: String::from(status), - } - } - - #[test] - fn changed_lines_are_listed_per_file_and_side() { - let expected = concat!( - "src/foo.rs: added 2, 4, 5 / removed 2\n", - "src/bar.rs: added 11, 13" - ); - - assert_eq!(format_changed_lines(&parse_changed_lines(DIFF)), expected); - } - - #[test] - fn a_deletion_only_pull_request_lists_removed_lines() { - let expected = "src/old.rs: removed 1, 2, 3"; - - assert_eq!( - format_changed_lines(&parse_changed_lines(DELETION_ONLY)), - expected - ); - } - - #[test] - fn hunk_content_starting_with_plus_or_minus_is_counted() { - let expected = "src/tricky.js: added 2 / removed 2"; - - assert_eq!( - format_changed_lines(&parse_changed_lines(TRICKY_CONTENT)), - expected - ); - } - - #[test] - fn a_renamed_file_uses_its_new_path() { - let expected = "src/new.rs: added 1 / removed 1"; - - assert_eq!( - format_changed_lines(&parse_changed_lines(RENAMED)), - expected - ); - } - - #[test] - fn a_path_with_a_space_is_read_from_the_headers() { - let expected = "src/my file.rs: added 2"; - - assert_eq!( - format_changed_lines(&parse_changed_lines(PATH_WITH_SPACE)), - expected - ); - } - - #[test] - fn a_quoted_path_is_decoded() { - let expected = "docs/café.md: added 2"; - - assert_eq!( - format_changed_lines(&parse_changed_lines(QUOTED_PATH)), - expected - ); - } - - #[test] - fn filenames_are_resolved_against_the_api_list() { - let mut changed_lines = parse_changed_lines(DIFF); - let files = vec![ - pull_request_file("src/bar.rs", None, "modified"), - pull_request_file("src/foo.rs", Some("src/renamed.rs"), "renamed"), - ]; - - resolve_filenames(&mut changed_lines, &files); - - assert_eq!(changed_lines[0].filename, "src/foo.rs"); - assert_eq!(changed_lines[1].filename, "src/bar.rs"); - } - - #[test] - fn a_file_absent_from_the_api_list_keeps_the_diff_path() { - let mut changed_lines = parse_changed_lines(DIFF); - let files = vec![pull_request_file("src/bar.rs", None, "modified")]; - - resolve_filenames(&mut changed_lines, &files); - - assert_eq!(changed_lines[0].filename, "src/foo.rs"); - assert_eq!(changed_lines[1].filename, "src/bar.rs"); - } - - #[test] - fn changes_describe_the_files_then_the_lines() { - let changed_lines = parse_changed_lines(DELETION_ONLY); - let files = vec![pull_request_file("src/old.rs", None, "deleted")]; - - let expected = concat!( - "Files changed by the pull request: src/old.rs (deleted)\n", - "\n", - "Lines to review, per file:\n", - "src/old.rs: removed 1, 2, 3" - ); - - assert_eq!(format_changes(&files, &changed_lines), expected); - } - - #[test] - fn changes_without_the_api_list_only_hold_the_lines() { - let changed_lines = parse_changed_lines(DELETION_ONLY); - - let expected = "Lines to review, per file:\nsrc/old.rs: removed 1, 2, 3"; - - assert_eq!(format_changes(&[], &changed_lines), expected); - } - - #[test] - fn generated_lockfiles_are_ignored() { - assert!(is_ignored("Cargo.lock")); - assert!(is_ignored("crates/steep-server/Cargo.lock")); - assert!(is_ignored("package-lock.json")); - assert!(is_ignored("web/yarn.lock")); - assert!(is_ignored("go.sum")); - - assert!(!is_ignored("src/main.rs")); - assert!(!is_ignored("Cargo.toml")); - assert!(!is_ignored("docs/lockfiles.md")); - } - - #[test] - fn generated_files_are_dropped_from_the_changed_lines() { - const DIFF_WITH_LOCKFILE: &str = concat!( - "diff --git a/Cargo.lock b/Cargo.lock\n", - "--- a/Cargo.lock\n", - "+++ b/Cargo.lock\n", - "@@ -1,1 +1,2 @@\n", - " [[package]]\n", - "+name = \"x\"\n", - "diff --git a/src/foo.rs b/src/foo.rs\n", - "--- a/src/foo.rs\n", - "+++ b/src/foo.rs\n", - "@@ -1,1 +1,2 @@\n", - " fn a() {}\n", - "+fn b() {}\n", - ); - - let mut changed_lines = parse_changed_lines(DIFF_WITH_LOCKFILE); - drop_generated_files(&mut changed_lines); - - assert_eq!(format_changed_lines(&changed_lines), "src/foo.rs: added 2"); - } - - #[test] - fn a_generated_file_is_not_described_to_the_model() { - let files = vec![ - pull_request_file("Cargo.lock", None, "modified"), - pull_request_file("src/foo.rs", None, "modified"), - ]; - let changed_lines = parse_changed_lines(DIFF); - - let changes = format_changes(&files, &changed_lines); - - assert!(!changes.contains("Cargo.lock")); - assert!(changes.contains("src/foo.rs (modified)")); - } - - #[test] - fn a_renamed_file_is_described_with_its_previous_path() { - let file = pull_request_file("src/new.rs", Some("src/old.rs"), "renamed"); - - assert_eq!(describe_file(&file), "src/new.rs (renamed from src/old.rs)"); - } - - #[test] - fn reviews_keep_their_changed_line_and_resolve_their_side() { - let changed_lines = parse_changed_lines(DIFF); - let mut review_result = review_result(vec![ - review("src/foo.rs", Some(4), Some(ReviewSide::Added)), - review("src/foo.rs", Some(2), Some(ReviewSide::Removed)), - review("src/bar.rs", Some(13), None), - ]); - - resolve_review_sides(&mut review_result, &changed_lines); - - let sides = review_result - .reviews - .iter() - .map(|review| review.side) - .collect::>(); - - assert_eq!( - sides, - vec![ - Some(ReviewSide::Added), - Some(ReviewSide::Removed), - Some(ReviewSide::Added) - ] - ); - } - - #[test] - fn a_line_changed_on_both_sides_defaults_to_added() { - let changed_lines = parse_changed_lines(DIFF); - let mut review_result = review_result(vec![review("src/foo.rs", Some(2), None)]); - - resolve_review_sides(&mut review_result, &changed_lines); - - assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Added)); - } - - #[test] - fn reviews_outside_changed_lines_are_dropped() { - let changed_lines = parse_changed_lines(DIFF); - let mut review_result = review_result(vec![ - // valid - review("src/foo.rs", Some(2), Some(ReviewSide::Added)), - // a line that exists but is not part of the change - review("src/foo.rs", Some(3), None), - // a line beyond the change - review("src/foo.rs", Some(999), None), - // a changed line, but on the wrong side - review("src/foo.rs", Some(4), Some(ReviewSide::Removed)), - // a line changed in another file - review("src/foo.rs", Some(11), None), - // an unknown file - review("src/baz.rs", Some(1), None), - // no line at all - review("src/bar.rs", None, None), - ]); - - resolve_review_sides(&mut review_result, &changed_lines); - - assert_eq!(review_result.reviews.len(), 1); - assert_eq!(review_result.reviews[0].filename, "src/foo.rs"); - assert_eq!(review_result.reviews[0].line, Some(2)); - assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Added)); - } - - #[test] - fn a_deleted_line_is_kept_as_a_removed_anchor() { - let changed_lines = parse_changed_lines(DELETION_ONLY); - let mut review_result = review_result(vec![ - review("src/old.rs", Some(2), Some(ReviewSide::Removed)), - review("src/old.rs", Some(2), Some(ReviewSide::Added)), - ]); - - resolve_review_sides(&mut review_result, &changed_lines); - - assert_eq!(review_result.reviews.len(), 1); - assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Removed)); - } - - #[test] - fn a_raw_answer_is_parsed() { - let answer = r#"{"reviews": [], "comment": "ok"}"#; - - assert_eq!(answer.parse::().unwrap().comment, "ok"); - } - - #[test] - fn a_fenced_answer_is_extracted() { - let answer = concat!( - "Voici ma review :\n", - "```json\n", - "{\"reviews\": [], \"comment\": \"rien à signaler\"}\n", - "```\n" - ); - - assert_eq!( - answer.parse::().unwrap().comment, - "rien à signaler" - ); - } - - #[test] - fn a_sentence_around_the_object_is_ignored() { - let answer = r#"Rien à signaler. {"reviews": [], "comment": "ok"} Bonne journée !"#; - - assert_eq!(answer.parse::().unwrap().comment, "ok"); - } - - #[test] - fn an_answer_without_json_is_rejected() { - assert!("Je n'ai rien relevé.".parse::().is_err()); - } - - #[test] - fn an_answer_that_is_an_object_but_not_a_review_is_rejected() { - assert!(r#"{"message": "LGTM"}"#.parse::().is_err()); - } - - #[test] - fn odd_sides_from_the_model_are_tolerated() { - let changed_lines = parse_changed_lines(DIFF); - let mut review_result: ReviewResult = serde_json::from_str( - r#"{ - "reviews": [ - { "filename": "src/foo.rs", "line": 4, "side": "Added", "message": "a" }, - { "filename": "src/foo.rs", "line": 2, "side": "new", "message": "b" }, - { "filename": "src/foo.rs", "line": 2, "side": "REMOVED", "message": "c" }, - { "filename": "src/foo.rs", "line": 2, "side": "banana", "message": "d" }, - { "filename": "src/foo.rs", "line": 5, "message": "e" } - ], - "comment": "" - }"#, - ) - .unwrap(); - - resolve_review_sides(&mut review_result, &changed_lines); - - let sides = review_result - .reviews - .iter() - .map(|review| review.side) - .collect::>(); - - assert_eq!( - sides, - vec![ - Some(ReviewSide::Added), - Some(ReviewSide::Added), - Some(ReviewSide::Removed), - Some(ReviewSide::Added), - Some(ReviewSide::Added) - ] - ); - } - - #[test] - fn severities_are_read_from_the_model() { - let result: ReviewResult = serde_json::from_str( - r#"{ - "reviews": [ - { "filename": "a", "line": 1, "severity": "Bug", "message": "x" }, - { "filename": "b", "line": 2, "severity": "SECURITY", "message": "x" }, - { "filename": "c", "line": 3, "severity": "perf", "message": "x" }, - { "filename": "d", "line": 4, "severity": "banana", "message": "x" } - ], - "comment": "" - }"#, - ) - .unwrap(); - - let severities = result - .reviews - .iter() - .map(|review| review.severity) - .collect::>(); - - assert_eq!( - severities, - vec![ - Some(ReviewSeverity::Bug), - Some(ReviewSeverity::Security), - Some(ReviewSeverity::Performance), - None, - ] - ); - } - - #[test] - fn the_breakdown_counts_severities_most_severe_first() { - let reviews = vec![ - ReviewItem { - severity: Some(ReviewSeverity::Maintainability), - ..review("a", Some(1), None) - }, - ReviewItem { - severity: Some(ReviewSeverity::Bug), - ..review("b", Some(2), None) - }, - ReviewItem { - severity: Some(ReviewSeverity::Bug), - ..review("c", Some(3), None) - }, - // A severity the model left out counts as maintainability. - review("d", Some(4), None), - ]; - - assert_eq!(severity_breakdown(&reviews), "- 2 bug\n- 2 maintainability"); - } - - #[test] - fn the_markdown_breaks_the_issues_down_by_severity() { - let mut result = review_result(vec![ - ReviewItem { - severity: Some(ReviewSeverity::Bug), - ..review("a", Some(1), None) - }, - ReviewItem { - severity: Some(ReviewSeverity::Performance), - ..review("b", Some(2), None) - }, - ]); - result.comment = String::from("Le découpage en crates est propre."); - - let markdown = review_result_to_markdown(&result); - - assert!(markdown.contains("- 1 bug")); - assert!(markdown.contains("- 1 performance")); - assert!(markdown.contains("Le découpage en crates est propre.")); - } -} diff --git a/crates/steep-server/src/bot_actions/review/anchor.rs b/crates/steep-server/src/bot_actions/review/anchor.rs new file mode 100644 index 0000000..0691188 --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/anchor.rs @@ -0,0 +1,177 @@ +//! Anchoring of the model's reviews on the lines a pull request changes. + +use tracing::warn; + +use super::diff::ChangedLines; +use crate::domain::review::ReviewResult; + +/// Resolves the side each review is anchored on and drops the reviews that do +/// not match a line the pull request changes. +/// +/// The model is asked to pick a line and a side from the provided lists, but +/// nothing forces it to, and Gitea accepts any position: a wrong one places the +/// comment on an unrelated line of the file instead of failing. A review that +/// omits its side is resolved from the lists, and one that matches no changed +/// line is dropped. +pub(super) fn resolve_review_sides(review_result: &mut ReviewResult, changed_lines: &ChangedLines) { + let mut dropped = Vec::new(); + + review_result.reviews.retain_mut(|review| { + let side = review.line.and_then(|line| { + let file = changed_lines + .iter() + .find(|file| file.filename == review.filename)?; + + let side = review.side.or_else(|| file.side_of(line))?; + + file.lines(side).contains(&line).then_some(side) + }); + + match side { + Some(side) => { + review.side = Some(side); + true + } + None => { + dropped.push(match review.line { + Some(line) => format!("{}:{line}", review.filename), + None => format!("{}:no line", review.filename), + }); + false + } + } + }); + + if !dropped.is_empty() { + warn!( + dropped = dropped.len(), + reviews = %dropped.join(", "), + "Dropped reviews that are not anchored on a changed line" + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + bot_actions::review::{diff::parse_changed_lines, fixtures::*}, + domain::review::ReviewSide, + }; + + #[test] + fn reviews_keep_their_changed_line_and_resolve_their_side() { + let changed_lines = parse_changed_lines(DIFF); + let mut review_result = review_result(vec![ + review("src/foo.rs", Some(4), Some(ReviewSide::Added)), + review("src/foo.rs", Some(2), Some(ReviewSide::Removed)), + review("src/bar.rs", Some(13), None), + ]); + + resolve_review_sides(&mut review_result, &changed_lines); + + let sides = review_result + .reviews + .iter() + .map(|review| review.side) + .collect::>(); + + assert_eq!( + sides, + vec![ + Some(ReviewSide::Added), + Some(ReviewSide::Removed), + Some(ReviewSide::Added) + ] + ); + } + + #[test] + fn a_line_changed_on_both_sides_defaults_to_added() { + let changed_lines = parse_changed_lines(DIFF); + let mut review_result = review_result(vec![review("src/foo.rs", Some(2), None)]); + + resolve_review_sides(&mut review_result, &changed_lines); + + assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Added)); + } + + #[test] + fn reviews_outside_changed_lines_are_dropped() { + let changed_lines = parse_changed_lines(DIFF); + let mut review_result = review_result(vec![ + // valid + review("src/foo.rs", Some(2), Some(ReviewSide::Added)), + // a line that exists but is not part of the change + review("src/foo.rs", Some(3), None), + // a line beyond the change + review("src/foo.rs", Some(999), None), + // a changed line, but on the wrong side + review("src/foo.rs", Some(4), Some(ReviewSide::Removed)), + // a line changed in another file + review("src/foo.rs", Some(11), None), + // an unknown file + review("src/baz.rs", Some(1), None), + // no line at all + review("src/bar.rs", None, None), + ]); + + resolve_review_sides(&mut review_result, &changed_lines); + + assert_eq!(review_result.reviews.len(), 1); + assert_eq!(review_result.reviews[0].filename, "src/foo.rs"); + assert_eq!(review_result.reviews[0].line, Some(2)); + assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Added)); + } + + #[test] + fn a_deleted_line_is_kept_as_a_removed_anchor() { + let changed_lines = parse_changed_lines(DELETION_ONLY); + let mut review_result = review_result(vec![ + review("src/old.rs", Some(2), Some(ReviewSide::Removed)), + review("src/old.rs", Some(2), Some(ReviewSide::Added)), + ]); + + resolve_review_sides(&mut review_result, &changed_lines); + + assert_eq!(review_result.reviews.len(), 1); + assert_eq!(review_result.reviews[0].side, Some(ReviewSide::Removed)); + } + + #[test] + fn odd_sides_from_the_model_are_tolerated() { + let changed_lines = parse_changed_lines(DIFF); + let mut review_result: ReviewResult = serde_json::from_str( + r#"{ + "reviews": [ + { "filename": "src/foo.rs", "line": 4, "side": "Added", "message": "a" }, + { "filename": "src/foo.rs", "line": 2, "side": "new", "message": "b" }, + { "filename": "src/foo.rs", "line": 2, "side": "REMOVED", "message": "c" }, + { "filename": "src/foo.rs", "line": 2, "side": "banana", "message": "d" }, + { "filename": "src/foo.rs", "line": 5, "message": "e" } + ], + "comment": "" + }"#, + ) + .unwrap(); + + resolve_review_sides(&mut review_result, &changed_lines); + + let sides = review_result + .reviews + .iter() + .map(|review| review.side) + .collect::>(); + + assert_eq!( + sides, + vec![ + Some(ReviewSide::Added), + Some(ReviewSide::Added), + Some(ReviewSide::Removed), + Some(ReviewSide::Added), + Some(ReviewSide::Added) + ] + ); + } +} diff --git a/crates/steep-server/src/bot_actions/review/diff.rs b/crates/steep-server/src/bot_actions/review/diff.rs new file mode 100644 index 0000000..3a0bc5a --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/diff.rs @@ -0,0 +1,389 @@ +//! Parsing of a pull request unified diff into the lines it changes. + +use tracing::warn; + +use crate::{domain::review::ReviewSide, gitea::PullRequestFile}; + +/// The lines a pull request changed, per file. +/// +/// Line numbers are the ones Gitea expects to place a review comment: +/// [`ReviewSide::Added`] numbers refer to the new version of the file (sent as +/// `new_position`), [`ReviewSide::Removed`] numbers to the old version (sent as +/// `old_position`). +pub(super) type ChangedLines = Vec; + +pub(super) struct ChangedFile { + pub(super) filename: String, + pub(super) added: Vec, + pub(super) removed: Vec, +} + +impl ChangedFile { + fn new(filename: &str) -> Self { + Self { + filename: String::from(filename), + added: Vec::new(), + removed: Vec::new(), + } + } + + /// Lines that can be commented on for the given side. + pub(super) fn lines(&self, side: ReviewSide) -> &[u64] { + match side { + ReviewSide::Added => &self.added, + ReviewSide::Removed => &self.removed, + } + } + + /// Side a line belongs to, used when the model did not state one. + pub(super) fn side_of(&self, line: u64) -> Option { + if self.added.contains(&line) { + Some(ReviewSide::Added) + } else if self.removed.contains(&line) { + Some(ReviewSide::Removed) + } else { + None + } + } +} + +/// Lists the lines changed by the diff, per file, on both sides. +/// +/// Only the line numbers are kept: the model reads the code itself through the +/// sandbox tools. +pub(super) fn parse_changed_lines(git_diff: &str) -> ChangedLines { + let mut files = Vec::new(); + let mut current_file: Option = None; + let mut in_hunk = false; + let mut old_line: u64 = 0; + let mut new_line: u64 = 0; + + for line in git_diff.lines() { + if line.starts_with("diff --git ") { + current_file = None; + in_hunk = false; + continue; + } + + // `--- a/x` and `+++ b/x` only appear before the first hunk of a file. + // Inside a hunk, a line may legitimately start with them: removing + // `--x` gives `---x`, adding `++i;` gives `+++i;`. + if !in_hunk && (line.starts_with("--- ") || line.starts_with("+++ ")) { + // A header is never a content line: `+++ /dev/null` on a deleted + // file keeps the path of the other side in place. + if let Some(path) = header_file_path(line) { + current_file = Some(path); + } + continue; + } + + if line.starts_with("@@") { + if let Some((old_start, new_start)) = parse_hunk_starts(line) { + old_line = old_start; + new_line = new_start; + in_hunk = true; + } + continue; + } + + let Some(filename) = current_file.as_deref() else { + continue; + }; + + match line.as_bytes().first() { + Some(b' ') => { + old_line += 1; + new_line += 1; + } + Some(b'-') => { + changed_file(&mut files, filename).removed.push(old_line); + old_line += 1; + } + Some(b'+') => { + changed_file(&mut files, filename).added.push(new_line); + new_line += 1; + } + // `\ No newline at end of file`, and anything unexpected: a line + // that advances neither side. + _ => {} + } + } + + files +} + +/// Path of the file on one side of the diff, from a `--- a/` or +/// `+++ b/` header line. +/// +/// These lines are the only unambiguous source for the path: the `diff --git` +/// line is cut at the first space, and it names the old path of a renamed file. +/// `None` for `/dev/null`, which leaves the path of the other side in place. +fn header_file_path(line: &str) -> Option { + let (prefix, raw) = match line.strip_prefix("--- ") { + Some(raw) => ("a/", raw), + None => ("b/", line.strip_prefix("+++ ")?), + }; + + let path = decode_git_path(raw); + + Some(String::from(path.strip_prefix(prefix)?)) +} + +/// Decodes a path as git writes it in a diff header: git wraps it in quotes and +/// escapes the bytes that need it (`\303\251` for `é`) when the path contains +/// non-printable or non-ASCII characters. +fn decode_git_path(raw: &str) -> String { + let Some(quoted) = raw.strip_prefix('"').and_then(|raw| raw.strip_suffix('"')) else { + return String::from(raw); + }; + + let bytes = quoted.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut index = 0; + + while let Some(byte) = bytes.get(index) { + index += 1; + + if *byte != b'\\' { + decoded.push(*byte); + continue; + } + + match bytes.get(index) { + // Octal escapes are the ones that matter for a path: git uses them + // for every non-ASCII byte. + Some(digit @ b'0'..=b'7') => { + let mut value = u32::from(digit - b'0'); + let mut digits = 1; + + while digits < 3 { + match bytes.get(index + digits) { + Some(next @ b'0'..=b'7') => { + value = value * 8 + u32::from(next - b'0'); + digits += 1; + } + _ => break, + } + } + + decoded.push(u8::try_from(value).unwrap_or(b'?')); + index += digits; + } + Some(escaped) => { + decoded.push(match escaped { + b't' => b'\t', + b'n' => b'\n', + b'r' => b'\r', + other => *other, + }); + index += 1; + } + None => decoded.push(b'\\'), + } + } + + String::from_utf8_lossy(&decoded).into_owned() +} + +/// Replaces the paths parsed from the diff with the exact paths reported by the +/// API, which are the ones the model sees in the sandbox. +pub(super) fn resolve_filenames(changed_lines: &mut ChangedLines, files: &[PullRequestFile]) { + for changed in changed_lines.iter_mut() { + let parsed = changed.filename.as_str(); + + let Some(file) = files.iter().find(|file| file.filename == parsed) else { + if !files.is_empty() { + warn!(path = %parsed, "Changed file is not in the pull request file list"); + } + continue; + }; + + changed.filename = file.filename.clone(); + } +} + +/// Basenames of generated files that are never reviewed. +/// +/// Their diffs are machine-written dependency churn: they run to thousands of +/// lines, flood the prompt with line numbers and drown the code the model should +/// look at. A lockfile is also never a place where a review comment belongs. +const IGNORED_FILE_NAMES: [&str; 16] = [ + "Cargo.lock", + "package-lock.json", + "npm-shrinkwrap.json", + "yarn.lock", + "pnpm-lock.yaml", + "bun.lock", + "bun.lockb", + "composer.lock", + "Gemfile.lock", + "poetry.lock", + "uv.lock", + "Pipfile.lock", + "go.sum", + "packages.lock.json", + "flake.lock", + "pubspec.lock", +]; + +/// Whether a changed path is a generated file that is never reviewed. +/// +/// The comparison is on the basename, so a lockfile nested in a workspace (for +/// example `crates/foo/Cargo.lock`) is matched too. +pub(super) fn is_ignored(filename: &str) -> bool { + let basename = filename.rsplit('/').next().unwrap_or(filename); + + IGNORED_FILE_NAMES.contains(&basename) +} + +/// Drops the generated files from the changed lines. +/// +/// Removing them here keeps the prompt free of their line numbers, and also makes +/// [`resolve_review_sides`] reject any review the model anchors on one of them. +pub(super) fn drop_generated_files(changed_lines: &mut ChangedLines) { + changed_lines.retain(|file| !is_ignored(&file.filename)); +} + +/// Borrows the entry of `files` for a file, creating it on its first change. +fn changed_file<'a>(files: &'a mut ChangedLines, filename: &str) -> &'a mut ChangedFile { + let index = match files.iter().position(|file| file.filename == filename) { + Some(index) => index, + None => { + files.push(ChangedFile::new(filename)); + files.len() - 1 + } + }; + + &mut files[index] +} + +/// Extracts the old and new starting line numbers of a hunk header such as +/// `@@ -12,3 +12,5 @@`. The counts are optional and git may append a section +/// heading after the closing `@@`. +fn parse_hunk_starts(hunk_header: &str) -> Option<(u64, u64)> { + let body = hunk_header.strip_prefix("@@ ")?; + let body = body.split(" @@").next()?; + + let (old, new) = body.split_once(" +")?; + let old = old.strip_prefix('-')?.split(',').next()?; + let new = new.split(',').next()?; + + Some((old.parse().ok()?, new.parse().ok()?)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bot_actions::review::{fixtures::*, prompt::format_changed_lines}; + + #[test] + fn a_deletion_only_pull_request_lists_removed_lines() { + let expected = "src/old.rs: removed 1, 2, 3"; + + assert_eq!( + format_changed_lines(&parse_changed_lines(DELETION_ONLY)), + expected + ); + } + + #[test] + fn hunk_content_starting_with_plus_or_minus_is_counted() { + let expected = "src/tricky.js: added 2 / removed 2"; + + assert_eq!( + format_changed_lines(&parse_changed_lines(TRICKY_CONTENT)), + expected + ); + } + + #[test] + fn a_renamed_file_uses_its_new_path() { + let expected = "src/new.rs: added 1 / removed 1"; + + assert_eq!( + format_changed_lines(&parse_changed_lines(RENAMED)), + expected + ); + } + + #[test] + fn a_path_with_a_space_is_read_from_the_headers() { + let expected = "src/my file.rs: added 2"; + + assert_eq!( + format_changed_lines(&parse_changed_lines(PATH_WITH_SPACE)), + expected + ); + } + + #[test] + fn a_quoted_path_is_decoded() { + let expected = "docs/café.md: added 2"; + + assert_eq!( + format_changed_lines(&parse_changed_lines(QUOTED_PATH)), + expected + ); + } + + #[test] + fn filenames_are_resolved_against_the_api_list() { + let mut changed_lines = parse_changed_lines(DIFF); + let files = vec![ + pull_request_file("src/bar.rs", None, "modified"), + pull_request_file("src/foo.rs", Some("src/renamed.rs"), "renamed"), + ]; + + resolve_filenames(&mut changed_lines, &files); + + assert_eq!(changed_lines[0].filename, "src/foo.rs"); + assert_eq!(changed_lines[1].filename, "src/bar.rs"); + } + + #[test] + fn a_file_absent_from_the_api_list_keeps_the_diff_path() { + let mut changed_lines = parse_changed_lines(DIFF); + let files = vec![pull_request_file("src/bar.rs", None, "modified")]; + + resolve_filenames(&mut changed_lines, &files); + + assert_eq!(changed_lines[0].filename, "src/foo.rs"); + assert_eq!(changed_lines[1].filename, "src/bar.rs"); + } + + #[test] + fn generated_lockfiles_are_ignored() { + assert!(is_ignored("Cargo.lock")); + assert!(is_ignored("crates/steep-server/Cargo.lock")); + assert!(is_ignored("package-lock.json")); + assert!(is_ignored("web/yarn.lock")); + assert!(is_ignored("go.sum")); + + assert!(!is_ignored("src/main.rs")); + assert!(!is_ignored("Cargo.toml")); + assert!(!is_ignored("docs/lockfiles.md")); + } + + #[test] + fn generated_files_are_dropped_from_the_changed_lines() { + const DIFF_WITH_LOCKFILE: &str = concat!( + "diff --git a/Cargo.lock b/Cargo.lock\n", + "--- a/Cargo.lock\n", + "+++ b/Cargo.lock\n", + "@@ -1,1 +1,2 @@\n", + " [[package]]\n", + "+name = \"x\"\n", + "diff --git a/src/foo.rs b/src/foo.rs\n", + "--- a/src/foo.rs\n", + "+++ b/src/foo.rs\n", + "@@ -1,1 +1,2 @@\n", + " fn a() {}\n", + "+fn b() {}\n", + ); + + let mut changed_lines = parse_changed_lines(DIFF_WITH_LOCKFILE); + drop_generated_files(&mut changed_lines); + + assert_eq!(format_changed_lines(&changed_lines), "src/foo.rs: added 2"); + } +} diff --git a/crates/steep-server/src/bot_actions/review/fixtures.rs b/crates/steep-server/src/bot_actions/review/fixtures.rs new file mode 100644 index 0000000..a90a29d --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/fixtures.rs @@ -0,0 +1,119 @@ +//! Shared fixtures of the review tests. + +use crate::{ + domain::review::{ReviewItem, ReviewResult, ReviewSide}, + gitea::PullRequestFile, +}; + +/// Additions, a removal and a line changed on both sides. +pub(super) const DIFF: &str = concat!( + "diff --git a/src/foo.rs b/src/foo.rs\n", + "--- a/src/foo.rs\n", + "+++ b/src/foo.rs\n", + "@@ -1,4 +1,6 @@\n", + " fn main() {\n", + "+ let x = 1;\n", + "- let removed = 0;\n", + " println!(\"hello\");\n", + "+ let y = 2;\n", + "+ let z = 3;\n", + " }\n", + "diff --git a/src/bar.rs b/src/bar.rs\n", + "--- a/src/bar.rs\n", + "+++ b/src/bar.rs\n", + "@@ -10,4 +10,6 @@\n", + " old context\n", + "+ let a = 10;\n", + " more context\n", + "+ let b = 20;\n", +); + +/// A pull request that only deletes a file. +pub(super) const DELETION_ONLY: &str = concat!( + "diff --git a/src/old.rs b/src/old.rs\n", + "deleted file mode 100644\n", + "--- a/src/old.rs\n", + "+++ /dev/null\n", + "@@ -1,3 +0,0 @@\n", + "-fn a() {}\n", + "-fn b() {}\n", + "-fn c() {}\n", +); + +/// A hunk whose content starts with `+++` / `---`, which must not be taken +/// for the file headers. +pub(super) const TRICKY_CONTENT: &str = concat!( + "diff --git a/src/tricky.js b/src/tricky.js\n", + "--- a/src/tricky.js\n", + "+++ b/src/tricky.js\n", + "@@ -1,4 +1,4 @@\n", + " let i = 0;\n", + "+++i;\n", + "---x;\n", + " console.log(i);\n", +); + +/// A pull request that renames a file. +pub(super) const RENAMED: &str = concat!( + "diff --git a/src/old.rs b/src/new.rs\n", + "similarity index 50%\n", + "rename from src/old.rs\n", + "rename to src/new.rs\n", + "--- a/src/old.rs\n", + "+++ b/src/new.rs\n", + "@@ -1,1 +1,1 @@\n", + "-fn old() {}\n", + "+fn new() {}\n", +); + +/// A file whose path contains a space, which the `diff --git` line cannot +/// express without ambiguity. +pub(super) const PATH_WITH_SPACE: &str = concat!( + "diff --git a/src/my file.rs b/src/my file.rs\n", + "--- a/src/my file.rs\n", + "+++ b/src/my file.rs\n", + "@@ -1,1 +1,2 @@\n", + " fn a() {}\n", + "+fn b() {}\n", +); + +/// A file whose path git quotes and escapes (`caf\\303\\251.md` is +/// `caf\u{e9}.md`). +pub(super) const QUOTED_PATH: &str = concat!( + "diff --git \"a/docs/caf\\303\\251.md\" \"b/docs/caf\\303\\251.md\"\n", + "--- \"a/docs/caf\\303\\251.md\"\n", + "+++ \"b/docs/caf\\303\\251.md\"\n", + "@@ -1,1 +1,2 @@\n", + " intro\n", + "+ajout\n", +); + +pub(super) fn review(filename: &str, line: Option, side: Option) -> ReviewItem { + ReviewItem { + filename: String::from(filename), + line, + side, + severity: None, + message: String::from("message"), + } +} + +pub(super) fn review_result(reviews: Vec) -> ReviewResult { + ReviewResult { + reviews, + comment: String::new(), + cost: None, + } +} + +pub(super) fn pull_request_file( + filename: &str, + previous_filename: Option<&str>, + status: &str, +) -> PullRequestFile { + PullRequestFile { + filename: String::from(filename), + previous_filename: previous_filename.map(String::from), + status: String::from(status), + } +} diff --git a/crates/steep-server/src/bot_actions/review/mod.rs b/crates/steep-server/src/bot_actions/review/mod.rs new file mode 100644 index 0000000..46c6c8e --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/mod.rs @@ -0,0 +1,210 @@ +//! The pull request review action. +//! +//! [`exec_review`] drives one review end to end. The work is split by concern: +//! - [`diff`] parses the pull request diff into the lines it changes, +//! - [`prompt`] renders those changes for the model, +//! - [`anchor`] checks that the model's reviews land on changed lines, +//! - [`render`] builds the markdown summary posted on the pull request. + +mod anchor; +mod diff; +mod prompt; +mod render; + +#[cfg(test)] +mod fixtures; + +use tracing::{info, instrument, warn}; + +use crate::{ + consts::{BOT_PROCESS_MSG, REVIEW_PROMPT, SANDBOX_SYSTEM_PROMPT}, + domain::review::ReviewResult, + event_bus::Reporter, + gitea::{GiteaAPI, ReviewPayload}, + metrics, + open_router::{OpenRouterClient, Tool}, + sandbox::{Sandbox, SandboxConfig, agent}, +}; + +use self::{ + anchor::resolve_review_sides, + diff::{drop_generated_files, parse_changed_lines, resolve_filenames}, + prompt::format_changes, + render::review_result_to_markdown, +}; + +/// The services a review needs, borrowed from the bot for the duration of one job. +pub struct ReviewContext<'a> { + pub gitea: &'a GiteaAPI, + pub open_router: &'a OpenRouterClient, + pub sandbox: &'a SandboxConfig, + pub reporter: &'a Reporter, +} + +pub struct ReviewOutcome { + pub markdown: String, + pub cost: Option, + pub comments: u64, +} + +impl ReviewOutcome { + pub fn empty() -> Self { + Self { + markdown: String::new(), + cost: None, + comments: 0, + } + } +} + +#[instrument( + skip_all, + fields(repo = %payload.repository.full_name, pr = payload.pull_request.number) +)] +pub async fn exec_review( + ctx: &ReviewContext<'_>, + tools: Vec, + payload: &ReviewPayload, +) -> anyhow::Result { + info!(action = %payload.action, "Starting review"); + + let full_name = &payload.repository.full_name; + let index = payload.pull_request.number; + + let progress_comment = ctx + .gitea + .comment( + &BOT_PROCESS_MSG.replace("{model}", ctx.open_router.model()), + full_name, + index, + ) + .await?; + + let review = review_pull_request(ctx, tools, payload).await; + + // The progress comment is housekeeping: failing to update it must neither turn a + // posted review into a failure nor hide the error that made the review fail. + match review { + Ok((review_result, markdown)) => { + if let Err(err) = ctx + .gitea + .delete_comment(full_name, progress_comment.id) + .await + { + warn!(%err, "Failed to delete the progress comment"); + } + + Ok(ReviewOutcome { + markdown, + cost: review_result.cost, + comments: review_result.reviews.len() as u64, + }) + } + Err(err) => { + if let Err(edit_err) = ctx + .gitea + .edit_comment( + &format!("Error while reviewing: {err}"), + full_name, + progress_comment.id, + ) + .await + { + warn!(err = %edit_err, "Failed to report the error on the progress comment"); + } + + Err(err) + } + } +} + +/// Runs the review and posts it, returning the result and the markdown that was +/// posted. +async fn review_pull_request( + ctx: &ReviewContext<'_>, + tools: Vec, + payload: &ReviewPayload, +) -> anyhow::Result<(ReviewResult, String)> { + let full_name = &payload.repository.full_name; + let index = payload.pull_request.number; + + let git_diff = ctx.gitea.pull_request_diff(full_name, index).await?; + + // The file list only refines the paths and describes the changes: a failure is + // not fatal, the diff is the source of truth for the lines. + let files = match ctx.gitea.pull_request_files(full_name, index).await { + Ok(files) => files, + Err(err) => { + warn!(%err, "Failed to list the pull request files"); + Vec::new() + } + }; + + let mut changed_lines = parse_changed_lines(&git_diff); + drop_generated_files(&mut changed_lines); + resolve_filenames(&mut changed_lines, &files); + + let changes = format_changes(&files, &changed_lines); + + let bot_request = REVIEW_PROMPT + .replace("{subject}", &payload.pull_request.title) + .replace("{comment}", &payload.comment.body) + .replace("{changes}", &changes); + + let (mut review_result, cost) = run_sandboxed_review(ctx, tools, payload, &bot_request).await?; + + resolve_review_sides(&mut review_result, &changed_lines); + + review_result.cost = cost; + if let Some(cost) = review_result.cost { + metrics::openrouter_cost_usd(cost); + } + + let markdown = review_result_to_markdown(&review_result); + + ctx.gitea + .post_pull_request_review(&review_result, &markdown, full_name, index) + .await?; + + Ok((review_result, markdown)) +} + +async fn run_sandboxed_review( + ctx: &ReviewContext<'_>, + tools: Vec, + payload: &ReviewPayload, + bot_request: &str, +) -> anyhow::Result<(ReviewResult, Option)> { + let sandbox = Sandbox::create( + &ctx.sandbox.runtime, + &payload.repository.clone_url, + ctx.gitea.token(), + payload.pull_request.number, + ctx.reporter, + ) + .await?; + + let result = agent::run( + ctx.open_router, + &sandbox, + tools, + SANDBOX_SYSTEM_PROMPT, + bot_request, + ctx.sandbox.max_iterations, + ctx.reporter, + ) + .await; + + if let Err(err) = sandbox.cleanup().await { + warn!(%err, "Failed to clean up sandbox container"); + } + + let result = result?; + info!( + iterations = result.iterations, + cost = ?result.cost, + "Sandboxed review finished" + ); + + Ok((result.answer, result.cost)) +} diff --git a/crates/steep-server/src/bot_actions/review/prompt.rs b/crates/steep-server/src/bot_actions/review/prompt.rs new file mode 100644 index 0000000..c9b5e39 --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/prompt.rs @@ -0,0 +1,125 @@ +//! Rendering of a pull request's changes for the model. + +use super::diff::{ChangedLines, is_ignored}; +use crate::gitea::PullRequestFile; + +/// Renders the changes for the model: the files the pull request touches, then +/// the lines to review per file. +pub(super) fn format_changes(files: &[PullRequestFile], changed_lines: &ChangedLines) -> String { + let mut sections = Vec::new(); + + let described = files + .iter() + .filter(|file| !is_ignored(&file.filename)) + .map(describe_file) + .collect::>() + .join(", "); + + if !described.is_empty() { + sections.push(format!("Files changed by the pull request: {described}")); + } + + sections.push(format!( + "Lines to review, per file:\n{}", + format_changed_lines(changed_lines) + )); + + sections.join("\n\n") +} + +/// Describes a changed file for the model, including how it changed. +fn describe_file(file: &PullRequestFile) -> String { + match &file.previous_filename { + Some(previous) => format!("{} ({} from {})", file.filename, file.status, previous), + None => format!("{} ({})", file.filename, file.status), + } +} + +/// Renders the changed lines as `filename: added 1, 2 / removed 3`, one file per +/// line, keeping only the sides the file actually has. +pub(super) fn format_changed_lines(changed_lines: &ChangedLines) -> String { + changed_lines + .iter() + .map(|file| { + let mut sides = Vec::new(); + + for (label, lines) in [("added", &file.added), ("removed", &file.removed)] { + if !lines.is_empty() { + sides.push(format!("{label} {}", format_line_numbers(lines))); + } + } + + format!("{}: {}", file.filename, sides.join(" / ")) + }) + .collect::>() + .join("\n") +} + +fn format_line_numbers(lines: &[u64]) -> String { + lines + .iter() + .map(u64::to_string) + .collect::>() + .join(", ") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bot_actions::review::{diff::parse_changed_lines, fixtures::*}; + + #[test] + fn changed_lines_are_listed_per_file_and_side() { + let expected = concat!( + "src/foo.rs: added 2, 4, 5 / removed 2\n", + "src/bar.rs: added 11, 13" + ); + + assert_eq!(format_changed_lines(&parse_changed_lines(DIFF)), expected); + } + + #[test] + fn changes_describe_the_files_then_the_lines() { + let changed_lines = parse_changed_lines(DELETION_ONLY); + let files = vec![pull_request_file("src/old.rs", None, "deleted")]; + + let expected = concat!( + "Files changed by the pull request: src/old.rs (deleted)\n", + "\n", + "Lines to review, per file:\n", + "src/old.rs: removed 1, 2, 3" + ); + + assert_eq!(format_changes(&files, &changed_lines), expected); + } + + #[test] + fn changes_without_the_api_list_only_hold_the_lines() { + let changed_lines = parse_changed_lines(DELETION_ONLY); + + let expected = "Lines to review, per file:\nsrc/old.rs: removed 1, 2, 3"; + + assert_eq!(format_changes(&[], &changed_lines), expected); + } + + #[test] + fn a_generated_file_is_not_described_to_the_model() { + let files = vec![ + pull_request_file("Cargo.lock", None, "modified"), + pull_request_file("src/foo.rs", None, "modified"), + ]; + let changed_lines = parse_changed_lines(DIFF); + + let changes = format_changes(&files, &changed_lines); + + assert!(!changes.contains("Cargo.lock")); + assert!(changes.contains("src/foo.rs (modified)")); + } + + #[test] + fn a_renamed_file_is_described_with_its_previous_path() { + let file = pull_request_file("src/new.rs", Some("src/old.rs"), "renamed"); + + assert_eq!(describe_file(&file), "src/new.rs (renamed from src/old.rs)"); + } +} diff --git a/crates/steep-server/src/bot_actions/review/render.rs b/crates/steep-server/src/bot_actions/review/render.rs new file mode 100644 index 0000000..ed66520 --- /dev/null +++ b/crates/steep-server/src/bot_actions/review/render.rs @@ -0,0 +1,110 @@ +//! Markdown summary of a review, as posted on the pull request. + +use crate::domain::review::{ReviewItem, ReviewResult, ReviewSeverity}; + +pub(super) fn review_result_to_markdown(review_result: &ReviewResult) -> String { + if review_result.reviews.is_empty() { + return String::from("No issues found. ✅"); + } + + let mut md = String::from("## Review Feedback\n\n"); + + md.push_str(&format!( + "### {} issues found.\n\n", + review_result.reviews.len() + )); + + let breakdown = severity_breakdown(&review_result.reviews); + if !breakdown.is_empty() { + md.push_str(&breakdown); + md.push('\n'); + } + + if !review_result.comment.is_empty() { + md.push_str("\n---\n\n"); + md.push_str("### Summary\n\n"); + md.push_str(&review_result.comment); + md.push('\n'); + } + + if let Some(cost) = review_result.cost { + md.push_str("\n---\n\n"); + md.push_str(&format!("### Cost: ${}", cost)); + md.push('\n'); + } + + md +} + +/// Counts the reviews per severity, most severe first. +/// +/// A review whose severity the model omitted or wrote unreadably is counted as +/// [`ReviewSeverity::Maintainability`], the same fallback used when posting it. +fn severity_breakdown(reviews: &[ReviewItem]) -> String { + [ + ReviewSeverity::Bug, + ReviewSeverity::Security, + ReviewSeverity::Performance, + ReviewSeverity::Maintainability, + ] + .into_iter() + .filter_map(|severity| { + let count = reviews + .iter() + .filter(|review| review.severity.unwrap_or(ReviewSeverity::Maintainability) == severity) + .count(); + + (count > 0).then(|| format!("- {} {}", count, severity.label())) + }) + .collect::>() + .join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bot_actions::review::fixtures::*; + + #[test] + fn the_breakdown_counts_severities_most_severe_first() { + let reviews = vec![ + ReviewItem { + severity: Some(ReviewSeverity::Maintainability), + ..review("a", Some(1), None) + }, + ReviewItem { + severity: Some(ReviewSeverity::Bug), + ..review("b", Some(2), None) + }, + ReviewItem { + severity: Some(ReviewSeverity::Bug), + ..review("c", Some(3), None) + }, + // A severity the model left out counts as maintainability. + review("d", Some(4), None), + ]; + + assert_eq!(severity_breakdown(&reviews), "- 2 bug\n- 2 maintainability"); + } + + #[test] + fn the_markdown_breaks_the_issues_down_by_severity() { + let mut result = review_result(vec![ + ReviewItem { + severity: Some(ReviewSeverity::Bug), + ..review("a", Some(1), None) + }, + ReviewItem { + severity: Some(ReviewSeverity::Performance), + ..review("b", Some(2), None) + }, + ]); + result.comment = String::from("Le découpage en crates est propre."); + + let markdown = review_result_to_markdown(&result); + + assert!(markdown.contains("- 1 bug")); + assert!(markdown.contains("- 1 performance")); + assert!(markdown.contains("Le découpage en crates est propre.")); + } +} diff --git a/crates/steep-server/src/db.rs b/crates/steep-server/src/db.rs index 2eb62e6..fd38ed2 100644 --- a/crates/steep-server/src/db.rs +++ b/crates/steep-server/src/db.rs @@ -1,7 +1,5 @@ use toasty::Db; -use crate::schemas::{Job, JobStatus}; - pub async fn open(url: &str) -> toasty::Result { Db::builder() .models(toasty::models!(crate::*)) @@ -9,35 +7,45 @@ pub async fn open(url: &str) -> toasty::Result { .await } -pub async fn connect(url: &str) -> toasty::Result { - let mut db = open(url).await?; - - Job::update_by_status(JobStatus::Processing) - .status(JobStatus::Pending) - .exec(&mut db) - .await?; - - Ok(db) -} - +/// A connection for a test, on the database `DATABASE_URL` points to. +/// +/// Every `#[tokio::test]` runs on its own runtime, and the connection tasks live on +/// the runtime that opened the pool: sharing one `Db` between tests would leave +/// them with a pool whose runtime is gone. Each test therefore opens its own, and +/// only the migrations are applied once. #[cfg(test)] pub(crate) async fn test_connect() -> Db { - static DB: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); + static MIGRATED: tokio::sync::OnceCell<()> = tokio::sync::OnceCell::const_new(); - DB.get_or_init(|| async { - let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let migrations = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("toasty"); - let config = toasty_cli::Config::new() - .migration(toasty_cli::MigrationConfig::new().path(migrations)); + // The migrations run on a runtime of their own, which outlives the first test. + MIGRATED + .get_or_init(|| async { + let url = url.clone(); - toasty_cli::ToastyCli::with_config(open(&url).await.expect("open pool"), config) - .parse_from(["toasty", "migration", "apply"]) - .await - .expect("apply migrations"); + std::thread::spawn(move || { + tokio::runtime::Runtime::new() + .expect("migration runtime") + .block_on(async { + let migrations = + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("toasty"); + let config = toasty_cli::Config::new() + .migration(toasty_cli::MigrationConfig::new().path(migrations)); - connect(&url).await.expect("connect to postgres") - }) - .await - .clone() + toasty_cli::ToastyCli::with_config( + open(&url).await.expect("open pool"), + config, + ) + .parse_from(["toasty", "migration", "apply"]) + .await + .expect("apply migrations"); + }); + }) + .join() + .expect("migrations panicked"); + }) + .await; + + open(&url).await.expect("connect to postgres") } diff --git a/crates/steep-server/src/domain/mod.rs b/crates/steep-server/src/domain/mod.rs new file mode 100644 index 0000000..0755bd4 --- /dev/null +++ b/crates/steep-server/src/domain/mod.rs @@ -0,0 +1 @@ +pub mod review; diff --git a/crates/steep-server/src/domain/review.rs b/crates/steep-server/src/domain/review.rs new file mode 100644 index 0000000..1f8088c --- /dev/null +++ b/crates/steep-server/src/domain/review.rs @@ -0,0 +1,247 @@ +//! Domain types of a review: what the model answers with, and how it is read. + +use std::str::FromStr; + +use serde::{Deserialize, Deserializer}; +use tracing::warn; + +use crate::text::excerpt; + +#[derive(Deserialize, Debug)] +pub struct ReviewResult { + pub reviews: Vec, + pub comment: String, + pub cost: Option, +} + +#[derive(Deserialize, Debug)] +pub struct ReviewItem { + pub filename: String, + pub line: Option, + #[serde(default, deserialize_with = "deserialize_side")] + pub side: Option, + #[serde(default, deserialize_with = "deserialize_severity")] + pub severity: Option, + pub message: String, +} + +/// Which version of the file a review comment is anchored on. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReviewSide { + /// The line was added by the pull request: `line` is a line number of the + /// new version of the file. + Added, + /// The line was removed by the pull request: `line` is a line number of the + /// old version of the file. + Removed, +} + +impl ReviewSide { + /// Reads the side the model asked for, tolerating casing and synonyms. + fn parse(raw: &str) -> Option { + match raw.trim().to_ascii_lowercase().as_str() { + "added" | "add" | "new" | "right" => Some(Self::Added), + "removed" | "remove" | "deleted" | "delete" | "old" | "left" => Some(Self::Removed), + _ => None, + } + } +} + +/// What kind of problem a review reports. +/// +/// The categories are the ones the prompt asks for; a review whose severity is +/// missing or unreadable falls back to [`ReviewSeverity::Maintainability`], the +/// least alarming bucket, instead of failing the whole review. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReviewSeverity { + /// Wrong behaviour. + Bug, + /// A vulnerability. + Security, + /// A resource problem. + Performance, + /// Everything else: readability, structure, naming, tests. + Maintainability, +} + +impl ReviewSeverity { + /// Reads the severity the model asked for, tolerating casing and synonyms. + fn parse(raw: &str) -> Option { + match raw.trim().to_ascii_lowercase().as_str() { + "bug" | "bugs" | "correctness" | "error" => Some(Self::Bug), + "security" | "vulnerability" => Some(Self::Security), + "performance" | "perf" => Some(Self::Performance), + "maintainability" | "quality" | "style" => Some(Self::Maintainability), + _ => None, + } + } + + /// Lower-case label used in the review markdown. + pub fn label(self) -> &'static str { + match self { + Self::Bug => "bug", + Self::Security => "security", + Self::Performance => "performance", + Self::Maintainability => "maintainability", + } + } +} + +/// Reads the side the model asked for. An unreadable value is ignored instead of +/// failing the whole review: the side is then resolved from the changed lines. +fn deserialize_side<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let raw = Option::::deserialize(deserializer)?; + + Ok(raw + .as_ref() + .and_then(serde_json::Value::as_str) + .and_then(ReviewSide::parse)) +} + +/// Reads the severity the model asked for, ignoring an unreadable value: the +/// review is then reported as [`ReviewSeverity::Maintainability`]. +fn deserialize_severity<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let raw = Option::::deserialize(deserializer)?; + + Ok(raw + .as_ref() + .and_then(serde_json::Value::as_str) + .and_then(ReviewSeverity::parse)) +} + +/// Number of characters of a model answer kept in the logs when it cannot be +/// parsed. +const MAX_LOGGED_ANSWER: usize = 500; + +impl FromStr for ReviewResult { + type Err = anyhow::Error; + + /// Parses the review the model answered with. + /// + /// This is the contract the agent loop enforces: a rejected answer is sent + /// back to the model, with the reason, so that it can correct itself. + /// + /// The contract is a raw JSON object, but models sometimes wrap it in a + /// markdown code fence or surround it with a sentence: the object is then + /// extracted from the answer before failing, and the answer is logged so a + /// breach of the contract can be diagnosed. + fn from_str(message: &str) -> Result { + let error = match serde_json::from_str::(message) { + Ok(review_result) => return Ok(review_result), + Err(error) => error, + }; + + // A markdown code fence or a sentence around the object is tolerated, with + // a warning: the contract asks for a raw JSON object. + if let Some(json) = json_object(message) + && let Ok(review_result) = serde_json::from_str::(json) + { + warn!( + "Model answer is not a raw JSON object, it was extracted from the surrounding text" + ); + + return Ok(review_result); + } + + // The reason of the rejection is logged along with the answer: without it, + // a broken answer is impossible to diagnose. + warn!( + answer = %excerpt(message, MAX_LOGGED_ANSWER), + reason = %error, + "Model answer is not the expected JSON" + ); + + anyhow::bail!("the answer is not valid JSON: {error}") + } +} + +/// Returns the outermost `{...}` of an answer, which ignores a markdown code +/// fence or any text around it. +fn json_object(message: &str) -> Option<&str> { + let start = message.find('{')?; + let end = message.rfind('}')?; + + (start < end).then(|| &message[start..=end]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_raw_answer_is_parsed() { + let answer = r#"{"reviews": [], "comment": "ok"}"#; + + assert_eq!(answer.parse::().unwrap().comment, "ok"); + } + + #[test] + fn a_fenced_answer_is_extracted() { + let answer = concat!( + "Voici ma review :\n", + "```json\n", + "{\"reviews\": [], \"comment\": \"rien à signaler\"}\n", + "```\n" + ); + + assert_eq!( + answer.parse::().unwrap().comment, + "rien à signaler" + ); + } + + #[test] + fn a_sentence_around_the_object_is_ignored() { + let answer = r#"Rien à signaler. {"reviews": [], "comment": "ok"} Bonne journée !"#; + + assert_eq!(answer.parse::().unwrap().comment, "ok"); + } + + #[test] + fn an_answer_without_json_is_rejected() { + assert!("Je n'ai rien relevé.".parse::().is_err()); + } + + #[test] + fn an_answer_that_is_an_object_but_not_a_review_is_rejected() { + assert!(r#"{"message": "LGTM"}"#.parse::().is_err()); + } + + #[test] + fn severities_are_read_from_the_model() { + let result: ReviewResult = serde_json::from_str( + r#"{ + "reviews": [ + { "filename": "a", "line": 1, "severity": "Bug", "message": "x" }, + { "filename": "b", "line": 2, "severity": "SECURITY", "message": "x" }, + { "filename": "c", "line": 3, "severity": "perf", "message": "x" }, + { "filename": "d", "line": 4, "severity": "banana", "message": "x" } + ], + "comment": "" + }"#, + ) + .unwrap(); + + let severities = result + .reviews + .iter() + .map(|review| review.severity) + .collect::>(); + + assert_eq!( + severities, + vec![ + Some(ReviewSeverity::Bug), + Some(ReviewSeverity::Security), + Some(ReviewSeverity::Performance), + None, + ] + ); + } +} diff --git a/crates/steep-server/src/env.rs b/crates/steep-server/src/env.rs index adf1973..13a1214 100644 --- a/crates/steep-server/src/env.rs +++ b/crates/steep-server/src/env.rs @@ -1,4 +1,9 @@ -use anyhow::anyhow; +use std::{str::FromStr, time::Duration}; + +use anyhow::Context; + +/// Number of tool-calling iterations a sandboxed review may take, unless configured. +const DEFAULT_SANDBOX_MAX_ITERATIONS: usize = 8; #[derive(Clone)] pub struct EnvConfig { @@ -6,46 +11,31 @@ pub struct EnvConfig { pub webhook_secret: String, pub open_router_api_key: String, pub open_router_model: String, - pub open_router_timeout: u64, + pub open_router_timeout: Duration, pub bot_max_concurrent: usize, pub gitea_url: String, pub gitea_token: String, - pub gitea_timeout: u64, + pub gitea_timeout: Duration, pub database_url: String, pub metrics_bind_addr: Option, pub sandbox_max_iterations: usize, } pub fn load_config() -> anyhow::Result { - let http_port = try_get_env("HTTP_PORT")?.parse()?; - let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?; - let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?; - let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?; - let open_router_timeout = try_get_env("OPEN_ROUTER_TIMEOUT")?.parse()?; - let bot_max_concurrent = try_get_env("BOT_MAX_CONCURRENT")?.parse()?; - let gitea_url = try_get_env("GITEA_URL")?; - let gitea_token = try_get_env("GITEA_TOKEN")?; - let gitea_timeout = try_get_env("GITEA_TIMEOUT")?.parse()?; - let database_url = try_get_env("DATABASE_URL")?; - let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok(); - let sandbox_max_iterations = std::env::var("SANDBOX_MAX_ITERATIONS") - .ok() - .and_then(|value| value.parse().ok()) - .unwrap_or(8); - Ok(EnvConfig { - http_port, - webhook_secret, - open_router_api_key, - open_router_model, - open_router_timeout, - bot_max_concurrent, - gitea_url, - gitea_token, - gitea_timeout, - database_url, - metrics_bind_addr, - sandbox_max_iterations, + http_port: parse_env("HTTP_PORT")?, + webhook_secret: try_get_env("WEBHOOK_SIG_HEADER_SECRET")?, + open_router_api_key: try_get_env("OPEN_ROUTER_API_KEY")?, + open_router_model: try_get_env("OPEN_ROUTER_MODEL")?, + open_router_timeout: parse_seconds_env("OPEN_ROUTER_TIMEOUT")?, + bot_max_concurrent: parse_env("BOT_MAX_CONCURRENT")?, + gitea_url: try_get_env("GITEA_URL")?, + gitea_token: try_get_env("GITEA_TOKEN")?, + gitea_timeout: parse_seconds_env("GITEA_TIMEOUT")?, + database_url: try_get_env("DATABASE_URL")?, + metrics_bind_addr: std::env::var("METRICS_BIND_ADDR").ok(), + sandbox_max_iterations: parse_optional_env("SANDBOX_MAX_ITERATIONS")? + .unwrap_or(DEFAULT_SANDBOX_MAX_ITERATIONS), }) } @@ -53,16 +43,57 @@ pub fn try_get_env(key: &str) -> anyhow::Result { let env_value = std::env::var(key).map_err(|e| anyhow::anyhow!("{}: {}", key, e))?; if env_value.trim().is_empty() { - return Err(anyhow!(format!("env var {} is empty", key))); + anyhow::bail!("env var {key} is empty"); } Ok(env_value) } +/// Reads a required variable and parses it, naming the variable when it is invalid. +fn parse_env(key: &str) -> anyhow::Result +where + T: FromStr, + T::Err: std::error::Error + Send + Sync + 'static, +{ + parse_value(key, &try_get_env(key)?) +} + +/// Reads an optional variable and parses it. A missing or empty variable is `None`; +/// one that is set but invalid is an error, like it is for the required ones. +fn parse_optional_env(key: &str) -> anyhow::Result> +where + T: FromStr, + T::Err: std::error::Error + Send + Sync + 'static, +{ + match std::env::var(key) { + Ok(value) if !value.trim().is_empty() => parse_value(key, &value).map(Some), + _ => Ok(None), + } +} + +/// Reads a required variable holding a number of seconds. +fn parse_seconds_env(key: &str) -> anyhow::Result { + parse_env::(key).map(Duration::from_secs) +} + +fn parse_value(key: &str, value: &str) -> anyhow::Result +where + T: FromStr, + T::Err: std::error::Error + Send + Sync + 'static, +{ + value + .trim() + .parse() + .with_context(|| format!("env var {key} is invalid: `{value}`")) +} + #[cfg(test)] mod tests { use super::*; + // Every test uses its own variable: the environment is shared by the tests, + // which run in parallel. + #[test] fn test_try_get_env_returns_value() { unsafe { std::env::set_var("TEST_ENV_PRESENT", "hello") }; @@ -88,4 +119,38 @@ mod tests { let err = try_get_env("TEST_ENV_WHITESPACE").unwrap_err(); assert!(err.to_string().contains("TEST_ENV_WHITESPACE")); } + + #[test] + fn a_timeout_is_read_as_seconds() { + unsafe { std::env::set_var("TEST_ENV_SECONDS", "30") }; + assert_eq!( + parse_seconds_env("TEST_ENV_SECONDS").unwrap(), + Duration::from_secs(30) + ); + } + + #[test] + fn an_invalid_number_names_its_variable() { + unsafe { std::env::set_var("TEST_ENV_NOT_A_NUMBER", "soon") }; + + let err = parse_env::("TEST_ENV_NOT_A_NUMBER").unwrap_err(); + + assert!(format!("{err:#}").contains("TEST_ENV_NOT_A_NUMBER")); + assert!(format!("{err:#}").contains("soon")); + } + + #[test] + fn an_absent_optional_variable_is_none() { + unsafe { std::env::remove_var("TEST_ENV_OPTIONAL_ABSENT") }; + assert_eq!( + parse_optional_env::("TEST_ENV_OPTIONAL_ABSENT").unwrap(), + None + ); + } + + #[test] + fn an_invalid_optional_variable_is_an_error_instead_of_the_default() { + unsafe { std::env::set_var("TEST_ENV_OPTIONAL_INVALID", "many") }; + assert!(parse_optional_env::("TEST_ENV_OPTIONAL_INVALID").is_err()); + } } diff --git a/crates/steep-server/src/event_bus.rs b/crates/steep-server/src/event_bus.rs index c9c051e..0c03799 100644 --- a/crates/steep-server/src/event_bus.rs +++ b/crates/steep-server/src/event_bus.rs @@ -11,7 +11,7 @@ const PROGRESS_CAPACITY: usize = 256; const HISTORY_CAPACITY: usize = 256; const MAX_EVENT_ARGUMENTS: usize = 200; -/// Évènement de cycle de vie d'un job, diffusé globalement. +/// Lifecycle event of a job, broadcast globally. #[derive(Clone, Debug, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum JobEvent { @@ -212,7 +212,7 @@ impl Reporter { }); } - /// Signale la fin d'un appel d'outil. + /// Reports the end of a tool call. pub fn tool_result(&self, iteration: usize, name: &str, truncated: bool, output: &str) { self.channel.emit(Progress::ToolResult { iteration, diff --git a/crates/steep-server/src/gitea.rs b/crates/steep-server/src/gitea.rs index d1616f3..071ce79 100644 --- a/crates/steep-server/src/gitea.rs +++ b/crates/steep-server/src/gitea.rs @@ -8,8 +8,8 @@ use tokio_util::io::StreamReader; use tracing::{instrument, warn}; use crate::{ - bot::{ReviewResult, ReviewSeverity, ReviewSide}, consts::MAX_DIFF_SIZE, + domain::review::{ReviewResult, ReviewSeverity, ReviewSide}, errors::AppError, }; @@ -27,7 +27,7 @@ pub struct GiteaAPI { } impl GiteaAPI { - pub fn new(base_url: &str, token: &str, timeout: u64) -> anyhow::Result { + pub fn new(base_url: &str, token: &str, timeout: Duration) -> anyhow::Result { let mut default_headers = reqwest::header::HeaderMap::new(); default_headers.insert( reqwest::header::HeaderName::from_static("authorization"), @@ -38,7 +38,7 @@ impl GiteaAPI { base_url: String::from(base_url), token: String::from(token), client: reqwest::Client::builder() - .timeout(Duration::from_secs(timeout)) + .timeout(timeout) .default_headers(default_headers) .build()?, }) @@ -320,6 +320,21 @@ pub struct ReviewPayload { pub comment: Comment, } +impl ReviewPayload { + /// Checks that the comment is a new one that mentions the bot. + fn check_addressed_to(&self, bot_name: &str) -> Result<(), AppError> { + if self.action != "created" { + return Err(AppError::InvalidActionErr); + } + + if !self.comment.body.starts_with(&format!("@{bot_name}")) { + return Err(AppError::UnauthorizedUserErr); + } + + Ok(()) + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct PullRequest { pub number: u64, @@ -357,28 +372,15 @@ pub struct PullRequestFile { impl WebhookType { pub fn from_event(event: &str, bot_name: &str, json: Value) -> Result { - let wb = match event { - "pull_request_comment" => Ok(WebhookType::Review(serde_json::from_value(json)?)), + match event { + "pull_request_comment" => { + let payload: ReviewPayload = serde_json::from_value(json)?; + payload.check_addressed_to(bot_name)?; + + Ok(WebhookType::Review(payload)) + } _ => Err(AppError::UnknownEventErr), - }?; - - let action = match &wb { - WebhookType::Review(review_payload) => &review_payload.action, - }; - - if action != "created" { - return Err(AppError::InvalidActionErr); } - - let pr_body = match &wb { - WebhookType::Review(review_payload) => &review_payload.comment.body, - }; - - if !pr_body.starts_with(&format!("@{}", bot_name)) { - return Err(AppError::UnauthorizedUserErr); - } - - Ok(wb) } } diff --git a/crates/steep-server/src/job_queue.rs b/crates/steep-server/src/job_queue.rs index 1b65737..2c06958 100644 --- a/crates/steep-server/src/job_queue.rs +++ b/crates/steep-server/src/job_queue.rs @@ -1,26 +1,30 @@ -use std::{collections::VecDeque, time::Duration}; +use std::sync::Arc; -use futures_util::stream::{self, Stream}; -use toasty::Db; -use tracing::error; +use toasty::{Db, stmt::Value}; +use tokio::sync::Notify; use crate::{ gitea::WebhookType, schemas::{Job, JobStatus}, }; -/// Délai entre deux interrogations de la table quand la file est vide. -const POLL_INTERVAL: Duration = Duration::from_secs(1); - -/// File des reviews à faire. +/// The reviews to do, stored in PostgreSQL. +/// +/// Steep runs as a single instance, so every job is pushed by this process: the +/// worker is woken up by [`JobQueue::push`] instead of polling the table. The table +/// only makes the queue survive a restart. #[derive(Clone)] pub struct JobQueue { db: Db, + work: Arc, } impl JobQueue { pub fn new(db: Db) -> Self { - Self { db } + Self { + db, + work: Arc::new(Notify::new()), + } } pub async fn push(&self, webhook: WebhookType) -> toasty::Result { @@ -40,54 +44,76 @@ impl JobQueue { .exec(&mut db) .await?; + // `notify_one` keeps a permit when nobody is waiting yet, so a job pushed + // while the worker is busy is not missed. + self.work.notify_one(); + Ok(job) } - pub fn pending(&self) -> impl Stream { - let db = self.db.clone(); - - stream::unfold(VecDeque::new(), move |mut buffer| { - let db = db.clone(); - - async move { - let mut db = db; - - loop { - if let Some(job) = buffer.pop_front() { - return Some((job, buffer)); - } - - match Job::filter_by_status(JobStatus::Pending) - .exec(&mut db) - .await - { - Ok(jobs) if jobs.is_empty() => { - tokio::time::sleep(POLL_INTERVAL).await; - } - Ok(jobs) => buffer.extend(jobs), - Err(err) => { - error!(%err, "failed to poll pending jobs"); - tokio::time::sleep(POLL_INTERVAL).await; - } - } - } - } - }) - } - - /// Marque un job comme en cours de traitement. - pub async fn mark_processing(&self, id: u64) -> toasty::Result<()> { + /// Whether a job is already queued for this event. + pub async fn is_queued(&self, event_id: u64) -> toasty::Result { let mut db = self.db.clone(); - Job::update_by_id(id) - .status(JobStatus::Processing) + Ok(Job::filter_by_event_id(event_id) + .first() + .exec(&mut db) + .await? + .is_some()) + } + + /// Puts back in the queue the jobs a previous process left `processing`. + /// + /// To call once at startup, before the worker starts: it is only correct because + /// no other instance can be working on those jobs. + pub async fn requeue_interrupted(&self) -> toasty::Result<()> { + let mut db = self.db.clone(); + + Job::update_by_status(JobStatus::Processing) + .status(JobStatus::Pending) .exec(&mut db) .await?; Ok(()) } - /// Retire un job de la file, une fois la review enregistrée. + /// Takes the oldest pending job and marks it `processing`, or returns `None` when + /// the queue is empty. + /// + /// The selection and the update are a single statement, so a job cannot be taken + /// twice. + pub async fn claim_next(&self) -> toasty::Result> { + let mut db = self.db.clone(); + + let rows = toasty::sql::query( + "UPDATE jobs + SET status = 'processing' + WHERE id = ( + SELECT id FROM jobs + WHERE status = 'pending' + ORDER BY id + LIMIT 1 + ) + RETURNING id", + ) + .exec(&mut db) + .await?; + + let Some(Value::Record(row)) = rows.into_iter().next() else { + return Ok(None); + }; + + let id = u64::try_from(row.as_slice()[0].clone())?; + + Ok(Some(Job::get_by_id(&mut db, id).await?)) + } + + /// Waits until a job may have been pushed. + pub async fn wait_for_work(&self) { + self.work.notified().await; + } + + /// Removes a job from the queue, once its review is recorded. pub async fn remove(&self, id: u64) -> toasty::Result<()> { let mut db = self.db.clone(); @@ -108,11 +134,13 @@ impl JobQueue { mod tests { use std::time::Duration; - use futures_util::StreamExt; + use tokio::sync::{Mutex, MutexGuard}; use super::*; use crate::gitea::{Comment, PullRequest, Repository, ReviewPayload}; + const FIRST_EVENT: u64 = 999_000_200; + fn webhook(event_id: u64) -> WebhookType { WebhookType::Review(ReviewPayload { action: "created".to_string(), @@ -131,41 +159,40 @@ mod tests { }) } - async fn next_job(queue: &JobQueue, event_id: u64) -> Job { - let mut pending = std::pin::pin!(queue.pending()); + /// The tests claim whichever job is next, so they must not overlap: one would + /// claim the jobs of another. + async fn exclusive_queue() -> (MutexGuard<'static, ()>, JobQueue, Db) { + static LOCK: Mutex<()> = Mutex::const_new(()); - tokio::time::timeout(Duration::from_secs(10), async { - loop { - if let Some(job) = pending.next().await - && job.event_id == event_id - { - break job; - } - } - }) + let guard = LOCK.lock().await; + let db = crate::db::test_connect().await; + + toasty::sql::statement( + "DELETE FROM jobs WHERE event_id >= 999000200 AND event_id < 999000300", + ) + .exec(&mut db.clone()) .await - .expect("timed out waiting for the job") + .unwrap(); + + (guard, JobQueue::new(db.clone()), db) } #[tokio::test] #[ignore = "requires DATABASE_URL"] - async fn a_pushed_job_is_pending_then_removed() { - let db = crate::db::test_connect().await; - let queue = JobQueue::new(db.clone()); + async fn a_pushed_job_is_claimed_then_removed() { + let (_guard, queue, db) = exclusive_queue().await; - let event_id = 999_000_201; - Job::delete_by_event_id(&mut db.clone(), event_id) - .await - .ok(); + queue.push(webhook(FIRST_EVENT)).await.unwrap(); + assert!(queue.is_queued(FIRST_EVENT).await.unwrap()); - queue.push(webhook(event_id)).await.unwrap(); + let job = queue.claim_next().await.unwrap().expect("a job to claim"); + assert_eq!(job.event_id, FIRST_EVENT); + assert_eq!(job.status, JobStatus::Processing); - let job = next_job(&queue, event_id).await; - queue.mark_processing(job.id).await.unwrap(); queue.remove(job.id).await.unwrap(); assert!( - Job::filter_by_event_id(event_id) + Job::filter_by_event_id(FIRST_EVENT) .first() .exec(&mut db.clone()) .await @@ -177,19 +204,70 @@ mod tests { #[tokio::test] #[ignore = "requires DATABASE_URL"] async fn a_duplicate_event_id_is_rejected() { - let db = crate::db::test_connect().await; - let queue = JobQueue::new(db.clone()); + let (_guard, queue, _db) = exclusive_queue().await; - let event_id = 999_000_202; - Job::delete_by_event_id(&mut db.clone(), event_id) - .await - .ok(); + queue.push(webhook(FIRST_EVENT + 1)).await.unwrap(); + assert!(queue.push(webhook(FIRST_EVENT + 1)).await.is_err()); + } - queue.push(webhook(event_id)).await.unwrap(); - assert!(queue.push(webhook(event_id)).await.is_err()); + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn a_claimed_job_is_not_claimed_again() { + let (_guard, queue, _db) = exclusive_queue().await; - Job::delete_by_event_id(&mut db.clone(), event_id) + queue.push(webhook(FIRST_EVENT + 2)).await.unwrap(); + + assert!(queue.claim_next().await.unwrap().is_some()); + assert!(queue.claim_next().await.unwrap().is_none()); + } + + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn jobs_are_claimed_oldest_first() { + let (_guard, queue, _db) = exclusive_queue().await; + + queue.push(webhook(FIRST_EVENT + 3)).await.unwrap(); + queue.push(webhook(FIRST_EVENT + 4)).await.unwrap(); + + let first = queue.claim_next().await.unwrap().unwrap(); + let second = queue.claim_next().await.unwrap().unwrap(); + + assert_eq!(first.event_id, FIRST_EVENT + 3); + assert_eq!(second.event_id, FIRST_EVENT + 4); + } + + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn an_interrupted_job_is_requeued() { + let (_guard, queue, _db) = exclusive_queue().await; + + queue.push(webhook(FIRST_EVENT + 5)).await.unwrap(); + let job = queue.claim_next().await.unwrap().unwrap(); + assert!(queue.claim_next().await.unwrap().is_none()); + + // The process died here: on restart, the job goes back to the queue. + queue.requeue_interrupted().await.unwrap(); + + let again = queue.claim_next().await.unwrap().expect("the job back"); + assert_eq!(again.id, job.id); + } + + #[tokio::test] + #[ignore = "requires DATABASE_URL"] + async fn waiting_for_work_returns_as_soon_as_a_job_is_pushed() { + let (_guard, queue, _db) = exclusive_queue().await; + + let waiter = { + let queue = queue.clone(); + tokio::spawn(async move { queue.wait_for_work().await }) + }; + + tokio::time::sleep(Duration::from_millis(100)).await; + queue.push(webhook(FIRST_EVENT + 6)).await.unwrap(); + + tokio::time::timeout(Duration::from_secs(1), waiter) .await + .expect("the worker was not woken up") .unwrap(); } } diff --git a/crates/steep-server/src/main.rs b/crates/steep-server/src/main.rs index c639505..aa3c9aa 100644 --- a/crates/steep-server/src/main.rs +++ b/crates/steep-server/src/main.rs @@ -1,8 +1,14 @@ use std::time::Duration; use crate::{ - bot::Bot, dedup::Dedup, event_bus::EventBus, gitea::GiteaAPI, job_queue::JobQueue, - open_router::OpenRouterClient, sandbox::SandboxConfig, state::AppState, + bot::{Bot, BotDeps}, + event_bus::EventBus, + gitea::GiteaAPI, + job_queue::JobQueue, + open_router::OpenRouterClient, + review_store::ReviewStore, + sandbox::SandboxConfig, + state::AppState, }; use dotenvy::dotenv; @@ -16,7 +22,7 @@ mod bot; mod bot_actions; mod consts; mod db; -mod dedup; +mod domain; mod env; mod errors; mod event_bus; @@ -24,21 +30,29 @@ mod gitea; mod job_queue; mod metrics; mod open_router; +mod review_store; mod sandbox; mod schemas; mod state; mod text; -/// Délai laissé aux reviews en cours et aux serveurs pour s'arrêter proprement. +/// Time left to the running reviews and the servers to stop cleanly. /// -/// Sans lui, une review prise dans une sandbox récalcitrante garderait le processus -/// en vie jusqu'à ce que le superviseur le tue. +/// Without it, a review stuck in an unresponsive sandbox would keep the process +/// alive until the supervisor kills it. const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(60); fn main() -> anyhow::Result<()> { dotenv().ok(); - // Sous-commande de migration : `steep-server migration `. + // TLS goes through rustls, which has no default crypto provider here: the + // workspace compiles `ring` only, and it is installed once, for every client + // (reqwest, sentry). + rustls::crypto::ring::default_provider() + .install_default() + .map_err(|_| anyhow::anyhow!("a rustls crypto provider is already installed"))?; + + // Migration subcommand: `steep-server migration `. if std::env::args().nth(1).as_deref() == Some("migration") { return tokio::runtime::Runtime::new()?.block_on(run_migrations()); } @@ -46,7 +60,7 @@ fn main() -> anyhow::Result<()> { tracing_subscriber::registry() .with(fmt::layer()) .with( - EnvFilter::try_from_default_env() // lit RUST_LOG depuis l'env + EnvFilter::try_from_default_env() // reads RUST_LOG from the environment .unwrap_or_else(|_| EnvFilter::new("info")), ) .init(); @@ -124,25 +138,29 @@ async fn run() -> anyhow::Result<()> { ); } - let db = db::connect(&config.database_url).await?; - let dedup = Dedup::new(db.clone()); + let db = db::open(&config.database_url).await?; + let reviews = ReviewStore::new(db.clone()); + let queue = JobQueue::new(db); + queue.requeue_interrupted().await?; + let event_bus = EventBus::new(); let bot = Bot::new( - gitea_api, - open_router_client, + BotDeps { + gitea: gitea_api, + open_router: open_router_client, + sandbox, + reviews: reviews.clone(), + queue: queue.clone(), + event_bus: event_bus.clone(), + }, config.bot_max_concurrent, - config.open_router_model.clone(), - sandbox, - dedup.clone(), - queue.clone(), - event_bus.clone(), ); let app_state = AppState { queue, - dedup, + reviews, event_bus, bot_name: gitea_user.login.clone(), config, diff --git a/crates/steep-server/src/open_router.rs b/crates/steep-server/src/open_router.rs index 938c2fe..040b2ea 100644 --- a/crates/steep-server/src/open_router.rs +++ b/crates/steep-server/src/open_router.rs @@ -254,16 +254,19 @@ pub struct OpenRouterClient { } impl OpenRouterClient { - pub fn new(token: &str, model: &str, timeout: u64) -> anyhow::Result { + pub fn new(token: &str, model: &str, timeout: Duration) -> anyhow::Result { Ok(Self { - client: reqwest::Client::builder() - .timeout(Duration::from_secs(timeout)) - .build()?, + client: reqwest::Client::builder().timeout(timeout).build()?, api_key: String::from(token), model: String::from(model), }) } + /// The model every completion is requested from. + pub fn model(&self) -> &str { + &self.model + } + /// Sends a conversation with tool definitions and returns either a final /// message or the tool calls requested by the model. /// diff --git a/crates/steep-server/src/dedup.rs b/crates/steep-server/src/review_store.rs similarity index 64% rename from crates/steep-server/src/dedup.rs rename to crates/steep-server/src/review_store.rs index 51be893..d278be4 100644 --- a/crates/steep-server/src/dedup.rs +++ b/crates/steep-server/src/review_store.rs @@ -1,7 +1,8 @@ use toasty::Db; -use crate::schemas::{Job, Review}; +use crate::schemas::Review; +/// What is kept of a processed review. pub struct ReviewRecord { pub event_id: u64, pub repo: String, @@ -14,35 +15,19 @@ pub struct ReviewRecord { pub duration_ms: u64, } +/// The history of the reviews Steep has processed. +/// +/// It also is what keeps an event from being reviewed twice. #[derive(Clone)] -pub struct Dedup { +pub struct ReviewStore { db: Db, } -impl Dedup { +impl ReviewStore { pub fn new(db: Db) -> Self { Self { db } } - pub async fn is_known(&self, event_id: u64) -> toasty::Result { - let mut db = self.db.clone(); - - if Job::filter_by_event_id(event_id) - .first() - .exec(&mut db) - .await? - .is_some() - { - return Ok(true); - } - - Ok(Review::filter_by_event_id(event_id) - .first() - .exec(&mut db) - .await? - .is_some()) - } - pub async fn is_reviewed(&self, event_id: u64) -> toasty::Result { let mut db = self.db.clone(); @@ -53,7 +38,8 @@ impl Dedup { .is_some()) } - pub async fn mark_reviewed(&self, record: ReviewRecord) -> toasty::Result<()> { + /// Records a processed review, replacing the one of the same event if any. + pub async fn record(&self, record: ReviewRecord) -> toasty::Result<()> { let mut db = self.db.clone(); Review::upsert_by_event_id(record.event_id) @@ -71,7 +57,7 @@ impl Dedup { Ok(()) } - pub async fn list_reviews(&self) -> toasty::Result> { + pub async fn list(&self) -> toasty::Result> { let mut db = self.db.clone(); let reviews = Review::all().exec(&mut db).await?; @@ -82,24 +68,22 @@ impl Dedup { #[cfg(test)] mod tests { use super::*; - use crate::schemas::Review; #[tokio::test] #[ignore = "requires DATABASE_URL"] - async fn a_recorded_review_is_known_and_reviewed() { + async fn a_recorded_review_is_reviewed() { let db = crate::db::test_connect().await; - let dedup = Dedup::new(db.clone()); + let store = ReviewStore::new(db.clone()); let event_id = 999_000_101; Review::delete_by_event_id(&mut db.clone(), event_id) .await .ok(); - assert!(!dedup.is_known(event_id).await.unwrap()); - assert!(!dedup.is_reviewed(event_id).await.unwrap()); + assert!(!store.is_reviewed(event_id).await.unwrap()); - dedup - .mark_reviewed(ReviewRecord { + store + .record(ReviewRecord { event_id, repo: "owner/repo".to_string(), pr: 1, @@ -113,8 +97,15 @@ mod tests { .await .unwrap(); - assert!(dedup.is_reviewed(event_id).await.unwrap()); - assert!(dedup.is_known(event_id).await.unwrap()); + assert!(store.is_reviewed(event_id).await.unwrap()); + assert!( + store + .list() + .await + .unwrap() + .iter() + .any(|r| r.event_id == event_id) + ); Review::delete_by_event_id(&mut db.clone(), event_id) .await diff --git a/crates/steep-server/src/sandbox/agent.rs b/crates/steep-server/src/sandbox/agent.rs index a2c1768..4ca9643 100644 --- a/crates/steep-server/src/sandbox/agent.rs +++ b/crates/steep-server/src/sandbox/agent.rs @@ -95,8 +95,8 @@ where let mut last_rejection: Option = None; for iteration in 1..=max_iterations { - // Le dernier tour n'est plus un tour d'exploration : le modèle doit rendre - // sa réponse, avec ce qu'il a déjà vu. + // The last turn is no longer an exploration turn: the model must give its + // answer, with what it has already seen. let last = iteration == max_iterations; if last { @@ -132,8 +132,8 @@ where "Model answered" ); - // Au dernier tour, les outils sont refusés : même si le fournisseur les - // renvoie malgré tout, on ne les exécute pas et on tente la réponse. + // On the last turn, tools are refused: even if the provider returns them + // anyway, we do not run them and try the answer instead. if last || response.tool_calls.is_empty() { let answer = response.message.unwrap_or_default(); @@ -181,7 +181,7 @@ where arguments = %excerpt(call.arguments_json(), MAX_LOGGED_ARGUMENTS), output_bytes = content.len(), truncated, - // Les sauts de ligne casseraient la lisibilité d'une ligne de log. + // Line breaks would hurt the readability of a log line. output = %excerpt(&content, MAX_LOGGED_OUTPUT).replace('\n', " "), elapsed_ms = started.elapsed().as_millis() as u64, "Tool call finished" diff --git a/crates/steep-server/src/sandbox/instance.rs b/crates/steep-server/src/sandbox/instance.rs index 8478c58..cc31fdf 100644 --- a/crates/steep-server/src/sandbox/instance.rs +++ b/crates/steep-server/src/sandbox/instance.rs @@ -1,5 +1,5 @@ -//! Le [`Sandbox`] : clone d'une pull request exécuté dans un devcontainer -//! éphémère, et sa configuration. +//! The [`Sandbox`]: a pull request clone running inside an ephemeral +//! devcontainer, and its configuration. use std::{ path::{Path, PathBuf}, @@ -19,7 +19,7 @@ const DEVCONTAINER_PATHS: [&str; 2] = [".devcontainer/devcontainer.json", ".devc /// Sandbox-related runtime configuration. #[derive(Clone)] pub struct SandboxConfig { - /// Client du daemon de containers qui exécute la sandbox. + /// Client of the container daemon that runs the sandbox. pub runtime: ContainerRuntime, /// Maximum number of tool-calling iterations per agent run. pub max_iterations: usize, @@ -79,12 +79,12 @@ impl Sandbox { Ok(sandbox) } - /// Vérifie que le clone est bien visible dans le container. + /// Checks that the clone is actually visible inside the container. /// - /// Sans ce contrôle, un workspace vide — par exemple un daemon qui n'a pas pu - /// recevoir le clone — fait échouer chaque outil ; le modèle enchaîne alors les - /// appels ratés jusqu'au budget d'itérations, sans jamais pouvoir reviewer quoi - /// que ce soit. + /// Without this check, an empty workspace (for example a daemon that could not + /// receive the clone) makes every tool fail; the model then chains failed calls + /// until the iteration budget is spent, without ever being able to review + /// anything. async fn check_workspace(&self) -> anyhow::Result<()> { let workspace_folder = self.workspace_folder(); let probe = self @@ -118,12 +118,12 @@ impl Sandbox { Ok(()) } - /// Rassemble de quoi expliquer un refus d'accès au workspace. + /// Gathers enough information to explain a refused access to the workspace. /// - /// Un `EACCES` a deux causes possibles, indistinguables dans le message de - /// `ls` : les permissions du clone, ou un confinement du noyau qui bloque - /// l'accès. L'identité de l'utilisateur d'exec et les permissions du point de - /// montage permettent de trancher. + /// An `EACCES` has two possible causes, indistinguishable in the `ls` message: + /// the clone's permissions, or kernel confinement blocking the access. The + /// identity of the exec user and the permissions of the mount point tell them + /// apart. async fn diagnose_workspace(&self) -> String { let mut details = Vec::new(); @@ -215,12 +215,12 @@ async fn clone_pull_request( Ok(()) } -/// Rend le clone lisible par les utilisateurs du container sandbox. +/// Makes the clone readable by the users of the sandbox container. /// -/// Le container peut ne pas avoir les mêmes uid que Steep (podman rootless -/// mappe les uid à travers des plages subuid), et l'umask de l'opérateur peut être -/// restrictif : sans cela, les outils de la sandbox échouent en `Permission -/// denied` sur des fichiers que Steep vient de cloner lui-même. +/// The container may not share Steep's uids (rootless podman maps uids through +/// subuid ranges), and the operator's umask may be restrictive: without this, the +/// sandbox tools fail with `Permission denied` on files that Steep itself just +/// cloned. async fn make_readable(repo_dir: &Path) -> anyhow::Result<()> { let output = tokio::process::Command::new("chmod") .args(["-R", "a+rX"]) diff --git a/crates/steep-server/src/sandbox/tools.rs b/crates/steep-server/src/sandbox/tools.rs index 3d4f5a0..7a9d249 100644 --- a/crates/steep-server/src/sandbox/tools.rs +++ b/crates/steep-server/src/sandbox/tools.rs @@ -167,11 +167,11 @@ async fn read_file(sandbox: &Sandbox, args: &Value) -> anyhow::Result { Ok(number_lines(&into_stdout(output)?, first_line)) } -/// Préfixe chaque ligne par son numéro. +/// Prefixes each line with its number. /// -/// Le modèle doit citer une ligne précise pour ancrer son commentaire : sans -/// numéros, il les compte lui-même et se décale de quelques lignes, ce qui place le -/// commentaire à côté du code visé. +/// The model must cite a precise line to anchor its comment: without numbers, it +/// counts them itself and drifts by a few lines, which puts the comment next to +/// the targeted code. fn number_lines(content: &str, first_line: u64) -> String { content .lines() @@ -291,8 +291,8 @@ mod tests { #[test] fn a_range_keeps_the_absolute_line_numbers() { - // Un extrait lu à partir de la ligne 12 doit garder la numérotation du - // fichier : sinon le modèle citerait des lignes décalées. + // An excerpt read from line 12 must keep the file's numbering: otherwise the + // model would cite shifted lines. assert_eq!(number_lines("x\ny", 12), "12:x\n13:y"); } diff --git a/crates/steep-server/src/schemas/review.rs b/crates/steep-server/src/schemas/review.rs index 0715a07..0041d9d 100644 --- a/crates/steep-server/src/schemas/review.rs +++ b/crates/steep-server/src/schemas/review.rs @@ -13,22 +13,22 @@ pub struct Review { pub repo: String, pub pr: u64, - /// Vrai si la review a abouti. + /// True if the review succeeded. pub ok: bool, - /// Modèle utilisé. + /// Model used. pub model: String, - /// Markdown posté sur la pull request (vide en cas d'échec). + /// Markdown posted on the pull request (empty on failure). pub markdown: String, - /// Coût OpenRouter en USD, si connu. + /// OpenRouter cost in USD, if known. pub cost: Option, - /// Nombre de commentaires produits. + /// Number of comments produced. pub comments: u64, - /// Durée du traitement en millisecondes. + /// Processing duration in milliseconds. pub duration_ms: u64, #[default(jiff::Timestamp::now())] diff --git a/crates/steep-server/src/state.rs b/crates/steep-server/src/state.rs index 263ffaf..d1e0d46 100644 --- a/crates/steep-server/src/state.rs +++ b/crates/steep-server/src/state.rs @@ -1,14 +1,14 @@ use tokio_util::sync::CancellationToken; -use crate::{dedup::Dedup, env::EnvConfig, event_bus::EventBus, job_queue::JobQueue}; +use crate::{env::EnvConfig, event_bus::EventBus, job_queue::JobQueue, review_store::ReviewStore}; #[derive(Clone)] pub struct AppState { pub queue: JobQueue, - pub dedup: Dedup, + pub reviews: ReviewStore, pub event_bus: EventBus, pub bot_name: String, pub config: EnvConfig, - /// Jeton annulé au démarrage de l'arrêt gracieux : il sert à clore les flux SSE. + /// Cancelled when the graceful shutdown starts: it closes the SSE streams. pub shutdown: CancellationToken, } diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..124dc17 --- /dev/null +++ b/deny.toml @@ -0,0 +1,53 @@ +# cargo-deny configuration: https://embarkstudios.github.io/cargo-deny/ +# Run locally with `cargo deny check`. + +[graph] +all-features = true + +[advisories] +# A vulnerable, unmaintained or yanked dependency fails the build. +version = 2 +yanked = "deny" +ignore = [ + # Unmaintained, but only a thin wrapper around `rustls-pki-types`, and pulled by + # `toasty-driver-postgresql`: it can only go away with a toasty release. + { id = "RUSTSEC-2025-0134", reason = "transitive dependency of toasty, no vulnerability" }, +] + +[licenses] +version = 2 +# The workspace crates are not published, and have no license of their own. +private = { ignore = true } +allow = [ + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "CC0-1.0", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "MIT-0", + "MPL-2.0", + "OpenSSL", + "Unicode-3.0", + "Zlib", +] + +[bans] +multiple-versions = "warn" +wildcards = "deny" +# `path` dependencies between the (unpublished) workspace crates carry no version. +allow-wildcard-paths = true +# TLS goes through rustls: linking libssl would be a regression of the image +# (it would need `libssl` installed again). +deny = [ + { crate = "openssl-sys", reason = "TLS is rustls only, so that the image needs no libssl" }, + { crate = "native-tls", reason = "TLS is rustls only, so that the image needs no libssl" }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/front/js/api.js b/front/js/api.js index 2f6ac45..144f590 100644 --- a/front/js/api.js +++ b/front/js/api.js @@ -3,7 +3,7 @@ export const endpoints = { jobs: "/jobs", - reviews: "/dedup", + reviews: "/reviews", lifecycle: "/events", jobEvents: (id) => `/jobs/${id}/events`, }; diff --git a/front/js/dashboard.js b/front/js/dashboard.js index 5829184..80309b3 100644 --- a/front/js/dashboard.js +++ b/front/js/dashboard.js @@ -1,6 +1,6 @@ // : assemble le tableau de bord. // -// Au montage, il charge l'état initial via REST (`/jobs`, `/dedup`), puis +// Au montage, il charge l'état initial via REST (`/jobs`, `/reviews`), puis // s'abonne au flux global `/events` pour tenir les listes à jour en direct. // La sélection d'un job ouvre son flux fin via . From 131f1ec84a4680f7e4e017289023f8dad020cad7 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 13 Oct 2026 20:09:15 +0000 Subject: [PATCH 5/8] Fix rust version --- Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containerfile b/Containerfile index bda6797..fbe0b56 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM rust:1.98-trixie as builder +FROM rust:1.99-trixie as builder WORKDIR /app From b40985528e6365c75404d3a7c444bdd21428a649 Mon Sep 17 00:00:00 2001 From: qpismont Date: Wed, 14 Oct 2026 16:41:50 +0000 Subject: [PATCH 6/8] rwlock to mutex --- crates/steep-server/src/event_bus.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/steep-server/src/event_bus.rs b/crates/steep-server/src/event_bus.rs index 0c03799..ca7f443 100644 --- a/crates/steep-server/src/event_bus.rs +++ b/crates/steep-server/src/event_bus.rs @@ -1,5 +1,5 @@ use std::collections::{HashMap, VecDeque}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex}; use serde::Serialize; use tokio::sync::broadcast; @@ -13,7 +13,7 @@ const MAX_EVENT_ARGUMENTS: usize = 200; /// Lifecycle event of a job, broadcast globally. #[derive(Clone, Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] pub enum JobEvent { Queued { id: u64, @@ -46,7 +46,7 @@ pub enum JobEvent { } #[derive(Clone, Debug, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] pub enum Progress { ToolCall { iteration: usize, @@ -98,7 +98,7 @@ pub struct JobSubscription { #[derive(Clone)] pub struct EventBus { lifecycle: broadcast::Sender, - jobs: Arc>>>, + jobs: Arc>>>, } impl EventBus { @@ -107,7 +107,7 @@ impl EventBus { Self { lifecycle, - jobs: Arc::new(RwLock::new(HashMap::new())), + jobs: Arc::new(Mutex::new(HashMap::new())), } } @@ -127,7 +127,7 @@ impl EventBus { }); self.jobs - .write() + .lock() .expect("job registry lock poisoned") .insert(id, channel.clone()); @@ -162,7 +162,7 @@ impl EventBus { if let Some(channel) = self .jobs - .write() + .lock() .expect("job registry lock poisoned") .remove(&id) { @@ -181,7 +181,7 @@ impl EventBus { pub fn subscribe_job(&self, id: u64) -> Option { let channel = self .jobs - .read() + .lock() .expect("job registry lock poisoned") .get(&id) .cloned()?; From cedd052a4da08b7a23ab0ee3f9838d408eff8bd6 Mon Sep 17 00:00:00 2001 From: qpismont Date: Wed, 14 Oct 2026 16:50:02 +0000 Subject: [PATCH 7/8] same migration process test and prod --- .woodpecker/tests.yml | 1 + README.md | 2 +- crates/steep-server/src/db.rs | 37 +++-------------------------------- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/.woodpecker/tests.yml b/.woodpecker/tests.yml index f05e763..044829e 100644 --- a/.woodpecker/tests.yml +++ b/.woodpecker/tests.yml @@ -39,6 +39,7 @@ steps: environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/steep commands: + - bash scripts/migrate-apply.sh - cargo test --workspace --all-targets -- --include-ignored - name: container-build diff --git a/README.md b/README.md index 44cd51a..aa7a657 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ After changing a model in `crates/steep-server/src/schemas/`, generate a migrati ./scripts/migrate-generate.sh describe_change ``` -Apply the pending migrations **before starting the service** — the server does not migrate at boot: +Apply the pending migrations **before starting the service** — the server does not migrate at boot. The tests need the same schema: run this before `cargo test` too (CI does it as well): ```sh ./scripts/migrate-apply.sh diff --git a/crates/steep-server/src/db.rs b/crates/steep-server/src/db.rs index fd38ed2..5954319 100644 --- a/crates/steep-server/src/db.rs +++ b/crates/steep-server/src/db.rs @@ -9,43 +9,12 @@ pub async fn open(url: &str) -> toasty::Result { /// A connection for a test, on the database `DATABASE_URL` points to. /// -/// Every `#[tokio::test]` runs on its own runtime, and the connection tasks live on -/// the runtime that opened the pool: sharing one `Db` between tests would leave -/// them with a pool whose runtime is gone. Each test therefore opens its own, and -/// only the migrations are applied once. +/// The schema must already be migrated: `scripts/migrate-apply.sh` does it, the same +/// way it does for the server. Each test opens its own connection, because the +/// connection tasks live on the runtime of the test that opened them. #[cfg(test)] pub(crate) async fn test_connect() -> Db { - static MIGRATED: tokio::sync::OnceCell<()> = tokio::sync::OnceCell::const_new(); - let url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - // The migrations run on a runtime of their own, which outlives the first test. - MIGRATED - .get_or_init(|| async { - let url = url.clone(); - - std::thread::spawn(move || { - tokio::runtime::Runtime::new() - .expect("migration runtime") - .block_on(async { - let migrations = - std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("toasty"); - let config = toasty_cli::Config::new() - .migration(toasty_cli::MigrationConfig::new().path(migrations)); - - toasty_cli::ToastyCli::with_config( - open(&url).await.expect("open pool"), - config, - ) - .parse_from(["toasty", "migration", "apply"]) - .await - .expect("apply migrations"); - }); - }) - .join() - .expect("migrations panicked"); - }) - .await; - open(&url).await.expect("connect to postgres") } From 426a10d5bebaf93b030bda90c704f8114b288197 Mon Sep 17 00:00:00 2001 From: qpismont Date: Wed, 14 Oct 2026 16:52:12 +0000 Subject: [PATCH 8/8] update lock cve --- Cargo.lock | 433 ++++++++++++++++++++++++++++------------------------- 1 file changed, 230 insertions(+), 203 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a2406d..c0d9d6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 4 [[package]] name = "actix-codec" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31404e1443b7b7bcaa311c1af456775cc3f668e34573f1503d7ce4844327629e" +checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-core", "futures-sink", @@ -21,15 +21,14 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.13.3" +version = "3.18.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11004b0e9b44b4eb3d15e0c3132b96fb178c7e50a74758b2f17bb9cc9a7fb4f6" +checksum = "a3f290edc569ad10c07287eebb96629686b37f1e55283a093569151d3adde0c7" dependencies = [ "actix-codec", - "actix-rt", "actix-service", "actix-utils", - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "bytestring", "derive_more", @@ -66,9 +65,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.13.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a16bf2f19c2ad84842bdfe6f3665620e93197d5c607889bfa3aaac45e762fd1" +checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f" dependencies = [ "futures-core", "tokio", @@ -76,13 +75,12 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d44ae8a6516f4ac7bfc7b61aabcd286104e96b4b24c747ce220832a016056d9" +checksum = "5e296908c4d6f3a191e6b9873dbdfec8023350a89129d2b470f61d17233931ee" dependencies = [ "actix-rt", "actix-service", - "actix-utils", "futures-core", "futures-util", "mio", @@ -103,9 +101,9 @@ dependencies = [ [[package]] name = "actix-utils" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d" dependencies = [ "local-waker", "pin-project-lite", @@ -113,9 +111,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.15.0" +version = "4.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbacab3593b6b4f7be815076fc52d60a83c873426824675417e2abdd229e2e36" +checksum = "824c7250d7e468feb7903e41efc5d6883cb1f3957e8db2c3d2402a9ff430b886" dependencies = [ "actix-codec", "actix-http", @@ -146,6 +144,7 @@ dependencies = [ "smallvec", "socket2", "time", + "tokio", "tracing", "url", ] @@ -361,7 +360,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -486,7 +485,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cexpr", "clang-sys", "itertools 0.12.1", @@ -531,9 +530,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bitvec" @@ -635,7 +634,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -676,7 +675,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -702,9 +701,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", "shlex 2.0.1", @@ -721,9 +720,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" @@ -813,7 +812,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -902,6 +901,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpubits" version = "0.1.1" @@ -946,9 +951,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] @@ -964,9 +969,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-bigint" @@ -1126,9 +1131,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" [[package]] name = "derive_more" @@ -1201,7 +1206,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", ] @@ -1213,7 +1218,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -1236,11 +1241,16 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] @@ -1265,9 +1275,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -1448,7 +1458,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -1496,9 +1506,9 @@ checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" [[package]] name = "generator" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +checksum = "1be81b31c9ce4caf7f3178eeb90662bb1864d3ad363ec6bdada3201adff19c37" dependencies = [ "cc", "cfg-if", @@ -1578,9 +1588,9 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "h2" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" dependencies = [ "atomic-waker", "bytes", @@ -1761,9 +1771,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", @@ -1812,16 +1822,17 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", "http 1.5.0", "http-body", + "httparse", "hyper", "ipnet", "libc", @@ -2050,9 +2061,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.3.5" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277ff51754a3f68f12f58446c5d006aa8baa4914ea273cce24a599cfaff33d4f" +checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193" [[package]] name = "index_vec" @@ -2062,9 +2073,9 @@ checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2090,9 +2101,9 @@ dependencies = [ [[package]] name = "inventory" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" dependencies = [ "rustversion", ] @@ -2103,16 +2114,16 @@ version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3bd0ecfbb87805f538bb7b32e5239ca0763890c623e349860ecba69469f2bb" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "libc", ] [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" @@ -2249,9 +2260,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -2266,9 +2277,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "lazycell" @@ -2289,9 +2300,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libloading" @@ -2346,7 +2357,7 @@ checksum = "77060ebe535362c3da75682cd17b0431017b6e7c5661e714fc69a7ad017d1301" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -2553,9 +2564,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "log", @@ -2563,13 +2574,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -2630,9 +2647,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ "objc2-encode", ] @@ -2643,7 +2660,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", "objc2-foundation", ] @@ -2664,7 +2681,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dispatch2", "objc2", ] @@ -2675,7 +2692,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "dispatch2", "objc2", "objc2-core-foundation", @@ -2708,7 +2725,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -2726,7 +2743,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2", "libc", "objc2", @@ -2739,7 +2756,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", "objc2-core-foundation", ] @@ -2750,7 +2767,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2771,7 +2788,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "block2", "objc2", "objc2-cloud-kit", @@ -3001,7 +3018,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix 1.1.5", "windows-sys 0.61.2", ] @@ -3077,9 +3094,9 @@ dependencies = [ [[package]] name = "powerfmt" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "ppv-lite86" @@ -3292,7 +3309,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3301,7 +3318,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", ] [[package]] @@ -3321,7 +3338,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -3457,7 +3474,7 @@ version = "0.40.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink 0.12.2", @@ -3509,20 +3526,20 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.12.1", @@ -3531,9 +3548,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "log", "once_cell", @@ -3546,9 +3563,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3660,7 +3677,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "core-foundation", "core-foundation-sys", "libc", @@ -3685,9 +3702,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "sentry" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a88b65feb368d9dde5d531a2b59b25016e36fd6e4978432371a3ee77746ca2" +checksum = "9fa951f4e644464ebfd2347a7ab03b0828c512d2448a76262cad2607b343dff4" dependencies = [ "cfg_aliases", "httpdate", @@ -3708,9 +3725,9 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b59b6298f8b1c621e7e711050f7ae9620b972215eb2822db2bfc4b061ed0cd2" +checksum = "909d0a93b15d1ddcde3894aa23d003cbefc39ef53fe6d7c8294357ced2969cfb" dependencies = [ "actix-http", "actix-web", @@ -3721,9 +3738,9 @@ dependencies = [ [[package]] name = "sentry-anyhow" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e4c4b2e5bf7bb63f8223eb4f6d24f64e2c18b4d01fdeb3d561a951e057425b" +checksum = "5ae34326bd95495e0db3c34c8888655684630cfe7609a73e863f0a7f4be5efdf" dependencies = [ "anyhow", "sentry-backtrace", @@ -3732,9 +3749,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c51511d67ed670266a93afeaa26a08019b04ad1420cb9191da30f2338d22c48" +checksum = "f4f27cf048aa63ccd5bc94c29989133eabb82e9c6bf7441b58e9d4a95837a105" dependencies = [ "backtrace", "regex", @@ -3743,9 +3760,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b757ac1f335856e8d7f5062a1fd9bc07a05a7eb3cc48247db79bf2618a490bd" +checksum = "c5ed680585dbdacdf9859c2b4dc53358ff02bd256766882e22e343836db36b22" dependencies = [ "hostname", "libc", @@ -3757,11 +3774,11 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d889520a375e5b93efb0a66def1630d21d168b0251eb55b286b03fa940ccfc84" +checksum = "c3ed398494bb8fc412a11a182c1883a10ae147587b8f6314abd222d1d626f31b" dependencies = [ - "rand 0.9.5", + "rand 0.10.3", "sentry-types", "serde", "serde_json", @@ -3770,9 +3787,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656487fd5f7b7c0105b2e82171982aac64489e0cb679436038febf070f2f76d6" +checksum = "55854bb40681e84ffd2a69578ad4ef9bfaf5bf2bf1380a745578b591fa24af22" dependencies = [ "findshlibs", "sentry-core", @@ -3780,20 +3797,20 @@ dependencies = [ [[package]] name = "sentry-log" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6c1bee99be05938885266fe5c95e0e4a2f46722cae205519f26e4ebdddd733" +checksum = "9a3dc14009e1c24b07a8ff9ff8e1bcd0913b3e448b1565e679414776f2e58651" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "log", "sentry-core", ] [[package]] name = "sentry-panic" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0fe456a7380e9df875a1ef4df1e468d35b19b9051599845fab9d8f0c71c4ae" +checksum = "b7153e68642b761ed3aa62c022795c11b2facc728b2db7484084492373c944f9" dependencies = [ "sentry-backtrace", "sentry-core", @@ -3801,9 +3818,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3481bd6aabb99f331e5f32c0fba7c2c22f41282219e7011e100263cb47e335f" +checksum = "624ee2c60caf72fa2cd6be00334b3a3ce0c6df3b1f01941ea62e3d244492d334" dependencies = [ "axum", "http 1.5.0", @@ -3816,11 +3833,11 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77796d14eedbef22c2fe78e9c69fb09f14c7ad607e624d48dce92eedc17a136e" +checksum = "e7e7db1aefdf623f4e48e21ec9e4af535024187aeafb2a4cc7e29d035def5a13" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "sentry-backtrace", "sentry-core", "tracing-core", @@ -3829,13 +3846,13 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.49.2" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc71f5ca55942d9b2901af95d5df5c5d65c164134c22a83682cac3d0b6c7ef2d" +checksum = "1c18bf7b159bdfc7c8f97816f476b4947a1937fb6bea9d38d3479e58631614ac" dependencies = [ "debugid", "hex", - "rand 0.9.5", + "rand 0.10.3", "serde", "serde_json", "thiserror", @@ -3871,14 +3888,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -3906,14 +3923,14 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -3959,9 +3976,9 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if", "cpufeatures 0.3.1", @@ -4080,9 +4097,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "socket2" @@ -4171,7 +4188,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "byteorder", "bytes", "digest", @@ -4295,9 +4312,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -4315,13 +4332,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -4350,28 +4367,28 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix 1.1.5", "windows-sys 0.61.2", ] [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -4610,7 +4627,7 @@ dependencies = [ "pluralizer", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", "toasty-core", ] @@ -4628,9 +4645,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ "bytes", "libc", @@ -4651,7 +4668,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]] @@ -4718,9 +4735,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", @@ -4732,9 +4749,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.6+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ "indexmap", "serde_core", @@ -4747,18 +4764,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.15+spec-1.1.0" +version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ "indexmap", "serde_core", @@ -4771,18 +4788,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -4806,7 +4823,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-util", "http 1.5.0", @@ -4824,7 +4841,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "bytes", "futures-core", "futures-util", @@ -4965,7 +4982,7 @@ dependencies = [ "arc-swap", "aristo", "bigdecimal", - "bitflags 2.13.1", + "bitflags 2.13.2", "branches", "bumpalo", "bytemuck", @@ -5000,7 +5017,7 @@ dependencies = [ "regex-syntax", "roaring", "rustc-hash 2.1.3", - "rustix 1.1.4", + "rustix 1.1.5", "ryu", "serde_json", "shuttle", @@ -5049,7 +5066,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0e58d029f02e442df4be0b5036ce520b49c553505d9d52ef5a98fc4056e95b7" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.2", "memchr", "miette", "strum", @@ -5175,9 +5192,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-normalization" @@ -5236,9 +5253,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.4.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" dependencies = [ "base64 0.23.1", "log", @@ -5252,9 +5269,9 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" dependencies = [ "base64 0.23.1", "http 1.5.0", @@ -5301,9 +5318,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5343,9 +5360,9 @@ dependencies = [ [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -5385,9 +5402,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -5398,19 +5415,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5418,22 +5436,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] @@ -5453,9 +5471,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -5603,6 +5621,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -5731,7 +5758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.4", + "rustix 1.1.5", ] [[package]] @@ -5747,30 +5774,30 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.56" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.56" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", @@ -5788,21 +5815,21 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" dependencies = [ "zeroize_derive", ] @@ -5850,7 +5877,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.9", ] [[package]]