From 0caa600717e25f8b8705eba86c04cab18d671723 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 18 Jun 2026 17:57:51 -0700 Subject: [PATCH 01/19] [SLOP(claude-opus-4-8)] fix(kitchen-sink): serve built frontend from server --- examples/kitchen-sink/src/server.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/kitchen-sink/src/server.ts b/examples/kitchen-sink/src/server.ts index f1e5ba9d70..bdac261b7c 100644 --- a/examples/kitchen-sink/src/server.ts +++ b/examples/kitchen-sink/src/server.ts @@ -1,6 +1,9 @@ +import { existsSync, readFileSync } from "node:fs"; import type { Server as HttpServer } from "node:http"; +import { resolve } from "node:path"; import * as v8 from "node:v8"; import { serve } from "@hono/node-server"; +import { serveStatic } from "@hono/node-server/serve-static"; import { Hono } from "hono"; import { registry } from "./index.ts"; import { resolveMode } from "./mode.ts"; @@ -164,6 +167,23 @@ if (mode === "serverful") { app.all("/api/rivet", (c) => registry.handler(c.req.raw)); } +// Serve the built frontend when it is present. The Vite build emits `dist/`, +// which only exists in production images, so dev runs skip this branch. +const distDir = "dist"; +const indexPath = resolve(process.cwd(), distDir, "index.html"); +if (existsSync(indexPath)) { + app.use("/*", serveStatic({ root: distDir })); + const indexHtml = readFileSync(indexPath, "utf8"); + app.get("/*", (c) => { + const path = new URL(c.req.url).pathname; + const last = path.slice(path.lastIndexOf("/") + 1); + // Fall through to 404 for asset-like paths so missing files do not + // resolve to the SPA shell. + if (last.includes(".")) return c.notFound(); + return c.html(indexHtml); + }); +} + const server = serve({ fetch: app.fetch, port }, () => { if (mode === "serverful") { console.log( From c40c936bcbc90e4cf5e9422eb3acc11fa3f785ec Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Fri, 19 Jun 2026 14:25:58 -0700 Subject: [PATCH 02/19] [SLOP(claude-opus-4-8-medium)] chore: add profiling cargo profile for heaptrack leak investigation --- Cargo.toml | 10 ++++ .../rivetkit-napi/src/napi_actor_events.rs | 47 +++++++++++++------ 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f544252f3..511aec200f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -672,6 +672,16 @@ lto = "fat" codegen-units = 1 opt-level = 3 +# Release-grade optimization with DWARF line info and no symbol stripping so +# heaptrack can resolve native allocation backtraces during leak investigation. +# Uses thin LTO and more codegen units to keep frames un-inlined and builds fast. +[profile.profiling] +inherits = "release" +debug = 1 +lto = "thin" +codegen-units = 16 +strip = false + [profile.quick] inherits = "dev" debug = false # no debug info → faster link, smaller binary diff --git a/rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs b/rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs index bb2e91a6f1..9da9f0847e 100644 --- a/rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs +++ b/rivetkit-typescript/packages/rivetkit-napi/src/napi_actor_events.rs @@ -173,21 +173,40 @@ async fn run_event_loop( dirty: &Arc, events: &mut ActorEvents, ) { - while let Some(event) = events.recv().await { + loop { pump_registered_tasks(tasks, registered_task_rx); - dispatch_event( - event, - bindings, - config, - ctx, - abort, - tasks, - registered_task_rx, - dirty, - ) - .await; - if ctx.has_end_reason() { - break; + + tokio::select! { + // Reap completed background tasks as they finish. A tokio JoinSet + // retains each finished task's allocation until it is joined, so + // without this the set grows for the entire actor lifetime and + // shows up as native (non-V8) RSS growth. + Some(result) = tasks.join_next(), if !tasks.is_empty() => { + if let Err(error) = result { + if !error.is_cancelled() { + tracing::error!(?error, "napi background task failed to join"); + } + } + } + event = events.recv() => { + let Some(event) = event else { + break; + }; + dispatch_event( + event, + bindings, + config, + ctx, + abort, + tasks, + registered_task_rx, + dirty, + ) + .await; + if ctx.has_end_reason() { + break; + } + } } } } From 9ff6358ec9e27f33e4c677ae4f21cfb0d25276d4 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 23 Jun 2026 12:49:51 -0700 Subject: [PATCH 03/19] Add observation and ser/de metrics for serde calls --- engine/packages/api-peer/src/internal.rs | 2 +- engine/packages/cache/src/req_config.rs | 6 +- engine/packages/depot/Cargo.toml | 6 +- engine/packages/gasoline-macros/src/lib.rs | 2 +- .../gasoline/src/builder/common/signal.rs | 2 +- .../gasoline/src/builder/common/workflow.rs | 4 +- .../gasoline/src/builder/workflow/lupe.rs | 8 +- .../gasoline/src/builder/workflow/message.rs | 2 +- .../gasoline/src/builder/workflow/signal.rs | 2 +- .../src/builder/workflow/sub_workflow.rs | 4 +- engine/packages/gasoline/src/ctx/message.rs | 7 +- engine/packages/gasoline/src/ctx/workflow.rs | 8 +- engine/packages/gasoline/src/db/mod.rs | 8 +- engine/packages/gasoline/src/history/event.rs | 7 +- engine/packages/gasoline/src/message.rs | 4 +- engine/packages/gasoline/src/registry.rs | 4 +- engine/packages/gasoline/src/signal.rs | 2 +- engine/packages/gasoline/src/workflow.rs | 4 +- engine/packages/ups-broadcast/src/lib.rs | 16 +- engine/packages/ups-broadcast/src/sim.rs | 2509 ----------------- engine/packages/util/src/lib.rs | 128 + engine/packages/util/src/metrics.rs | 34 + engine/packages/util/src/serde.rs | 106 + 23 files changed, 312 insertions(+), 2563 deletions(-) delete mode 100644 engine/packages/ups-broadcast/src/sim.rs create mode 100644 engine/packages/util/src/metrics.rs diff --git a/engine/packages/api-peer/src/internal.rs b/engine/packages/api-peer/src/internal.rs index 9b39644a08..eaa1e9ed34 100644 --- a/engine/packages/api-peer/src/internal.rs +++ b/engine/packages/api-peer/src/internal.rs @@ -62,7 +62,7 @@ pub async fn set_tracing_config( body: SetTracingConfigRequest, ) -> Result { // Broadcast message to all services via UPS - let message = serde_json::to_vec(&body)?; + let message = rivet_util::serde::json_to_vec!(&body)?; ctx.ups()? .publish(TracingConfigSubject, &message, PublishOpts::broadcast()) diff --git a/engine/packages/cache/src/req_config.rs b/engine/packages/cache/src/req_config.rs index c97df9c512..1b92618689 100644 --- a/engine/packages/cache/src/req_config.rs +++ b/engine/packages/cache/src/req_config.rs @@ -365,7 +365,7 @@ impl RequestConfig { keys: cache_keys.clone(), }; - let payload = serde_json::to_vec(&message)?; + let payload = rivet_util::serde::json_to_vec!(&message)?; if let Err(err) = ups .publish( @@ -495,12 +495,12 @@ impl RequestConfig { keys, getter, |value: &Value| -> Result> { - serde_json::to_vec(&value) + rivet_util::serde::json_to_vec!(&value) .map_err(Error::SerdeEncode) .map_err(Into::into) }, |value: &[u8]| -> Result { - serde_json::from_slice(value) + rivet_util::serde::json_from_slice!(value) .map_err(Error::SerdeDecode) .map_err(Into::into) }, diff --git a/engine/packages/depot/Cargo.toml b/engine/packages/depot/Cargo.toml index fbf16b652d..ff38b8cdd4 100644 --- a/engine/packages/depot/Cargo.toml +++ b/engine/packages/depot/Cargo.toml @@ -30,19 +30,19 @@ rivet-error.workspace = true rivet-metrics.workspace = true rivet-pools.workspace = true rivet-runtime.workspace = true +rivet-util.workspace = true +rusqlite.workspace = true scc.workspace = true -serde.workspace = true serde_bare.workspace = true serde_json.workspace = true +serde.workspace = true sha2.workspace = true -rusqlite.workspace = true tempfile.workspace = true tokio.workspace = true tokio-util.workspace = true tracing.workspace = true universaldb.workspace = true universalpubsub.workspace = true -util.workspace = true uuid.workspace = true vbare.workspace = true diff --git a/engine/packages/gasoline-macros/src/lib.rs b/engine/packages/gasoline-macros/src/lib.rs index ba241cb186..7b4810a118 100644 --- a/engine/packages/gasoline-macros/src/lib.rs +++ b/engine/packages/gasoline-macros/src/lib.rs @@ -403,7 +403,7 @@ pub fn signal(attr: TokenStream, item: TokenStream) -> TokenStream { } fn parse(_name: &str, body: &serde_json::value::RawValue) -> gas::prelude::WorkflowResult { - serde_json::from_str(body.get()).map_err(WorkflowError::DeserializeSignalBody) + rivet_util::serde::json_from_str!(body.get()).map_err(WorkflowError::DeserializeSignalBody) } } }; diff --git a/engine/packages/gasoline/src/builder/common/signal.rs b/engine/packages/gasoline/src/builder/common/signal.rs index 84a473c8dd..4a56c0a7dc 100644 --- a/engine/packages/gasoline/src/builder/common/signal.rs +++ b/engine/packages/gasoline/src/builder/common/signal.rs @@ -129,7 +129,7 @@ impl SignalBuilder { tracing::Span::current().record("signal_id", signal_id.to_string()); // Serialize input - let input_val = serde_json::value::to_raw_value(&self.body) + let input_val = rivet_util::serde::json_to_raw_value!(&self.body) .map_err(WorkflowError::SerializeSignalBody)?; match ( diff --git a/engine/packages/gasoline/src/builder/common/workflow.rs b/engine/packages/gasoline/src/builder/common/workflow.rs index da650d511b..f06ba4080e 100644 --- a/engine/packages/gasoline/src/builder/common/workflow.rs +++ b/engine/packages/gasoline/src/builder/common/workflow.rs @@ -81,7 +81,7 @@ where return self; } - match serde_json::to_value(&v) { + match rivet_util::serde::json_to_value!(&v) { Ok(v) => { self.tags.insert(k.to_string(), v); } @@ -125,7 +125,7 @@ where } // Serialize input - let input_val = serde_json::value::to_raw_value(&input) + let input_val = rivet_util::serde::json_to_raw_value!(&input) .map_err(WorkflowError::SerializeWorkflowInput)?; let actual_workflow_id = self diff --git a/engine/packages/gasoline/src/builder/workflow/lupe.rs b/engine/packages/gasoline/src/builder/workflow/lupe.rs index 8b45b79615..8dd72b34b8 100644 --- a/engine/packages/gasoline/src/builder/workflow/lupe.rs +++ b/engine/packages/gasoline/src/builder/workflow/lupe.rs @@ -72,7 +72,7 @@ impl<'a, S: Serialize + DeserializeOwned> LoopBuilder<'a, S> { (loop_event.iteration, state, output, None) } else { - let state_val = serde_json::value::to_raw_value(&state) + let state_val = rivet_util::serde::json_to_raw_value!(&state) .map_err(WorkflowError::SerializeLoopOutput)?; // Clone data to move into future @@ -219,7 +219,7 @@ impl<'a, S: Serialize + DeserializeOwned> LoopBuilder<'a, S> { if iteration % commit_interval.unwrap_or(DEFAULT_LOOP_COMMIT_INTERVAL) == 0 { - let state_val = serde_json::value::to_raw_value(&state) + let state_val = rivet_util::serde::json_to_raw_value!(&state) .map_err(WorkflowError::SerializeLoopOutput)?; // Clone data to move into future @@ -251,9 +251,9 @@ impl<'a, S: Serialize + DeserializeOwned> LoopBuilder<'a, S> { Loop::Break(res) => { iteration += 1; - let state_val = serde_json::value::to_raw_value(&state) + let state_val = rivet_util::serde::json_to_raw_value!(&state) .map_err(WorkflowError::SerializeLoopOutput)?; - let output_val = serde_json::value::to_raw_value(&res) + let output_val = rivet_util::serde::json_to_raw_value!(&res) .map_err(WorkflowError::SerializeLoopOutput)?; // Commit loop output and final state to db. Note that we don't defer this because diff --git a/engine/packages/gasoline/src/builder/workflow/message.rs b/engine/packages/gasoline/src/builder/workflow/message.rs index d21869956c..aebb3a08f2 100644 --- a/engine/packages/gasoline/src/builder/workflow/message.rs +++ b/engine/packages/gasoline/src/builder/workflow/message.rs @@ -76,7 +76,7 @@ impl<'a, M: Message> MessageBuilder<'a, M> { let start_instant = Instant::now(); // Serialize body - let body_val = serde_json::value::to_raw_value(&self.body) + let body_val = rivet_util::serde::json_to_raw_value!(&self.body) .map_err(WorkflowError::SerializeMessageBody)?; let topic = self.topic.unwrap_or_else(|| "*".to_string()); let tags = serde_json::Value::Object( diff --git a/engine/packages/gasoline/src/builder/workflow/signal.rs b/engine/packages/gasoline/src/builder/workflow/signal.rs index 34a7e8e63f..fb34607d72 100644 --- a/engine/packages/gasoline/src/builder/workflow/signal.rs +++ b/engine/packages/gasoline/src/builder/workflow/signal.rs @@ -145,7 +145,7 @@ impl<'a, T: Signal + Serialize> SignalBuilder<'a, T> { let db_write_duration; // Serialize input - let input_val = serde_json::value::to_raw_value(&self.body) + let input_val = rivet_util::serde::json_to_raw_value!(&self.body) .map_err(WorkflowError::SerializeSignalBody)?; match ( diff --git a/engine/packages/gasoline/src/builder/workflow/sub_workflow.rs b/engine/packages/gasoline/src/builder/workflow/sub_workflow.rs index 31c5ae8f34..d83a8e8c84 100644 --- a/engine/packages/gasoline/src/builder/workflow/sub_workflow.rs +++ b/engine/packages/gasoline/src/builder/workflow/sub_workflow.rs @@ -155,7 +155,7 @@ where } // Serialize input - let input_val = serde_json::value::to_raw_value(input) + let input_val = rivet_util::serde::json_to_raw_value!(input) .map_err(WorkflowError::SerializeWorkflowOutput)?; let actual_sub_workflow_id = ctx @@ -228,7 +228,7 @@ where // Err for version mismatch self.ctx.compare_version("sub workflow", self.version)?; - let input_val = serde_json::value::to_raw_value(&input) + let input_val = rivet_util::serde::json_to_raw_value!(&input) .map_err(WorkflowError::SerializeWorkflowInput)?; let mut branch = self .ctx diff --git a/engine/packages/gasoline/src/ctx/message.rs b/engine/packages/gasoline/src/ctx/message.rs index 181dfe0a28..a6edb2fe32 100644 --- a/engine/packages/gasoline/src/ctx/message.rs +++ b/engine/packages/gasoline/src/ctx/message.rs @@ -96,8 +96,8 @@ impl MessageCtx { let ts = duration_since_epoch.as_millis() as i64; // Serialize the body - let body_buf = - serde_json::to_string(&message_body).map_err(WorkflowError::SerializeMessage)?; + let body_buf = rivet_util::serde::json_to_string!(&message_body) + .map_err(WorkflowError::SerializeMessage)?; let body_buf_len = body_buf.len(); let body_buf = serde_json::value::RawValue::from_string(body_buf) .map_err(WorkflowError::SerializeMessage)?; @@ -111,7 +111,8 @@ impl MessageCtx { ts, body: &body_buf, }; - let message_buf = serde_json::to_vec(&message).map_err(WorkflowError::SerializeMessage)?; + let message_buf = + rivet_util::serde::json_to_vec!(&message).map_err(WorkflowError::SerializeMessage)?; tracing::debug!( %subject, diff --git a/engine/packages/gasoline/src/ctx/workflow.rs b/engine/packages/gasoline/src/ctx/workflow.rs index 7ed12c3e13..c89974fc62 100644 --- a/engine/packages/gasoline/src/ctx/workflow.rs +++ b/engine/packages/gasoline/src/ctx/workflow.rs @@ -302,9 +302,9 @@ impl WorkflowCtx { tracing::debug!("activity success"); // Write output - let input_val = serde_json::value::to_raw_value(input) + let input_val = rivet_util::serde::json_to_raw_value!(input) .map_err(WorkflowError::SerializeActivityInput)?; - let output_val = serde_json::value::to_raw_value(&output) + let output_val = rivet_util::serde::json_to_raw_value!(&output) .map_err(WorkflowError::SerializeActivityOutput)?; tokio::try_join!( @@ -346,7 +346,7 @@ impl WorkflowCtx { tracing::error!(?err, "activity error"); let err_str = err.to_string(); - let input_val = serde_json::value::to_raw_value(input) + let input_val = rivet_util::serde::json_to_raw_value!(input) .map_err(WorkflowError::SerializeActivityInput)?; // Write error (failed state) @@ -380,7 +380,7 @@ impl WorkflowCtx { tracing::debug!("activity timeout"); let err_str = err.to_string(); - let input_val = serde_json::value::to_raw_value(input) + let input_val = rivet_util::serde::json_to_raw_value!(input) .map_err(WorkflowError::SerializeActivityInput)?; self.db diff --git a/engine/packages/gasoline/src/db/mod.rs b/engine/packages/gasoline/src/db/mod.rs index 4f07de3758..5ac2af04c5 100644 --- a/engine/packages/gasoline/src/db/mod.rs +++ b/engine/packages/gasoline/src/db/mod.rs @@ -310,17 +310,19 @@ pub struct WorkflowData { impl WorkflowData { pub fn parse_input(&self) -> WorkflowResult { - serde_json::from_str(self.input.get()).map_err(WorkflowError::DeserializeWorkflowInput) + rivet_util::serde::json_from_str!(self.input.get()) + .map_err(WorkflowError::DeserializeWorkflowInput) } pub fn parse_state(&self) -> WorkflowResult { - serde_json::from_str(self.state.get()).map_err(WorkflowError::DeserializeWorkflowState) + rivet_util::serde::json_from_str!(self.state.get()) + .map_err(WorkflowError::DeserializeWorkflowState) } pub fn parse_output(&self) -> WorkflowResult> { self.output .as_ref() - .map(|x| serde_json::from_str(x.get())) + .map(|x| rivet_util::serde::json_from_str!(x.get())) .transpose() .map_err(WorkflowError::DeserializeWorkflowOutput) } diff --git a/engine/packages/gasoline/src/history/event.rs b/engine/packages/gasoline/src/history/event.rs index c7841f3140..0587f7442e 100644 --- a/engine/packages/gasoline/src/history/event.rs +++ b/engine/packages/gasoline/src/history/event.rs @@ -133,7 +133,7 @@ impl ActivityEvent { pub fn parse_output(&self) -> WorkflowResult> { self.output .as_ref() - .map(|x| serde_json::from_str(x.get())) + .map(|x| rivet_util::serde::json_from_str!(x.get())) .transpose() .map_err(WorkflowError::DeserializeActivityOutput) } @@ -166,13 +166,14 @@ pub struct LoopEvent { impl LoopEvent { pub fn parse_state(&self) -> WorkflowResult { - serde_json::from_str(self.state.get()).map_err(WorkflowError::DeserializeLoopState) + rivet_util::serde::json_from_str!(self.state.get()) + .map_err(WorkflowError::DeserializeLoopState) } pub fn parse_output(&self) -> WorkflowResult> { self.output .as_ref() - .map(|x| serde_json::from_str(x.get())) + .map(|x| rivet_util::serde::json_from_str!(x.get())) .transpose() .map_err(WorkflowError::DeserializeLoopOutput) } diff --git a/engine/packages/gasoline/src/message.rs b/engine/packages/gasoline/src/message.rs index 24eb9e983b..db30cc2b68 100644 --- a/engine/packages/gasoline/src/message.rs +++ b/engine/packages/gasoline/src/message.rs @@ -35,7 +35,7 @@ where wrapper: PubsubMessageWrapper<'_>, ) -> WorkflowResult { // Deserialize the body - let body = serde_json::from_str(wrapper.body.get()) + let body = rivet_util::serde::json_from_str!(wrapper.body.get()) .map_err(WorkflowError::DeserializeMessageBody)?; Ok(PubsubMessage { @@ -51,7 +51,7 @@ where pub(crate) fn deserialize_wrapper<'a>( buf: &'a [u8], ) -> WorkflowResult> { - serde_json::from_slice(buf).map_err(WorkflowError::DeserializeMessage) + rivet_util::serde::json_from_slice!(buf).map_err(WorkflowError::DeserializeMessage) } } diff --git a/engine/packages/gasoline/src/registry.rs b/engine/packages/gasoline/src/registry.rs index 73bad1ffb6..bcc9e77a40 100644 --- a/engine/packages/gasoline/src/registry.rs +++ b/engine/packages/gasoline/src/registry.rs @@ -62,7 +62,7 @@ impl Registry { run: |ctx| { async move { // Deserialize input - let input = serde_json::from_str(ctx.input().get()) + let input = rivet_util::serde::json_from_str!(ctx.input().get()) .map_err(WorkflowError::DeserializeWorkflowInput)?; // Run workflow @@ -79,7 +79,7 @@ impl Registry { }; // Serialize output - let output_val = serde_json::value::to_raw_value(&output) + let output_val = rivet_util::serde::json_to_raw_value!(&output) .map_err(WorkflowError::SerializeWorkflowOutput)?; Ok(output_val) diff --git a/engine/packages/gasoline/src/signal.rs b/engine/packages/gasoline/src/signal.rs index 47f2b27ac6..945a8c5344 100644 --- a/engine/packages/gasoline/src/signal.rs +++ b/engine/packages/gasoline/src/signal.rs @@ -73,7 +73,7 @@ macro_rules! join_signal { if name == <$types as gas::signal::Signal>::NAME { std::result::Result::Ok( Self::$names( - serde_json::from_str(body.get()) + rivet_util::serde::json_from_str!(body.get()) .map_err(WorkflowError::DeserializeSignalBody)? ) ) diff --git a/engine/packages/gasoline/src/workflow.rs b/engine/packages/gasoline/src/workflow.rs index e44c2e921b..bb16ded732 100644 --- a/engine/packages/gasoline/src/workflow.rs +++ b/engine/packages/gasoline/src/workflow.rs @@ -33,7 +33,7 @@ impl<'a, T: DeserializeOwned + Serialize> StateGuard<'a, T> { pub(crate) fn new( guard: MutexGuard<'a, (Box, bool)>, ) -> Result { - let value = serde_json::from_str::(guard.0.get())?; + let value = rivet_util::observe!(serde_json::from_str::(guard.0.get())?); Ok(Self { guard, @@ -60,7 +60,7 @@ impl<'a, T: DeserializeOwned + Serialize> std::ops::DerefMut for StateGuard<'a, impl<'a, T: DeserializeOwned + Serialize> Drop for StateGuard<'a, T> { fn drop(&mut self) { // TODO: Somehow don't panic when committing state back into mutex - self.guard.0 = serde_json::value::to_raw_value(&self.inner).expect("bad state"); + self.guard.0 = rivet_util::serde::json_to_raw_value!(&self.inner).expect("bad state"); } } diff --git a/engine/packages/ups-broadcast/src/lib.rs b/engine/packages/ups-broadcast/src/lib.rs index b3d0c15947..69cf170500 100644 --- a/engine/packages/ups-broadcast/src/lib.rs +++ b/engine/packages/ups-broadcast/src/lib.rs @@ -5,8 +5,6 @@ use universalpubsub::NextOutput; use universalpubsub::PublishOpts; use universalpubsub::Subject; -mod sim; - pub const BROADCAST_TOPIC: &str = "rivet.ups.broadcast"; pub struct BroadcastSubject; @@ -28,7 +26,7 @@ impl Subject for BroadcastSubject { } #[tracing::instrument(skip_all)] -pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { +pub async fn start(_config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { let ups = pools.ups()?; let mut sub = ups.subscribe(BroadcastSubject).await?; @@ -38,18 +36,6 @@ pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> R let handle = tokio::spawn(async move { while let Ok(NextOutput::Message(_)) = sub.next().await {} }); - if let Some(sim_config) = sim::Config::from_env()? { - let sim_udb = pools.udb().ok(); - let sim_ups = sim::pubsub_for_sim( - &config, - &ups, - sim_config.force_driver, - sim_config.disable_memory_optimization, - ) - .await?; - sim::spawn(sim_ups, sim_udb, sim_config); - } - loop { if let Err(err) = ups .publish(BroadcastSubject, &[], PublishOpts::broadcast()) diff --git a/engine/packages/ups-broadcast/src/sim.rs b/engine/packages/ups-broadcast/src/sim.rs deleted file mode 100644 index e901b14d8a..0000000000 --- a/engine/packages/ups-broadcast/src/sim.rs +++ /dev/null @@ -1,2509 +0,0 @@ -use std::{ - borrow::Cow, - env, fmt, hint, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, - time::{Duration, Instant}, -}; - -use anyhow::{bail, Context, Result}; -use futures_util::{FutureExt, StreamExt}; -use gas::prelude::Id; -use rivet_pools::UdbPool; -use serde::Deserialize; -use universaldb::{ - prelude::{PackError, PackResult, TupleDepth, TuplePack, TupleUnpack, VersionstampOffset}, - utils::IsolationLevel::{Serializable, Snapshot}, - RangeOption, Subspace, -}; -use universalpubsub::{NextOutput, PubSub, PublishOpts, Subject, Subscriber}; - -const ENV_PREFIX: &str = "UPS_BROADCAST_SIM"; -const TICK: Duration = Duration::from_millis(10); -const PUBLISH_MAX_IN_FLIGHT: usize = 8_192; -const DEFAULT_TUNE_PATH: &str = "/tmp/ups-broadcast-sim-tune.json"; -const TUNE_POLL_INTERVAL: Duration = Duration::from_secs(1); -const TUNE_SUBJECT: &str = "rivet.ups.broadcast.sim.tune"; -const TUNE_SUBJECT_ROOT: &str = "rivet.ups.broadcast.sim.tune"; -const GATEWAY_MEMBERSHIP_PREFIX: &[u8] = b"rivet/ups-broadcast/sim/gateway-members"; -const GATEWAY_MEMBERSHIP_TX: &str = "ups_broadcast_sim_gateway_membership"; -const UDB_HOT_COUNTER_TX: &str = "ups_broadcast_sim_udb_hot_counter"; -const UDB_READ_SCAN_SEED_TX: &str = "ups_broadcast_sim_udb_read_scan_seed"; -const UDB_READ_SCAN_TX: &str = "ups_broadcast_sim_udb_read_scan"; -const UDB_CONFLICT_SEED_TX: &str = "ups_broadcast_sim_udb_conflict_seed"; -const UDB_CONFLICT_TX: &str = "ups_broadcast_sim_udb_conflict"; -const UDB_READ_SCAN_SEED_BATCH_SIZE: u64 = 500; -const UDB_CONFLICT_SEED_BATCH_SIZE: u64 = 500; -const READ_SCAN_KEY_ROOT: usize = 1; -const CONFLICT_KEY_ROOT: usize = 2; -static SUBJECT_SEQ: AtomicU64 = AtomicU64::new(0); -static HOT_COUNTER_SEQ: AtomicU64 = AtomicU64::new(0); -static READ_SCAN_SEQ: AtomicU64 = AtomicU64::new(0); -static CONFLICT_SEQ: AtomicU64 = AtomicU64::new(0); - -pub struct Config { - pub force_driver: bool, - pub disable_memory_optimization: bool, - tune_path: Option, - gateway_subjects: usize, - gateway_subscribers: usize, - gateway_publish_rps: f64, - gateway_payload_bytes: usize, - gateway_work_delay_ms: u64, - gateway_work_cpu_us: u64, - gateway_spread_replicas: usize, - gateway_spread_member_ttl_ms: u64, - envoy_subjects: usize, - envoy_responders: usize, - envoy_queue_group: Option, - envoy_request_unknown_root: bool, - envoy_request_rps: f64, - envoy_request_payload_bytes: usize, - envoy_request_timeout_ms: u64, - envoy_request_max_in_flight: usize, - envoy_work_delay_ms: u64, - envoy_work_cpu_us: u64, - envoy_eviction_subscribers: usize, - envoy_eviction_broadcast_rps: f64, - envoy_eviction_work_delay_ms: u64, - envoy_eviction_work_cpu_us: u64, - worker_bump_subscribers: usize, - worker_bump_broadcast_rps: f64, - worker_bump_work_delay_ms: u64, - worker_bump_work_cpu_us: u64, - serverless_subscribers: usize, - serverless_publish_rps: f64, - serverless_payload_bytes: usize, - serverless_work_delay_ms: u64, - serverless_work_cpu_us: u64, - cache_purge_subscribers: usize, - cache_purge_broadcast_rps: f64, - cache_purge_payload_bytes: usize, - cache_purge_work_delay_ms: u64, - cache_purge_work_cpu_us: u64, - tracing_config_subscribers: usize, - tracing_config_broadcast_rps: f64, - tracing_config_payload_bytes: usize, - tracing_config_work_delay_ms: u64, - tracing_config_work_cpu_us: u64, - route_stopped_subscribers: usize, - route_churn_rps: f64, - route_ephemeral_hold_ms: u64, - route_stopped_hold_ms: u64, - route_max_in_flight: usize, - route_work_delay_ms: u64, - route_work_cpu_us: u64, - workflow_signal_churn_rps: f64, - workflow_signal_hold_ms: u64, - workflow_signal_publish_rps: f64, - workflow_signal_work_delay_ms: u64, - workflow_signal_work_cpu_us: u64, - workflow_complete_publish_rps: f64, - udb_hot_counter_rps: f64, - udb_hot_counter_max_in_flight: usize, - udb_hot_counter_namespace_id: Id, - udb_hot_counter_actor_name: String, - udb_read_scan_rps: f64, - udb_read_scan_max_in_flight: usize, - udb_read_scan_seed_keys: u64, - udb_read_scan_keys_per_tx: usize, - udb_read_scan_value_bytes: usize, - udb_read_scan_unpack_keys: bool, - udb_conflict_rps: f64, - udb_conflict_max_in_flight: usize, - udb_conflict_keys: u64, -} - -impl Config { - pub fn from_env() -> Result> { - if !env_bool("ENABLED", false)? { - return Ok(None); - } - - let profile = env_string("PROFILE").with_context(|| { - format!("{ENV_PREFIX}_PROFILE must be set explicitly when {ENV_PREFIX}_ENABLED=true") - })?; - let mut config = match profile.as_str() { - "custom" => Self::custom(), - "staging_peak" => Self::staging_peak(), - other => bail!("unknown {ENV_PREFIX}_PROFILE: {other}"), - }; - - config.force_driver = env_bool("FORCE_DRIVER", config.force_driver)?; - config.disable_memory_optimization = env_bool( - "DISABLE_MEMORY_OPTIMIZATION", - config.disable_memory_optimization, - )?; - config.tune_path = env_string("TUNE_PATH") - .map(|x| if x.is_empty() { None } else { Some(x) }) - .unwrap_or(config.tune_path); - config.gateway_subjects = env_usize("GATEWAY_SUBJECTS", config.gateway_subjects)?; - config.gateway_subscribers = env_usize("GATEWAY_SUBSCRIBERS", config.gateway_subscribers)?; - config.gateway_publish_rps = env_f64("GATEWAY_PUBLISH_RPS", config.gateway_publish_rps)?; - config.gateway_payload_bytes = - env_usize("GATEWAY_PAYLOAD_BYTES", config.gateway_payload_bytes)?; - config.gateway_work_delay_ms = - env_u64("GATEWAY_WORK_DELAY_MS", config.gateway_work_delay_ms)?; - config.gateway_work_cpu_us = env_u64("GATEWAY_WORK_CPU_US", config.gateway_work_cpu_us)?; - config.gateway_spread_replicas = - env_usize("GATEWAY_SPREAD_REPLICAS", config.gateway_spread_replicas)?; - config.gateway_spread_member_ttl_ms = env_u64( - "GATEWAY_SPREAD_MEMBER_TTL_MS", - config.gateway_spread_member_ttl_ms, - )?; - config.envoy_subjects = env_usize("ENVOY_SUBJECTS", config.envoy_subjects)?; - config.envoy_responders = env_usize("ENVOY_RESPONDERS", config.envoy_responders)?; - config.envoy_queue_group = env_string("ENVOY_QUEUE_GROUP") - .map(|x| if x.is_empty() { None } else { Some(x) }) - .unwrap_or(config.envoy_queue_group); - config.envoy_request_unknown_root = env_bool( - "ENVOY_REQUEST_UNKNOWN_ROOT", - config.envoy_request_unknown_root, - )?; - config.envoy_request_rps = env_f64("ENVOY_REQUEST_RPS", config.envoy_request_rps)?; - config.envoy_request_payload_bytes = env_usize( - "ENVOY_REQUEST_PAYLOAD_BYTES", - config.envoy_request_payload_bytes, - )?; - config.envoy_request_timeout_ms = - env_u64("ENVOY_REQUEST_TIMEOUT_MS", config.envoy_request_timeout_ms)?; - config.envoy_request_max_in_flight = env_usize( - "ENVOY_REQUEST_MAX_IN_FLIGHT", - config.envoy_request_max_in_flight, - )?; - config.envoy_work_delay_ms = env_u64("ENVOY_WORK_DELAY_MS", config.envoy_work_delay_ms)?; - config.envoy_work_cpu_us = env_u64("ENVOY_WORK_CPU_US", config.envoy_work_cpu_us)?; - config.envoy_eviction_subscribers = env_usize( - "ENVOY_EVICTION_SUBSCRIBERS", - config.envoy_eviction_subscribers, - )?; - config.envoy_eviction_broadcast_rps = env_f64( - "ENVOY_EVICTION_BROADCAST_RPS", - config.envoy_eviction_broadcast_rps, - )?; - config.envoy_eviction_work_delay_ms = env_u64( - "ENVOY_EVICTION_WORK_DELAY_MS", - config.envoy_eviction_work_delay_ms, - )?; - config.envoy_eviction_work_cpu_us = env_u64( - "ENVOY_EVICTION_WORK_CPU_US", - config.envoy_eviction_work_cpu_us, - )?; - config.worker_bump_subscribers = - env_usize("WORKER_BUMP_SUBSCRIBERS", config.worker_bump_subscribers)?; - config.worker_bump_broadcast_rps = env_f64( - "WORKER_BUMP_BROADCAST_RPS", - config.worker_bump_broadcast_rps, - )?; - config.worker_bump_work_delay_ms = env_u64( - "WORKER_BUMP_WORK_DELAY_MS", - config.worker_bump_work_delay_ms, - )?; - config.worker_bump_work_cpu_us = - env_u64("WORKER_BUMP_WORK_CPU_US", config.worker_bump_work_cpu_us)?; - config.serverless_subscribers = - env_usize("SERVERLESS_SUBSCRIBERS", config.serverless_subscribers)?; - config.serverless_publish_rps = - env_f64("SERVERLESS_PUBLISH_RPS", config.serverless_publish_rps)?; - config.serverless_payload_bytes = - env_usize("SERVERLESS_PAYLOAD_BYTES", config.serverless_payload_bytes)?; - config.serverless_work_delay_ms = - env_u64("SERVERLESS_WORK_DELAY_MS", config.serverless_work_delay_ms)?; - config.serverless_work_cpu_us = - env_u64("SERVERLESS_WORK_CPU_US", config.serverless_work_cpu_us)?; - config.cache_purge_subscribers = - env_usize("CACHE_PURGE_SUBSCRIBERS", config.cache_purge_subscribers)?; - config.cache_purge_broadcast_rps = env_f64( - "CACHE_PURGE_BROADCAST_RPS", - config.cache_purge_broadcast_rps, - )?; - config.cache_purge_payload_bytes = env_usize( - "CACHE_PURGE_PAYLOAD_BYTES", - config.cache_purge_payload_bytes, - )?; - config.cache_purge_work_delay_ms = env_u64( - "CACHE_PURGE_WORK_DELAY_MS", - config.cache_purge_work_delay_ms, - )?; - config.cache_purge_work_cpu_us = - env_u64("CACHE_PURGE_WORK_CPU_US", config.cache_purge_work_cpu_us)?; - config.tracing_config_subscribers = env_usize( - "TRACING_CONFIG_SUBSCRIBERS", - config.tracing_config_subscribers, - )?; - config.tracing_config_broadcast_rps = env_f64( - "TRACING_CONFIG_BROADCAST_RPS", - config.tracing_config_broadcast_rps, - )?; - config.tracing_config_payload_bytes = env_usize( - "TRACING_CONFIG_PAYLOAD_BYTES", - config.tracing_config_payload_bytes, - )?; - config.tracing_config_work_delay_ms = env_u64( - "TRACING_CONFIG_WORK_DELAY_MS", - config.tracing_config_work_delay_ms, - )?; - config.tracing_config_work_cpu_us = env_u64( - "TRACING_CONFIG_WORK_CPU_US", - config.tracing_config_work_cpu_us, - )?; - config.route_stopped_subscribers = env_usize( - "ROUTE_STOPPED_SUBSCRIBERS", - config.route_stopped_subscribers, - )?; - config.route_churn_rps = env_f64("ROUTE_CHURN_RPS", config.route_churn_rps)?; - config.route_ephemeral_hold_ms = - env_u64("ROUTE_EPHEMERAL_HOLD_MS", config.route_ephemeral_hold_ms)?; - config.route_stopped_hold_ms = - env_u64("ROUTE_STOPPED_HOLD_MS", config.route_stopped_hold_ms)?; - config.route_max_in_flight = env_usize("ROUTE_MAX_IN_FLIGHT", config.route_max_in_flight)?; - config.route_work_delay_ms = env_u64("ROUTE_WORK_DELAY_MS", config.route_work_delay_ms)?; - config.route_work_cpu_us = env_u64("ROUTE_WORK_CPU_US", config.route_work_cpu_us)?; - config.workflow_signal_churn_rps = env_f64( - "WORKFLOW_SIGNAL_CHURN_RPS", - config.workflow_signal_churn_rps, - )?; - config.workflow_signal_hold_ms = - env_u64("WORKFLOW_SIGNAL_HOLD_MS", config.workflow_signal_hold_ms)?; - config.workflow_signal_publish_rps = env_f64( - "WORKFLOW_SIGNAL_PUBLISH_RPS", - config.workflow_signal_publish_rps, - )?; - config.workflow_signal_work_delay_ms = env_u64( - "WORKFLOW_SIGNAL_WORK_DELAY_MS", - config.workflow_signal_work_delay_ms, - )?; - config.workflow_signal_work_cpu_us = env_u64( - "WORKFLOW_SIGNAL_WORK_CPU_US", - config.workflow_signal_work_cpu_us, - )?; - config.workflow_complete_publish_rps = env_f64( - "WORKFLOW_COMPLETE_PUBLISH_RPS", - config.workflow_complete_publish_rps, - )?; - config.udb_hot_counter_rps = env_f64("UDB_HOT_COUNTER_RPS", config.udb_hot_counter_rps)?; - config.udb_hot_counter_max_in_flight = env_usize( - "UDB_HOT_COUNTER_MAX_IN_FLIGHT", - config.udb_hot_counter_max_in_flight, - )?; - config.udb_hot_counter_namespace_id = env_id( - "UDB_HOT_COUNTER_NAMESPACE_ID", - config.udb_hot_counter_namespace_id, - )?; - config.udb_hot_counter_actor_name = - env_string("UDB_HOT_COUNTER_ACTOR_NAME").unwrap_or(config.udb_hot_counter_actor_name); - config.udb_read_scan_rps = env_f64("UDB_READ_SCAN_RPS", config.udb_read_scan_rps)?; - config.udb_read_scan_max_in_flight = env_usize( - "UDB_READ_SCAN_MAX_IN_FLIGHT", - config.udb_read_scan_max_in_flight, - )?; - config.udb_read_scan_seed_keys = - env_u64("UDB_READ_SCAN_SEED_KEYS", config.udb_read_scan_seed_keys)?; - config.udb_read_scan_keys_per_tx = env_usize( - "UDB_READ_SCAN_KEYS_PER_TX", - config.udb_read_scan_keys_per_tx, - )?; - config.udb_read_scan_value_bytes = env_usize( - "UDB_READ_SCAN_VALUE_BYTES", - config.udb_read_scan_value_bytes, - )?; - config.udb_read_scan_unpack_keys = env_bool( - "UDB_READ_SCAN_UNPACK_KEYS", - config.udb_read_scan_unpack_keys, - )?; - config.udb_conflict_rps = env_f64("UDB_CONFLICT_RPS", config.udb_conflict_rps)?; - config.udb_conflict_max_in_flight = env_usize( - "UDB_CONFLICT_MAX_IN_FLIGHT", - config.udb_conflict_max_in_flight, - )?; - config.udb_conflict_keys = env_u64("UDB_CONFLICT_KEYS", config.udb_conflict_keys)?; - - validate_rate("GATEWAY_PUBLISH_RPS", config.gateway_publish_rps)?; - validate_rate("ENVOY_REQUEST_RPS", config.envoy_request_rps)?; - validate_rate( - "ENVOY_EVICTION_BROADCAST_RPS", - config.envoy_eviction_broadcast_rps, - )?; - validate_rate( - "WORKER_BUMP_BROADCAST_RPS", - config.worker_bump_broadcast_rps, - )?; - validate_rate("SERVERLESS_PUBLISH_RPS", config.serverless_publish_rps)?; - validate_rate( - "CACHE_PURGE_BROADCAST_RPS", - config.cache_purge_broadcast_rps, - )?; - validate_rate( - "TRACING_CONFIG_BROADCAST_RPS", - config.tracing_config_broadcast_rps, - )?; - validate_rate("ROUTE_CHURN_RPS", config.route_churn_rps)?; - validate_rate( - "WORKFLOW_SIGNAL_CHURN_RPS", - config.workflow_signal_churn_rps, - )?; - validate_rate( - "WORKFLOW_SIGNAL_PUBLISH_RPS", - config.workflow_signal_publish_rps, - )?; - validate_rate( - "WORKFLOW_COMPLETE_PUBLISH_RPS", - config.workflow_complete_publish_rps, - )?; - validate_rate("UDB_HOT_COUNTER_RPS", config.udb_hot_counter_rps)?; - validate_rate("UDB_READ_SCAN_RPS", config.udb_read_scan_rps)?; - validate_rate("UDB_CONFLICT_RPS", config.udb_conflict_rps)?; - - Ok(Some(config)) - } - - fn custom() -> Self { - Self { - force_driver: true, - disable_memory_optimization: false, - tune_path: Some(DEFAULT_TUNE_PATH.to_string()), - gateway_subjects: 0, - gateway_subscribers: 0, - gateway_publish_rps: 0.0, - gateway_payload_bytes: 192, - gateway_work_delay_ms: 0, - gateway_work_cpu_us: 0, - gateway_spread_replicas: 0, - gateway_spread_member_ttl_ms: 15_000, - envoy_subjects: 0, - envoy_responders: 0, - envoy_queue_group: None, - envoy_request_unknown_root: true, - envoy_request_rps: 0.0, - envoy_request_payload_bytes: 64, - envoy_request_timeout_ms: 30_000, - envoy_request_max_in_flight: 8_192, - envoy_work_delay_ms: 0, - envoy_work_cpu_us: 0, - envoy_eviction_subscribers: 0, - envoy_eviction_broadcast_rps: 0.0, - envoy_eviction_work_delay_ms: 0, - envoy_eviction_work_cpu_us: 0, - worker_bump_subscribers: 0, - worker_bump_broadcast_rps: 0.0, - worker_bump_work_delay_ms: 0, - worker_bump_work_cpu_us: 0, - serverless_subscribers: 0, - serverless_publish_rps: 0.0, - serverless_payload_bytes: 256, - serverless_work_delay_ms: 0, - serverless_work_cpu_us: 0, - cache_purge_subscribers: 0, - cache_purge_broadcast_rps: 0.0, - cache_purge_payload_bytes: 128, - cache_purge_work_delay_ms: 0, - cache_purge_work_cpu_us: 0, - tracing_config_subscribers: 0, - tracing_config_broadcast_rps: 0.0, - tracing_config_payload_bytes: 128, - tracing_config_work_delay_ms: 0, - tracing_config_work_cpu_us: 0, - route_stopped_subscribers: 0, - route_churn_rps: 0.0, - route_ephemeral_hold_ms: 25, - route_stopped_hold_ms: 7_500, - route_max_in_flight: 4_096, - route_work_delay_ms: 0, - route_work_cpu_us: 0, - workflow_signal_churn_rps: 0.0, - workflow_signal_hold_ms: 3_000, - workflow_signal_publish_rps: 0.0, - workflow_signal_work_delay_ms: 0, - workflow_signal_work_cpu_us: 0, - workflow_complete_publish_rps: 0.0, - udb_hot_counter_rps: 0.0, - udb_hot_counter_max_in_flight: 1_024, - udb_hot_counter_namespace_id: Id::nil(), - udb_hot_counter_actor_name: "sim-hot-namespace".to_string(), - udb_read_scan_rps: 0.0, - udb_read_scan_max_in_flight: 512, - udb_read_scan_seed_keys: 50_000, - udb_read_scan_keys_per_tx: 50, - udb_read_scan_value_bytes: 128, - udb_read_scan_unpack_keys: true, - udb_conflict_rps: 0.0, - udb_conflict_max_in_flight: 512, - udb_conflict_keys: 32, - } - } - - fn staging_peak() -> Self { - Self { - gateway_subjects: 20, - gateway_subscribers: 20, - gateway_publish_rps: 5_016.0, - gateway_payload_bytes: 192, - gateway_work_cpu_us: 100, - gateway_spread_replicas: 10, - envoy_subjects: 8, - envoy_responders: 8, - envoy_queue_group: Some("rivet-ups-broadcast-sim-envoy".to_string()), - envoy_request_unknown_root: true, - envoy_request_rps: 2_094.0, - envoy_request_payload_bytes: 64, - envoy_work_delay_ms: 10, - envoy_work_cpu_us: 250, - envoy_eviction_subscribers: 72, - envoy_eviction_work_delay_ms: 1, - worker_bump_subscribers: 10, - worker_bump_broadcast_rps: 141.0, - worker_bump_work_delay_ms: 225, - worker_bump_work_cpu_us: 2_000, - serverless_subscribers: 10, - serverless_work_delay_ms: 2, - serverless_work_cpu_us: 250, - cache_purge_subscribers: 20, - cache_purge_broadcast_rps: 0.62, - cache_purge_work_delay_ms: 2, - cache_purge_work_cpu_us: 250, - tracing_config_subscribers: 20, - tracing_config_work_delay_ms: 1, - route_stopped_subscribers: 2_100, - route_churn_rps: 6.5, - route_work_delay_ms: 5, - route_work_cpu_us: 500, - workflow_signal_churn_rps: 281.0, - workflow_signal_hold_ms: 2_900, - workflow_signal_publish_rps: 0.76, - workflow_signal_work_delay_ms: 15, - workflow_signal_work_cpu_us: 500, - workflow_complete_publish_rps: 0.04, - ..Self::custom() - } - } -} - -#[derive(Clone)] -struct Rate { - value: Arc, -} - -impl Rate { - fn new(value: f64) -> Self { - Self { - value: Arc::new(AtomicU64::new(value.to_bits())), - } - } - - fn load(&self) -> f64 { - f64::from_bits(self.value.load(Ordering::Relaxed)) - } - - fn store(&self, value: f64) { - self.value.store(value.to_bits(), Ordering::Relaxed); - } -} - -struct Rates { - gateway_publish_rps: Rate, - envoy_request_rps: Rate, - envoy_eviction_broadcast_rps: Rate, - worker_bump_broadcast_rps: Rate, - serverless_publish_rps: Rate, - cache_purge_broadcast_rps: Rate, - tracing_config_broadcast_rps: Rate, - route_churn_rps: Rate, - workflow_signal_churn_rps: Rate, - workflow_signal_publish_rps: Rate, - workflow_complete_publish_rps: Rate, - udb_hot_counter_rps: Rate, - udb_read_scan_rps: Rate, - udb_conflict_rps: Rate, -} - -impl Rates { - fn new(config: &Config) -> Self { - Self { - gateway_publish_rps: Rate::new(config.gateway_publish_rps), - envoy_request_rps: Rate::new(config.envoy_request_rps), - envoy_eviction_broadcast_rps: Rate::new(config.envoy_eviction_broadcast_rps), - worker_bump_broadcast_rps: Rate::new(config.worker_bump_broadcast_rps), - serverless_publish_rps: Rate::new(config.serverless_publish_rps), - cache_purge_broadcast_rps: Rate::new(config.cache_purge_broadcast_rps), - tracing_config_broadcast_rps: Rate::new(config.tracing_config_broadcast_rps), - route_churn_rps: Rate::new(config.route_churn_rps), - workflow_signal_churn_rps: Rate::new(config.workflow_signal_churn_rps), - workflow_signal_publish_rps: Rate::new(config.workflow_signal_publish_rps), - workflow_complete_publish_rps: Rate::new(config.workflow_complete_publish_rps), - udb_hot_counter_rps: Rate::new(config.udb_hot_counter_rps), - udb_read_scan_rps: Rate::new(config.udb_read_scan_rps), - udb_conflict_rps: Rate::new(config.udb_conflict_rps), - } - } -} - -#[derive(Clone)] -struct Workload { - delay_ms: Arc, - cpu_us: Arc, -} - -impl Workload { - fn new(delay_ms: u64, cpu_us: u64) -> Self { - Self { - delay_ms: Arc::new(AtomicU64::new(delay_ms)), - cpu_us: Arc::new(AtomicU64::new(cpu_us)), - } - } - - fn store_delay_ms(&self, value: u64) { - self.delay_ms.store(value, Ordering::Relaxed); - } - - fn store_cpu_us(&self, value: u64) { - self.cpu_us.store(value, Ordering::Relaxed); - } - - async fn run(&self) { - let cpu_us = self.cpu_us.load(Ordering::Relaxed); - if cpu_us > 0 { - burn_cpu(Duration::from_micros(cpu_us)); - } - - let delay_ms = self.delay_ms.load(Ordering::Relaxed); - if delay_ms > 0 { - tokio::time::sleep(Duration::from_millis(delay_ms)).await; - } - } -} - -struct Workloads { - gateway: Workload, - envoy: Workload, - envoy_eviction: Workload, - worker_bump: Workload, - serverless: Workload, - cache_purge: Workload, - tracing_config: Workload, - route: Workload, - workflow_signal: Workload, -} - -impl Workloads { - fn new(config: &Config) -> Self { - Self { - gateway: Workload::new(config.gateway_work_delay_ms, config.gateway_work_cpu_us), - envoy: Workload::new(config.envoy_work_delay_ms, config.envoy_work_cpu_us), - envoy_eviction: Workload::new( - config.envoy_eviction_work_delay_ms, - config.envoy_eviction_work_cpu_us, - ), - worker_bump: Workload::new( - config.worker_bump_work_delay_ms, - config.worker_bump_work_cpu_us, - ), - serverless: Workload::new( - config.serverless_work_delay_ms, - config.serverless_work_cpu_us, - ), - cache_purge: Workload::new( - config.cache_purge_work_delay_ms, - config.cache_purge_work_cpu_us, - ), - tracing_config: Workload::new( - config.tracing_config_work_delay_ms, - config.tracing_config_work_cpu_us, - ), - route: Workload::new(config.route_work_delay_ms, config.route_work_cpu_us), - workflow_signal: Workload::new( - config.workflow_signal_work_delay_ms, - config.workflow_signal_work_cpu_us, - ), - } - } -} - -#[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] -struct TunePatch { - gateway_publish_rps: Option, - gateway_work_delay_ms: Option, - gateway_work_cpu_us: Option, - envoy_request_rps: Option, - envoy_work_delay_ms: Option, - envoy_work_cpu_us: Option, - envoy_eviction_broadcast_rps: Option, - envoy_eviction_work_delay_ms: Option, - envoy_eviction_work_cpu_us: Option, - worker_bump_broadcast_rps: Option, - worker_bump_work_delay_ms: Option, - worker_bump_work_cpu_us: Option, - serverless_publish_rps: Option, - serverless_work_delay_ms: Option, - serverless_work_cpu_us: Option, - cache_purge_broadcast_rps: Option, - cache_purge_work_delay_ms: Option, - cache_purge_work_cpu_us: Option, - tracing_config_broadcast_rps: Option, - tracing_config_work_delay_ms: Option, - tracing_config_work_cpu_us: Option, - route_churn_rps: Option, - route_work_delay_ms: Option, - route_work_cpu_us: Option, - workflow_signal_churn_rps: Option, - workflow_signal_publish_rps: Option, - workflow_signal_work_delay_ms: Option, - workflow_signal_work_cpu_us: Option, - workflow_complete_publish_rps: Option, - udb_hot_counter_rps: Option, - udb_read_scan_rps: Option, - udb_conflict_rps: Option, -} - -impl TunePatch { - fn apply(&self, rates: &Rates, workloads: &Workloads) -> Result<()> { - apply_rate( - "gateway_publish_rps", - self.gateway_publish_rps, - &rates.gateway_publish_rps, - )?; - apply_workload( - "gateway", - self.gateway_work_delay_ms, - self.gateway_work_cpu_us, - &workloads.gateway, - ); - apply_rate( - "envoy_request_rps", - self.envoy_request_rps, - &rates.envoy_request_rps, - )?; - apply_workload( - "envoy", - self.envoy_work_delay_ms, - self.envoy_work_cpu_us, - &workloads.envoy, - ); - apply_rate( - "envoy_eviction_broadcast_rps", - self.envoy_eviction_broadcast_rps, - &rates.envoy_eviction_broadcast_rps, - )?; - apply_workload( - "envoy_eviction", - self.envoy_eviction_work_delay_ms, - self.envoy_eviction_work_cpu_us, - &workloads.envoy_eviction, - ); - apply_rate( - "worker_bump_broadcast_rps", - self.worker_bump_broadcast_rps, - &rates.worker_bump_broadcast_rps, - )?; - apply_workload( - "worker_bump", - self.worker_bump_work_delay_ms, - self.worker_bump_work_cpu_us, - &workloads.worker_bump, - ); - apply_rate( - "serverless_publish_rps", - self.serverless_publish_rps, - &rates.serverless_publish_rps, - )?; - apply_workload( - "serverless", - self.serverless_work_delay_ms, - self.serverless_work_cpu_us, - &workloads.serverless, - ); - apply_rate( - "cache_purge_broadcast_rps", - self.cache_purge_broadcast_rps, - &rates.cache_purge_broadcast_rps, - )?; - apply_workload( - "cache_purge", - self.cache_purge_work_delay_ms, - self.cache_purge_work_cpu_us, - &workloads.cache_purge, - ); - apply_rate( - "tracing_config_broadcast_rps", - self.tracing_config_broadcast_rps, - &rates.tracing_config_broadcast_rps, - )?; - apply_workload( - "tracing_config", - self.tracing_config_work_delay_ms, - self.tracing_config_work_cpu_us, - &workloads.tracing_config, - ); - apply_rate( - "route_churn_rps", - self.route_churn_rps, - &rates.route_churn_rps, - )?; - apply_workload( - "route", - self.route_work_delay_ms, - self.route_work_cpu_us, - &workloads.route, - ); - apply_rate( - "workflow_signal_churn_rps", - self.workflow_signal_churn_rps, - &rates.workflow_signal_churn_rps, - )?; - apply_rate( - "workflow_signal_publish_rps", - self.workflow_signal_publish_rps, - &rates.workflow_signal_publish_rps, - )?; - apply_workload( - "workflow_signal", - self.workflow_signal_work_delay_ms, - self.workflow_signal_work_cpu_us, - &workloads.workflow_signal, - ); - apply_rate( - "workflow_complete_publish_rps", - self.workflow_complete_publish_rps, - &rates.workflow_complete_publish_rps, - )?; - apply_rate( - "udb_hot_counter_rps", - self.udb_hot_counter_rps, - &rates.udb_hot_counter_rps, - )?; - apply_rate( - "udb_read_scan_rps", - self.udb_read_scan_rps, - &rates.udb_read_scan_rps, - )?; - apply_rate( - "udb_conflict_rps", - self.udb_conflict_rps, - &rates.udb_conflict_rps, - )?; - - Ok(()) - } -} - -fn apply_rate(name: &'static str, value: Option, rate: &Rate) -> Result<()> { - if let Some(value) = value { - validate_rate(name, value)?; - rate.store(value); - } - - Ok(()) -} - -fn apply_workload( - name: &'static str, - delay_ms: Option, - cpu_us: Option, - workload: &Workload, -) { - if let Some(delay_ms) = delay_ms { - workload.store_delay_ms(delay_ms); - tracing::info!(name, delay_ms, "updated UPS simulation workload delay"); - } - if let Some(cpu_us) = cpu_us { - workload.store_cpu_us(cpu_us); - tracing::info!(name, cpu_us, "updated UPS simulation workload CPU"); - } -} - -pub async fn pubsub_for_sim( - config: &rivet_config::Config, - existing: &PubSub, - force_driver: bool, - disable_memory_optimization: bool, -) -> Result { - if !force_driver { - return Ok(existing.clone()); - } - - let mut root = (**config).clone(); - let mut pubsub = config.pubsub().clone(); - match &mut pubsub { - rivet_config::config::PubSub::Nats(nats) => { - nats.disable_memory_optimization = disable_memory_optimization; - } - rivet_config::config::PubSub::PostgresNotify(postgres) => { - postgres.disable_memory_optimization = disable_memory_optimization; - } - rivet_config::config::PubSub::Memory(memory) => { - memory.disable_memory_optimization = disable_memory_optimization; - } - } - root.pubsub = Some(pubsub); - - let sim_config = rivet_config::Config::from_root(root); - rivet_pools::db::ups::setup(&sim_config, "rivet-ups-broadcast-sim") - .await - .context("failed to create UPS simulation pubsub") -} - -pub fn spawn(ups: PubSub, udb: Option, config: Config) { - let rates = Arc::new(Rates::new(&config)); - let workloads = Arc::new(Workloads::new(&config)); - let workflow_signal_subjects = ActiveSubjects::default(); - - tracing::info!( - force_driver = config.force_driver, - disable_memory_optimization = config.disable_memory_optimization, - tune_path = ?config.tune_path, - gateway_publish_rps = config.gateway_publish_rps, - gateway_spread_replicas = config.gateway_spread_replicas, - envoy_request_rps = config.envoy_request_rps, - worker_bump_broadcast_rps = config.worker_bump_broadcast_rps, - worker_bump_work_delay_ms = config.worker_bump_work_delay_ms, - envoy_work_delay_ms = config.envoy_work_delay_ms, - workflow_signal_work_delay_ms = config.workflow_signal_work_delay_ms, - workflow_signal_churn_rps = config.workflow_signal_churn_rps, - udb_hot_counter_rps = config.udb_hot_counter_rps, - udb_read_scan_rps = config.udb_read_scan_rps, - udb_read_scan_seed_keys = config.udb_read_scan_seed_keys, - udb_read_scan_keys_per_tx = config.udb_read_scan_keys_per_tx, - udb_read_scan_unpack_keys = config.udb_read_scan_unpack_keys, - udb_conflict_rps = config.udb_conflict_rps, - udb_conflict_keys = config.udb_conflict_keys, - "starting UPS broadcast traffic simulator" - ); - - spawn_tuner( - ups.clone(), - rates.clone(), - workloads.clone(), - config.tune_path.clone(), - ); - - spawn_gateway_subscribers( - ups.clone(), - udb.clone(), - config.gateway_subjects, - config.gateway_subscribers, - config.gateway_spread_replicas, - Duration::from_millis(config.gateway_spread_member_ttl_ms), - workloads.gateway.clone(), - ); - let envoy_queue_group = config.envoy_queue_group.clone().map(Arc::new); - spawn_subject_subscribers( - ups.clone(), - "envoy", - "pegboard.envoy", - "pegboard.envoy.sim", - config.envoy_subjects, - config.envoy_responders, - envoy_queue_group, - Some(Arc::new(Vec::new())), - workloads.envoy.clone(), - ); - spawn_subject_subscribers( - ups.clone(), - "envoy eviction", - "pegboard.envoy.eviction", - "pegboard.envoy.eviction.sim", - config.envoy_subjects, - config.envoy_eviction_subscribers, - None, - None, - workloads.envoy_eviction.clone(), - ); - spawn_worker_bump_subscribers( - ups.clone(), - SimSubject::new("gasoline.worker.bump", "gasoline.worker.bump"), - config.worker_bump_subscribers, - workloads.worker_bump.clone(), - ); - spawn_same_subject_subscribers( - ups.clone(), - "serverless outbound", - SimSubject::new( - "pegboard.serverless.outbound", - "pegboard.serverless.outbound", - ), - config.serverless_subscribers, - None, - workloads.serverless.clone(), - ); - spawn_same_subject_subscribers( - ups.clone(), - "cache purge", - SimSubject::new("rivet.cache.purge", "rivet.cache.purge"), - config.cache_purge_subscribers, - None, - workloads.cache_purge.clone(), - ); - spawn_same_subject_subscribers( - ups.clone(), - "tracing config", - SimSubject::new("rivet.debug.tracing.config", "rivet.debug.tracing.config"), - config.tracing_config_subscribers, - None, - workloads.tracing_config.clone(), - ); - spawn_subject_subscribers( - ups.clone(), - "route stopped", - "gasoline.msg.pegboard_actor2_stopped", - "gasoline.msg.pegboard_actor2_stopped:actor", - config.route_stopped_subscribers, - config.route_stopped_subscribers, - None, - None, - Workload::new(0, 0), - ); - - let gateway_subjects = subjects( - "pegboard.gateway", - "pegboard.gateway.sim", - config.gateway_subjects, - ); - spawn_publish_rate( - ups.clone(), - "gateway publish", - gateway_subjects, - PublishOpts::one(), - rates.gateway_publish_rps.clone(), - Arc::new(payload(config.gateway_payload_bytes)), - ); - - if config.envoy_request_unknown_root { - spawn_request_rate( - ups.clone(), - raw_subjects("pegboard.envoy.sim", config.envoy_subjects), - rates.envoy_request_rps.clone(), - Arc::new(payload(config.envoy_request_payload_bytes)), - Duration::from_millis(config.envoy_request_timeout_ms), - config.envoy_request_max_in_flight, - ); - } else { - spawn_request_rate( - ups.clone(), - subjects( - "pegboard.envoy", - "pegboard.envoy.sim", - config.envoy_subjects, - ), - rates.envoy_request_rps.clone(), - Arc::new(payload(config.envoy_request_payload_bytes)), - Duration::from_millis(config.envoy_request_timeout_ms), - config.envoy_request_max_in_flight, - ); - } - - let envoy_eviction_subjects = subjects( - "pegboard.envoy.eviction", - "pegboard.envoy.eviction.sim", - config.envoy_subjects, - ); - spawn_publish_rate( - ups.clone(), - "envoy eviction broadcast", - envoy_eviction_subjects, - PublishOpts::broadcast(), - rates.envoy_eviction_broadcast_rps.clone(), - Arc::new(Vec::new()), - ); - spawn_publish_rate( - ups.clone(), - "worker bump broadcast", - vec![SimSubject::new( - "gasoline.worker.bump", - "gasoline.worker.bump", - )], - PublishOpts::broadcast(), - rates.worker_bump_broadcast_rps.clone(), - Arc::new(Vec::new()), - ); - spawn_publish_rate( - ups.clone(), - "serverless publish", - vec![SimSubject::new( - "pegboard.serverless.outbound", - "pegboard.serverless.outbound", - )], - PublishOpts::one(), - rates.serverless_publish_rps.clone(), - Arc::new(payload(config.serverless_payload_bytes)), - ); - spawn_publish_rate( - ups.clone(), - "cache purge broadcast", - vec![SimSubject::new("rivet.cache.purge", "rivet.cache.purge")], - PublishOpts::broadcast(), - rates.cache_purge_broadcast_rps.clone(), - Arc::new(payload(config.cache_purge_payload_bytes)), - ); - spawn_publish_rate( - ups.clone(), - "tracing config broadcast", - vec![SimSubject::new( - "rivet.debug.tracing.config", - "rivet.debug.tracing.config", - )], - PublishOpts::broadcast(), - rates.tracing_config_broadcast_rps.clone(), - Arc::new(payload(config.tracing_config_payload_bytes)), - ); - spawn_publish_active_rate( - ups.clone(), - "workflow signal broadcast", - workflow_signal_subjects.clone(), - PublishOpts::broadcast(), - rates.workflow_signal_publish_rps.clone(), - Arc::new(Vec::new()), - ); - spawn_publish_rate( - ups.clone(), - "workflow complete broadcast", - vec![unique_subject( - "gasoline.workflow.complete", - "gasoline.workflow.complete", - )], - PublishOpts::broadcast(), - rates.workflow_complete_publish_rps.clone(), - Arc::new(Vec::new()), - ); - - spawn_route_churn( - ups.clone(), - rates.route_churn_rps.clone(), - Duration::from_millis(config.route_ephemeral_hold_ms), - Duration::from_millis(config.route_stopped_hold_ms), - config.route_max_in_flight, - workloads.route.clone(), - ); - spawn_subscription_churn( - ups, - "workflow signal churn", - "gasoline.signal.for-workflow", - "gasoline.signal.for-workflow", - rates.workflow_signal_churn_rps.clone(), - Duration::from_millis(config.workflow_signal_hold_ms), - Some(workflow_signal_subjects), - workloads.workflow_signal.clone(), - ); - - spawn_udb_hot_counter( - udb.clone(), - rates.udb_hot_counter_rps.clone(), - config.udb_hot_counter_max_in_flight, - config.udb_hot_counter_namespace_id, - config.udb_hot_counter_actor_name, - ); - spawn_udb_read_scan( - udb.clone(), - rates.udb_read_scan_rps.clone(), - config.udb_read_scan_max_in_flight, - config.udb_read_scan_seed_keys, - config.udb_read_scan_keys_per_tx, - config.udb_read_scan_value_bytes, - config.udb_read_scan_unpack_keys, - ); - spawn_udb_conflict( - udb, - rates.udb_conflict_rps.clone(), - config.udb_conflict_max_in_flight, - config.udb_conflict_keys, - ); -} - -fn spawn_tuner( - ups: PubSub, - rates: Arc, - workloads: Arc, - tune_path: Option, -) { - let tune_subject = tune_subject(); - { - let ups = ups.clone(); - let rates = rates.clone(); - let workloads = workloads.clone(); - let tune_subject = tune_subject.clone(); - tokio::spawn(async move { - loop { - let mut sub = match ups.subscribe(tune_subject.clone()).await { - Ok(sub) => sub, - Err(err) => { - tracing::warn!(?err, "failed to subscribe to UPS simulation tune subject"); - tokio::time::sleep(Duration::from_secs(2)).await; - continue; - } - }; - - loop { - match sub.next().await { - Ok(NextOutput::Message(message)) => { - if let Err(err) = - apply_tune_patch_bytes(&message.payload, &rates, &workloads) - { - tracing::warn!(?err, "failed to apply UPS simulation tune message"); - } - } - Ok(NextOutput::Unsubscribed | NextOutput::NoResponders) => break, - Err(err) => { - tracing::warn!(?err, "UPS simulation tune subscriber failed"); - break; - } - } - } - } - }); - } - - if let Some(path) = tune_path { - tokio::spawn(async move { - let mut last_payload = None::>; - - loop { - match tokio::fs::read(&path).await { - Ok(payload) if Some(&payload) != last_payload.as_ref() => { - match apply_tune_patch_bytes(&payload, &rates, &workloads) { - Ok(()) => { - last_payload = Some(payload.clone()); - if let Err(err) = ups - .publish( - tune_subject.clone(), - &payload, - PublishOpts::broadcast(), - ) - .await - { - tracing::warn!( - ?err, - %path, - "failed to broadcast UPS simulation tune patch" - ); - } - } - Err(err) => { - tracing::warn!( - ?err, - %path, - "failed to apply UPS simulation tune file" - ); - } - } - } - Ok(_) => {} - Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} - Err(err) => { - tracing::debug!(?err, %path, "failed to read UPS simulation tune file"); - } - } - - tokio::time::sleep(TUNE_POLL_INTERVAL).await; - } - }); - } -} - -fn apply_tune_patch_bytes(payload: &[u8], rates: &Rates, workloads: &Workloads) -> Result<()> { - let patch: TunePatch = - serde_json::from_slice(payload).context("failed to parse UPS simulation tune patch")?; - patch.apply(rates, workloads)?; - tracing::info!(?patch, "applied UPS simulation tune patch"); - Ok(()) -} - -fn tune_subject() -> SimSubject { - SimSubject::new(TUNE_SUBJECT, TUNE_SUBJECT_ROOT) -} - -fn spawn_subject_subscribers( - ups: PubSub, - label: &'static str, - root: &'static str, - prefix: &'static str, - subject_count: usize, - subscriber_count: usize, - queue_group: Option>, - reply_payload: Option>>, - workload: Workload, -) { - if subject_count == 0 || subscriber_count == 0 { - return; - } - - spawn_subject_subscribers_with_offset( - ups, - label, - root, - prefix, - subject_count, - subscriber_count, - 0, - queue_group, - reply_payload, - workload, - ); -} - -fn spawn_subject_subscribers_with_offset( - ups: PubSub, - label: &'static str, - root: &'static str, - prefix: &'static str, - subject_count: usize, - subscriber_count: usize, - subject_offset: usize, - queue_group: Option>, - reply_payload: Option>>, - workload: Workload, -) { - for idx in 0..subscriber_count { - let subject_idx = subject_offset.wrapping_add(idx) % subject_count; - let subject = SimSubject::new(format!("{prefix}.{subject_idx}"), root); - spawn_subscriber( - ups.clone(), - label, - subject, - queue_group.clone(), - reply_payload.clone(), - workload.clone(), - ); - } -} - -fn spawn_gateway_subscribers( - ups: PubSub, - udb: Option, - subject_count: usize, - subscriber_count: usize, - spread_replicas: usize, - member_ttl: Duration, - workload: Workload, -) { - if subject_count == 0 || subscriber_count == 0 { - return; - } - - let Some(udb) = udb.filter(|_| spread_replicas > 1 && subject_count > subscriber_count) else { - spawn_subject_subscribers( - ups, - "gateway", - "pegboard.gateway", - "pegboard.gateway.sim", - subject_count, - subscriber_count, - None, - None, - workload, - ); - return; - }; - - tokio::spawn(async move { - let member_id = gateway_member_id(); - - loop { - match gateway_subject_offset( - &udb, - &member_id, - spread_replicas, - subscriber_count, - member_ttl, - ) - .await - { - Ok(Some(subject_offset)) => { - spawn_gateway_membership_heartbeat(udb.clone(), member_id.clone(), member_ttl); - tracing::info!( - member_id, - subject_offset, - subject_count, - subscriber_count, - spread_replicas, - "starting spread gateway UPS simulation subscribers" - ); - spawn_subject_subscribers_with_offset( - ups, - "gateway", - "pegboard.gateway", - "pegboard.gateway.sim", - subject_count, - subscriber_count, - subject_offset, - None, - None, - workload, - ); - return; - } - Ok(None) => {} - Err(err) => { - tracing::warn!( - ?err, - member_id, - "failed to assign gateway UPS simulation subjects" - ); - } - } - - tokio::time::sleep(Duration::from_secs(2)).await; - } - }); -} - -fn spawn_gateway_membership_heartbeat(udb: UdbPool, member_id: String, member_ttl: Duration) { - let interval = (member_ttl / 3).max(Duration::from_secs(1)); - - tokio::spawn(async move { - loop { - if let Err(err) = gateway_refresh_member(&udb, &member_id).await { - tracing::warn!( - ?err, - member_id, - "failed to refresh gateway UPS simulation membership" - ); - } - - tokio::time::sleep(interval).await; - } - }); -} - -async fn gateway_refresh_member(udb: &UdbPool, member_id: &str) -> Result<()> { - let member_id = member_id.to_string(); - udb.txn(GATEWAY_MEMBERSHIP_TX, |tx| { - let member_id = member_id.clone(); - async move { - let now = now_ms(); - let group = gateway_member_group(&member_id); - let prefix = gateway_member_prefix(&group); - let member_key = gateway_member_key(&prefix, &member_id); - tx.informal().set(&member_key, &now.to_be_bytes()); - Ok(()) - } - }) - .await -} - -async fn gateway_subject_offset( - udb: &UdbPool, - member_id: &str, - expected_replicas: usize, - subscriber_count: usize, - member_ttl: Duration, -) -> Result> { - let member_id = member_id.to_string(); - let member_ttl_ms = duration_millis_u64(member_ttl); - let members = udb - .txn(GATEWAY_MEMBERSHIP_TX, |tx| { - let member_id = member_id.clone(); - async move { - let now = now_ms(); - let group = gateway_member_group(&member_id); - let prefix = gateway_member_prefix(&group); - let member_key = gateway_member_key(&prefix, &member_id); - tx.informal().set(&member_key, &now.to_be_bytes()); - - let mut end = prefix.clone(); - end.push(0xff); - let mut range: RangeOption<'static> = (prefix.clone()..end).into(); - range.limit = Some(expected_replicas.saturating_mul(4).max(32)); - - let min_fresh = now.saturating_sub(member_ttl_ms); - let informal = tx.informal(); - let mut stream = informal.get_ranges_keyvalues(range, Snapshot); - let mut members = Vec::new(); - while let Some(entry) = stream.next().await { - let entry = entry?; - let value = entry.value(); - if value.len() != 8 { - continue; - } - - let mut ts = [0; 8]; - ts.copy_from_slice(value); - if u64::from_be_bytes(ts) < min_fresh { - continue; - } - - if let Some(member) = gateway_member_from_key(&prefix, entry.key()) { - members.push(member); - } - } - - Ok(members) - } - }) - .await?; - - let mut members = members; - members.sort(); - members.dedup(); - - if members.len() < expected_replicas { - tracing::debug!( - member_id, - active_members = members.len(), - expected_replicas, - "waiting for stable gateway UPS simulation membership" - ); - return Ok(None); - } - - let Some(ordinal) = members.iter().position(|member| member == &member_id) else { - return Ok(None); - }; - - Ok(Some( - (ordinal % expected_replicas).saturating_mul(subscriber_count), - )) -} - -fn gateway_member_id() -> String { - env::var("HOSTNAME").unwrap_or_else(|_| format!("pid-{}", std::process::id())) -} - -fn gateway_member_group(member_id: &str) -> String { - member_id - .rsplit_once('-') - .map(|(group, _)| group) - .unwrap_or(member_id) - .to_string() -} - -fn gateway_member_prefix(group: &str) -> Vec { - let mut key = GATEWAY_MEMBERSHIP_PREFIX.to_vec(); - key.push(b'/'); - key.extend_from_slice(group.as_bytes()); - key.push(b'/'); - key -} - -fn gateway_member_key(prefix: &[u8], member_id: &str) -> Vec { - let mut key = prefix.to_vec(); - key.extend_from_slice(member_id.as_bytes()); - key -} - -fn gateway_member_from_key(prefix: &[u8], key: &[u8]) -> Option { - key.strip_prefix(prefix) - .and_then(|member| std::str::from_utf8(member).ok()) - .map(ToOwned::to_owned) -} - -fn spawn_same_subject_subscribers( - ups: PubSub, - label: &'static str, - subject: SimSubject, - subscriber_count: usize, - reply_payload: Option>>, - workload: Workload, -) { - for _ in 0..subscriber_count { - spawn_subscriber( - ups.clone(), - label, - subject.clone(), - None, - reply_payload.clone(), - workload.clone(), - ); - } -} - -fn spawn_worker_bump_subscribers( - ups: PubSub, - subject: SimSubject, - subscriber_count: usize, - workload: Workload, -) { - for _ in 0..subscriber_count { - let ups = ups.clone(); - let subject = subject.clone(); - let workload = workload.clone(); - tokio::spawn(async move { - loop { - let mut sub = match ups.subscribe(subject.clone()).await { - Ok(sub) => sub, - Err(err) => { - tracing::warn!( - ?err, - %subject, - "failed to subscribe for UPS worker bump simulation" - ); - tokio::time::sleep(Duration::from_secs(2)).await; - continue; - } - }; - - loop { - match sub.next().await { - Ok(NextOutput::Message(_)) => { - drain_ready_messages(&mut sub, "worker bump").await; - workload.run().await; - } - Ok(NextOutput::Unsubscribed | NextOutput::NoResponders) => break, - Err(err) => { - tracing::warn!( - ?err, - %subject, - "UPS worker bump simulation subscriber failed" - ); - break; - } - } - } - } - }); - } -} - -fn spawn_subscriber( - ups: PubSub, - label: &'static str, - subject: SimSubject, - queue_group: Option>, - reply_payload: Option>>, - workload: Workload, -) { - tokio::spawn(async move { - loop { - let sub_res = if let Some(queue_group) = queue_group.as_ref() { - ups.queue_subscribe(subject.clone(), queue_group.as_str()) - .await - } else { - ups.subscribe(subject.clone()).await - }; - let mut sub = match sub_res { - Ok(sub) => sub, - Err(err) => { - tracing::warn!(?err, %subject, label, "failed to subscribe for UPS simulation"); - tokio::time::sleep(Duration::from_secs(2)).await; - continue; - } - }; - - loop { - match sub.next().await { - Ok(NextOutput::Message(message)) => { - if let Some(reply_payload) = &reply_payload { - if let Err(err) = message.reply(reply_payload).await { - tracing::debug!( - ?err, - %subject, - label, - "failed to reply to UPS simulation message" - ); - } - } - workload.run().await; - } - Ok(NextOutput::Unsubscribed | NextOutput::NoResponders) => break, - Err(err) => { - tracing::warn!( - ?err, - %subject, - label, - "UPS simulation subscriber failed" - ); - break; - } - } - } - } - }); -} - -async fn drain_ready_messages(sub: &mut Subscriber, label: &'static str) { - for _ in 0..1023 { - match sub.next().now_or_never() { - Some(Ok(NextOutput::Message(_))) => {} - Some(Ok(NextOutput::Unsubscribed | NextOutput::NoResponders)) | None => break, - Some(Err(err)) => { - tracing::debug!(?err, label, "failed to drain UPS simulation messages"); - break; - } - } - } -} - -fn burn_cpu(duration: Duration) { - let start = Instant::now(); - let mut value = 0u64; - while start.elapsed() < duration { - value = value.wrapping_add(1); - hint::black_box(value); - } -} - -fn spawn_publish_rate( - ups: PubSub, - label: &'static str, - subjects: Vec, - opts: PublishOpts, - rate: Rate, - payload: Arc>, -) where - S: Subject + Clone + Send + Sync + 'static, -{ - if subjects.is_empty() { - return; - } - - tokio::spawn(async move { - let mut pacer = Pacer::new(); - let mut idx = 0usize; - let semaphore = Arc::new(tokio::sync::Semaphore::new(PUBLISH_MAX_IN_FLIGHT)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let subject = subjects[idx % subjects.len()].clone(); - idx = idx.wrapping_add(1); - let ups = ups.clone(); - let payload = payload.clone(); - tokio::spawn(async move { - let _permit = permit; - if let Err(err) = ups.publish(subject, &payload, opts).await { - tracing::warn!(?err, label, "UPS simulation publish failed"); - } - }); - } - } - }); -} - -#[derive(Clone, Default)] -struct ActiveSubjects { - subjects: Arc>>, -} - -impl ActiveSubjects { - async fn insert(&self, subject: SimSubject) { - self.subjects.write().await.push(subject); - } - - async fn remove(&self, subject: &SimSubject) { - self.subjects - .write() - .await - .retain(|existing| existing.subject != subject.subject); - } - - async fn get(&self, idx: usize) -> Option { - let subjects = self.subjects.read().await; - if subjects.is_empty() { - None - } else { - Some(subjects[idx % subjects.len()].clone()) - } - } -} - -fn spawn_publish_active_rate( - ups: PubSub, - label: &'static str, - subjects: ActiveSubjects, - opts: PublishOpts, - rate: Rate, - payload: Arc>, -) { - tokio::spawn(async move { - let mut pacer = Pacer::new(); - let mut idx = 0usize; - let semaphore = Arc::new(tokio::sync::Semaphore::new(PUBLISH_MAX_IN_FLIGHT)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Some(subject) = subjects.get(idx).await else { - continue; - }; - idx = idx.wrapping_add(1); - - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let ups = ups.clone(); - let payload = payload.clone(); - tokio::spawn(async move { - let _permit = permit; - if let Err(err) = ups.publish(subject, &payload, opts).await { - tracing::warn!(?err, label, "UPS simulation publish failed"); - } - }); - } - } - }); -} - -fn spawn_request_rate( - ups: PubSub, - subjects: Vec, - rate: Rate, - payload: Arc>, - timeout: Duration, - max_in_flight: usize, -) where - S: Subject + Clone + Send + Sync + 'static, -{ - if subjects.is_empty() || max_in_flight == 0 { - return; - } - - tokio::spawn(async move { - let mut pacer = Pacer::new(); - let mut idx = 0usize; - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_in_flight)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let subject = subjects[idx % subjects.len()].clone(); - idx = idx.wrapping_add(1); - let ups = ups.clone(); - let payload = payload.clone(); - tokio::spawn(async move { - let _permit = permit; - if let Err(err) = ups.request_with_timeout(subject, &payload, timeout).await { - tracing::debug!(?err, "UPS simulation request failed"); - } - }); - } - } - }); -} - -fn spawn_udb_hot_counter( - udb: Option, - rate: Rate, - max_in_flight: usize, - namespace_id: Id, - actor_name: String, -) { - if max_in_flight == 0 { - return; - } - - let Some(udb) = udb else { - if rate.load() > 0.0 { - tracing::warn!("UPS simulation UDB hot counter enabled without a UDB pool"); - } - return; - }; - - tokio::spawn(async move { - let mut pacer = Pacer::new(); - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_in_flight)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let udb = udb.clone(); - let actor_name = actor_name.clone(); - tokio::spawn(async move { - let _permit = permit; - let is_open = HOT_COUNTER_SEQ.fetch_add(1, Ordering::Relaxed) % 2 == 0; - let res = udb - .txn(UDB_HOT_COUNTER_TX, |tx| { - let actor_name = actor_name.clone(); - async move { - let tx = tx.with_subspace(namespace::keys::subspace()); - if is_open { - namespace::keys::metric::inc( - &tx, - namespace_id, - namespace::keys::metric::Metric::Requests( - actor_name.clone(), - "ws".to_string(), - ), - 1, - ); - namespace::keys::metric::inc( - &tx, - namespace_id, - namespace::keys::metric::Metric::ActiveRequests( - actor_name, - "ws".to_string(), - ), - 1, - ); - } else { - namespace::keys::metric::inc( - &tx, - namespace_id, - namespace::keys::metric::Metric::ActiveRequests( - actor_name, - "ws".to_string(), - ), - -1, - ); - } - - Ok(()) - } - }) - .await; - - if let Err(err) = res { - tracing::debug!(?err, "UPS simulation UDB hot counter transaction failed"); - } - }); - } - } - }); -} - -#[derive(Debug, Clone, Copy)] -struct ReadScanKey { - shard: u64, - index: u64, -} - -impl TuplePack for ReadScanKey { - fn pack( - &self, - w: &mut W, - tuple_depth: TupleDepth, - ) -> std::io::Result { - let t = (READ_SCAN_KEY_ROOT, self.shard, self.index); - t.pack(w, tuple_depth) - } -} - -impl<'de> TupleUnpack<'de> for ReadScanKey { - fn unpack(input: &[u8], tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { - let (input, (root, shard, index)) = <(usize, u64, u64)>::unpack(input, tuple_depth)?; - if root != READ_SCAN_KEY_ROOT { - return Err(PackError::Message("expected READ_SCAN key root".into())); - } - - Ok((input, Self { shard, index })) - } -} - -#[derive(Debug, Clone, Copy)] -struct ConflictKey { - index: u64, -} - -impl TuplePack for ConflictKey { - fn pack( - &self, - w: &mut W, - tuple_depth: TupleDepth, - ) -> std::io::Result { - let t = (CONFLICT_KEY_ROOT, self.index); - t.pack(w, tuple_depth) - } -} - -impl<'de> TupleUnpack<'de> for ConflictKey { - fn unpack(input: &[u8], tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { - let (input, (root, index)) = <(usize, u64)>::unpack(input, tuple_depth)?; - if root != CONFLICT_KEY_ROOT { - return Err(PackError::Message("expected CONFLICT key root".into())); - } - - Ok((input, Self { index })) - } -} - -fn sim_read_scan_subspace() -> Subspace { - Subspace::new(&("rivet", "ups-broadcast", "sim", "read-scan")) -} - -fn sim_conflict_subspace() -> Subspace { - Subspace::new(&("rivet", "ups-broadcast", "sim", "conflict")) -} - -fn read_scan_shard() -> u64 { - let member_id = gateway_member_id(); - let mut hash = 0xcbf2_9ce4_8422_2325u64; - for byte in member_id.as_bytes() { - hash ^= u64::from(*byte); - hash = hash.wrapping_mul(0x0000_0100_0000_01b3); - } - hash -} - -fn spawn_udb_read_scan( - udb: Option, - rate: Rate, - max_in_flight: usize, - seed_keys: u64, - keys_per_tx: usize, - value_bytes: usize, - unpack_keys: bool, -) { - if max_in_flight == 0 || keys_per_tx == 0 || seed_keys == 0 { - if rate.load() > 0.0 { - tracing::warn!( - max_in_flight, - seed_keys, - keys_per_tx, - "UPS simulation UDB read scan is enabled without enough configuration" - ); - } - return; - } - - let Some(udb) = udb else { - if rate.load() > 0.0 { - tracing::warn!("UPS simulation UDB read scan enabled without a UDB pool"); - } - return; - }; - - tokio::spawn(async move { - let shard = read_scan_shard(); - if let Err(err) = seed_udb_read_scan(&udb, shard, seed_keys, value_bytes).await { - tracing::warn!( - ?err, - shard, - "failed to seed UPS simulation UDB read scan keys" - ); - } - - let mut pacer = Pacer::new(); - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_in_flight)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let udb = udb.clone(); - tokio::spawn(async move { - let _permit = permit; - if let Err(err) = - run_udb_read_scan(&udb, shard, seed_keys, keys_per_tx, unpack_keys).await - { - tracing::debug!(?err, "UPS simulation UDB read scan transaction failed"); - } - }); - } - } - }); -} - -async fn seed_udb_read_scan( - udb: &UdbPool, - shard: u64, - seed_keys: u64, - value_bytes: usize, -) -> Result<()> { - let value = Arc::new(payload(value_bytes)); - let mut start = 0; - - tracing::info!( - shard, - seed_keys, - value_bytes, - "seeding UPS simulation UDB read scan keys" - ); - - while start < seed_keys { - let end = start - .saturating_add(UDB_READ_SCAN_SEED_BATCH_SIZE) - .min(seed_keys); - let value = value.clone(); - udb.txn(UDB_READ_SCAN_SEED_TX, |tx| { - let value = value.clone(); - async move { - let tx = tx.with_subspace(sim_read_scan_subspace()); - for index in start..end { - let key = tx.pack(&ReadScanKey { shard, index }); - tx.set(&key, value.as_slice()); - } - - Ok(()) - } - }) - .await?; - start = end; - } - - tracing::info!(shard, seed_keys, "seeded UPS simulation UDB read scan keys"); - Ok(()) -} - -async fn run_udb_read_scan( - udb: &UdbPool, - shard: u64, - seed_keys: u64, - keys_per_tx: usize, - unpack_keys: bool, -) -> Result<()> { - let keys_per_tx_u64 = u64::try_from(keys_per_tx) - .unwrap_or(u64::MAX) - .min(seed_keys); - let start = READ_SCAN_SEQ.fetch_add(keys_per_tx_u64, Ordering::Relaxed) % seed_keys; - let end = start.saturating_add(keys_per_tx_u64).min(seed_keys); - let limit = usize::try_from(end.saturating_sub(start)).unwrap_or(keys_per_tx); - - udb.txn(UDB_READ_SCAN_TX, |tx| async move { - let tx = tx.with_subspace(sim_read_scan_subspace()); - let begin = tx.pack(&ReadScanKey { - shard, - index: start, - }); - let end = tx.pack(&ReadScanKey { shard, index: end }); - let mut range: RangeOption<'static> = (begin..end).into(); - range.limit = Some(limit); - - let informal = tx.informal(); - let mut stream = informal.get_ranges_keyvalues(range, Snapshot); - while let Some(entry) = stream.next().await { - let entry = entry?; - if unpack_keys { - let _ = tx.unpack::(entry.key())?; - } - hint::black_box(entry.value().len()); - } - - Ok(()) - }) - .await -} - -fn spawn_udb_conflict(udb: Option, rate: Rate, max_in_flight: usize, key_count: u64) { - if max_in_flight == 0 || key_count == 0 { - if rate.load() > 0.0 { - tracing::warn!( - max_in_flight, - key_count, - "UPS simulation UDB conflict load is enabled without enough configuration" - ); - } - return; - } - - let Some(udb) = udb else { - if rate.load() > 0.0 { - tracing::warn!("UPS simulation UDB conflict load enabled without a UDB pool"); - } - return; - }; - - tokio::spawn(async move { - if let Err(err) = seed_udb_conflict(&udb, key_count).await { - tracing::warn!( - ?err, - key_count, - "failed to seed UPS simulation UDB conflict keys" - ); - } - - let mut pacer = Pacer::new(); - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_in_flight)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let udb = udb.clone(); - tokio::spawn(async move { - let _permit = permit; - let index = CONFLICT_SEQ.fetch_add(1, Ordering::Relaxed) % key_count; - if let Err(err) = run_udb_conflict(&udb, index).await { - tracing::debug!(?err, "UPS simulation UDB conflict transaction failed"); - } - }); - } - } - }); -} - -async fn seed_udb_conflict(udb: &UdbPool, key_count: u64) -> Result<()> { - let mut start = 0; - - tracing::info!(key_count, "seeding UPS simulation UDB conflict keys"); - - while start < key_count { - let end = start - .saturating_add(UDB_CONFLICT_SEED_BATCH_SIZE) - .min(key_count); - udb.txn(UDB_CONFLICT_SEED_TX, |tx| async move { - let tx = tx.with_subspace(sim_conflict_subspace()); - for index in start..end { - let key = tx.pack(&ConflictKey { index }); - tx.set(&key, &0u64.to_be_bytes()); - } - - Ok(()) - }) - .await?; - start = end; - } - - tracing::info!(key_count, "seeded UPS simulation UDB conflict keys"); - Ok(()) -} - -async fn run_udb_conflict(udb: &UdbPool, index: u64) -> Result<()> { - udb.txn(UDB_CONFLICT_TX, |tx| async move { - let tx = tx.with_subspace(sim_conflict_subspace()); - let key = tx.pack(&ConflictKey { index }); - let value = tx.get(&key, Serializable).await?; - let next = value - .as_ref() - .and_then(|value| value.as_slice().try_into().ok().map(u64::from_be_bytes)) - .unwrap_or(0) - .wrapping_add(1); - tx.set(&key, &next.to_be_bytes()); - Ok(()) - }) - .await -} - -fn spawn_route_churn( - ups: PubSub, - rate: Rate, - ephemeral_hold: Duration, - stopped_hold: Duration, - max_in_flight: usize, - workload: Workload, -) { - if max_in_flight == 0 { - return; - } - - tokio::spawn(async move { - let mut pacer = Pacer::new(); - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_in_flight)); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let Ok(permit) = semaphore.clone().try_acquire_owned() else { - continue; - }; - let ups = ups.clone(); - let workload = workload.clone(); - tokio::spawn(async move { - let _permit = permit; - let route_id = SUBJECT_SEQ.fetch_add(1, Ordering::Relaxed); - let mut ephemeral = Vec::new(); - for (root, prefix) in ROUTE_SUBJECTS { - let subject = - SimSubject::new(format!("{prefix}:actor_id:{route_id}"), *root); - match ups.subscribe(subject).await { - Ok(sub) => ephemeral.push(sub), - Err(err) => tracing::debug!( - ?err, - "failed to create UPS simulation route subscription" - ), - } - } - - let stopped = ups - .subscribe(SimSubject::new( - format!("gasoline.msg.pegboard_actor2_stopped:actor_id:{route_id}"), - "gasoline.msg.pegboard_actor2_stopped", - )) - .await - .ok(); - - workload.run().await; - tokio::time::sleep(ephemeral_hold).await; - drop(ephemeral); - tokio::time::sleep(stopped_hold).await; - drop(stopped); - }); - } - } - }); -} - -fn spawn_subscription_churn( - ups: PubSub, - label: &'static str, - root: &'static str, - prefix: &'static str, - rate: Rate, - hold: Duration, - active_subjects: Option, - workload: Workload, -) { - tokio::spawn(async move { - let mut pacer = Pacer::new(); - - loop { - let count = pacer.next_count(rate.load()).await; - for _ in 0..count { - let ups = ups.clone(); - let active_subjects = active_subjects.clone(); - let workload = workload.clone(); - tokio::spawn(async move { - let subject = unique_subject(root, prefix); - match ups.subscribe(subject.clone()).await { - Ok(mut sub) => { - if let Some(active_subjects) = active_subjects.as_ref() { - active_subjects.insert(subject.clone()).await; - } - - let deadline = tokio::time::Instant::now() + hold; - loop { - tokio::select! { - res = sub.next() => { - match res { - Ok(NextOutput::Message(_)) => workload.run().await, - Ok(NextOutput::Unsubscribed | NextOutput::NoResponders) => break, - Err(err) => { - tracing::debug!( - ?err, - %subject, - label, - "UPS simulation churn subscriber failed" - ); - break; - } - } - } - _ = tokio::time::sleep_until(deadline) => break, - } - } - - if let Some(active_subjects) = active_subjects.as_ref() { - active_subjects.remove(&subject).await; - } - drop(sub); - } - Err(err) => { - tracing::debug!( - ?err, - %subject, - label, - "failed to create UPS simulation churn subscription" - ); - } - } - }); - } - } - }); -} - -const ROUTE_SUBJECTS: &[(&str, &str)] = &[ - ( - "gasoline.msg.pegboard_actor_failed", - "gasoline.msg.pegboard_actor_failed", - ), - ( - "gasoline.msg.pegboard_actor_ready", - "gasoline.msg.pegboard_actor_ready", - ), - ( - "gasoline.msg.pegboard_actor_stopped", - "gasoline.msg.pegboard_actor_stopped", - ), - ( - "gasoline.msg.pegboard_actor_destroy_started", - "gasoline.msg.pegboard_actor_destroy_started", - ), - ( - "gasoline.msg.pegboard_actor_migrated_to_v2", - "gasoline.msg.pegboard_actor_migrated_to_v2", - ), - ( - "gasoline.msg.pegboard_actor2_ready", - "gasoline.msg.pegboard_actor2_ready", - ), - ( - "gasoline.msg.pegboard_actor2_stopped", - "gasoline.msg.pegboard_actor2_stopped", - ), - ( - "gasoline.msg.pegboard_actor2_failed", - "gasoline.msg.pegboard_actor2_failed", - ), - ( - "gasoline.msg.pegboard_actor2_destroy_started", - "gasoline.msg.pegboard_actor2_destroy_started", - ), -]; - -struct Pacer { - interval: tokio::time::Interval, - carry: f64, - last: Instant, -} - -impl Pacer { - fn new() -> Self { - let mut interval = tokio::time::interval(TICK); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - Self { - interval, - carry: 0.0, - last: Instant::now(), - } - } - - async fn next_count(&mut self, rate_per_sec: f64) -> usize { - self.interval.tick().await; - let now = Instant::now(); - let elapsed = now.duration_since(self.last); - self.last = now; - self.carry += rate_per_sec * elapsed.as_secs_f64(); - let count = self.carry.floor() as usize; - self.carry -= count as f64; - count - } -} - -#[derive(Clone)] -struct SimSubject { - subject: String, - root: String, -} - -impl SimSubject { - fn new(subject: impl Into, root: impl Into) -> Self { - Self { - subject: subject.into(), - root: root.into(), - } - } -} - -impl fmt::Display for SimSubject { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.subject.fmt(f) - } -} - -impl Subject for SimSubject { - fn subject_root<'a>(&'a self) -> Option> { - Some(Cow::Borrowed(self.root.as_str())) - } - - fn as_str(&self) -> Option<&str> { - Some(self.subject.as_str()) - } -} - -#[derive(Clone)] -struct RawSubject { - subject: String, -} - -impl RawSubject { - fn new(subject: impl Into) -> Self { - Self { - subject: subject.into(), - } - } -} - -impl fmt::Display for RawSubject { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.subject.fmt(f) - } -} - -impl Subject for RawSubject { - fn as_str(&self) -> Option<&str> { - Some(self.subject.as_str()) - } -} - -fn subjects(root: &'static str, prefix: &'static str, count: usize) -> Vec { - (0..count) - .map(|idx| SimSubject::new(format!("{prefix}.{idx}"), root)) - .collect() -} - -fn raw_subjects(prefix: &'static str, count: usize) -> Vec { - (0..count) - .map(|idx| RawSubject::new(format!("{prefix}.{idx}"))) - .collect() -} - -fn unique_subject(root: &'static str, prefix: &'static str) -> SimSubject { - let idx = SUBJECT_SEQ.fetch_add(1, Ordering::Relaxed); - SimSubject::new(format!("{prefix}.{idx}"), root) -} - -fn payload(size: usize) -> Vec { - vec![b'x'; size] -} - -fn env_key(key: &str) -> String { - format!("{ENV_PREFIX}_{key}") -} - -fn env_string(key: &str) -> Option { - env::var(env_key(key)).ok() -} - -fn env_bool(key: &str, default: bool) -> Result { - let Some(value) = env_string(key) else { - return Ok(default); - }; - match value.to_ascii_lowercase().as_str() { - "1" | "true" | "yes" | "on" => Ok(true), - "0" | "false" | "no" | "off" => Ok(false), - _ => bail!("{ENV_PREFIX}_{key} must be a boolean"), - } -} - -fn env_usize(key: &str, default: usize) -> Result { - parse_env(key, default) -} - -fn env_u64(key: &str, default: u64) -> Result { - parse_env(key, default) -} - -fn env_f64(key: &str, default: f64) -> Result { - parse_env(key, default) -} - -fn env_id(key: &str, default: Id) -> Result { - let Some(value) = env_string(key) else { - return Ok(default); - }; - Id::parse(&value).with_context(|| format!("failed to parse {ENV_PREFIX}_{key}")) -} - -fn parse_env(key: &str, default: T) -> Result -where - T: std::str::FromStr, - T::Err: std::error::Error + Send + Sync + 'static, -{ - let Some(value) = env_string(key) else { - return Ok(default); - }; - value - .parse() - .with_context(|| format!("failed to parse {ENV_PREFIX}_{key}")) -} - -fn validate_rate(key: &str, rate: f64) -> Result<()> { - if rate.is_finite() && rate >= 0.0 { - Ok(()) - } else { - bail!("{ENV_PREFIX}_{key} must be a finite non-negative number") - } -} - -fn now_ms() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|duration| duration.as_millis() as u64) - .unwrap_or(0) -} - -fn duration_millis_u64(duration: Duration) -> u64 { - u64::try_from(duration.as_millis()).unwrap_or(u64::MAX) -} diff --git a/engine/packages/util/src/lib.rs b/engine/packages/util/src/lib.rs index d899e608c3..9b77b4b487 100644 --- a/engine/packages/util/src/lib.rs +++ b/engine/packages/util/src/lib.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + pub use id::Id; pub use rivet_util_id as id; @@ -12,6 +14,7 @@ pub mod future; pub mod geo; pub mod math; pub mod metric; +pub mod metrics; pub mod req; pub mod serde; pub mod size; @@ -43,3 +46,128 @@ pub fn safe_slice(s: &str, start: usize, end: usize) -> &str { &s[new_start..=new_end] } + +/// Records the duration of the code inside the macro. +/// +/// ```rust +/// observe!(task()); +/// // or +/// observe!(long, long_task()); +/// ``` +/// +/// Supports async work. +/// Use `observe_with!` for callback. +/// ``` +#[macro_export] +macro_rules! observe { + (long, $($tt:tt)*) => {{ + let __start = std::time::Instant::now(); + + let __res = $($tt)*; + let __dt = __start.elapsed().as_secs_f64(); + + let __location = format!("{}:{}:{}", file!(), line!(), column!()); + $crate::metrics::LONG_OBSERVATION_DURATION.with_label_values(&[&__location]) + .observe(__dt); + + __res + }}; + ($($tt:tt)*) => {{ + let __start = std::time::Instant::now(); + + let __res = $($tt)*; + let __dt = __start.elapsed().as_secs_f64(); + + let __location = format!("{}:{}:{}", file!(), line!(), column!()); + $crate::metrics::OBSERVATION_DURATION.with_label_values(&[&__location]) + .observe(__dt); + + __res + }}; +} + +/// Records the duration of the code inside the macro and a callback macro. +/// +/// ```rust +/// observe_with!(task(), |dt, location| { +/// if dt > Duration::from_secs(10) { +/// tracing::warn!("long work at {location}"); +/// } +/// }); +/// // or +/// observe_with!(long, task(), |dt, location| { +/// if dt > Duration::from_secs(10) { +/// tracing::warn!("long work at {location}"); +/// } +/// }); +/// ``` +/// +/// Supports async work. +#[macro_export] +macro_rules! observe_with { + (long, $cb:expr, $($tt:tt)*) => {{ + let __start = std::time::Instant::now(); + + let __res = $($tt)*; + let __dt = __start.elapsed().as_secs_f64(); + + let __location = $crate::location!().to_string(); + + ($cb)(__dt, __location.as_str()); + + $crate::metrics::LONG_OBSERVATION_DURATION.with_label_values(&[__location.as_str()]) + .observe(__dt); + + __res + }}; + ($cb:expr, $($tt:tt)*) => {{ + let __start = std::time::Instant::now(); + + let __res = $($tt)*; + let __dt = __start.elapsed().as_secs_f64(); + + let __location = $crate::location!().to_string(); + + ($cb)(__dt, __location.as_str()); + + $crate::metrics::OBSERVATION_DURATION.with_label_values(&[__location.as_str()]) + .observe(__dt); + + __res + }}; +} + +#[derive(Debug)] +pub struct Location { + file: &'static str, + line: u32, + column: u32, +} + +impl Location { + pub fn new(file: &'static str, line: u32, column: u32) -> Self { + Location { file, line, column } + } +} + +impl Display for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}:{}", self.file, self.line, self.column) + } +} + +/// Constructs a `Location` object with the current file name, line number, and +/// column number. +/// +/// # Examples +/// +/// ``` +/// let loc = location!(); +/// println!("This code is at: {:?}", loc); +/// ``` +#[macro_export] +macro_rules! location { + () => { + $crate::Location::new(file!(), line!(), column!()) + }; +} diff --git a/engine/packages/util/src/metrics.rs b/engine/packages/util/src/metrics.rs new file mode 100644 index 0000000000..3529bebd88 --- /dev/null +++ b/engine/packages/util/src/metrics.rs @@ -0,0 +1,34 @@ +use rivet_metrics::{BUCKETS, MICRO_BUCKETS, REGISTRY, prometheus::*}; + +lazy_static::lazy_static! { + pub static ref OBSERVATION_DURATION: HistogramVec = register_histogram_vec_with_registry!( + "observation_duration", + "Duration of any code observation.", + &["location"], + MICRO_BUCKETS.to_vec(), + *REGISTRY + ).unwrap(); + pub static ref LONG_OBSERVATION_DURATION: HistogramVec = register_histogram_vec_with_registry!( + "long_observation_duration", + "Duration of any long code observation.", + &["location"], + BUCKETS.to_vec(), + *REGISTRY + ).unwrap(); + + pub static ref SERIALIZE_SIZE: HistogramVec = register_histogram_vec_with_registry!( + "serialize_size", + "Size in bytes for any serialization.", + &["format", "location"], + vec![16.0, 32.0, 64.0, 128.0, 256.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0, 4194304.0, 16777216.0], + *REGISTRY + ).unwrap(); + + pub static ref DESERIALIZE_SIZE: HistogramVec = register_histogram_vec_with_registry!( + "deserialize_size", + "Size in bytes for any deserialization.", + &["format", "location"], + vec![16.0, 32.0, 64.0, 128.0, 256.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0, 4194304.0, 16777216.0], + *REGISTRY + ).unwrap(); +} diff --git a/engine/packages/util/src/serde.rs b/engine/packages/util/src/serde.rs index 20c97419cb..9d2de3f4b0 100644 --- a/engine/packages/util/src/serde.rs +++ b/engine/packages/util/src/serde.rs @@ -1 +1,107 @@ pub use rivet_util_serde::*; + +/// Wraps `serde_json::to_vec` with observability. +#[macro_export] +macro_rules! json_to_vec { + ($value:expr) => {{ + let __res = $crate::observe!(serde_json::to_vec($value)); + if let std::result::Result::Ok(__res) = &__res { + $crate::metrics::SERIALIZE_SIZE + .with_label_values(&["json", $crate::location!().to_string().as_str()]) + .observe(__res.len() as f64); + } + __res + }}; +} +pub use json_to_vec; + +/// Wraps `serde_json::to_string` with observability. +#[macro_export] +macro_rules! json_to_string { + ($value:expr) => {{ + let __res = $crate::observe!(serde_json::to_string($value)); + if let std::result::Result::Ok(__res) = &__res { + $crate::metrics::SERIALIZE_SIZE + .with_label_values(&["json", $crate::location!().to_string().as_str()]) + .observe(__res.len() as f64); + } + __res + }}; +} +pub use json_to_string; + +/// Wraps `serde_json::to_value` with observability. +#[macro_export] +macro_rules! json_to_value { + ($value:expr) => {{ $crate::observe!(serde_json::to_value($value)) }}; +} +pub use json_to_value; + +/// Wraps `serde_json::value::to_raw_value` with observability. +#[macro_export] +macro_rules! json_to_raw_value { + ($value:expr) => {{ + let __res = $crate::observe!(serde_json::value::to_raw_value($value)); + if let std::result::Result::Ok(__res) = &__res { + $crate::metrics::SERIALIZE_SIZE + .with_label_values(&["json", $crate::location!().to_string().as_str()]) + .observe(__res.get().len() as f64); + } + __res + }}; +} +pub use json_to_raw_value; + +/// Wraps `serde_json::to_vec` with observability. +#[macro_export] +macro_rules! json_from_str { + ($value:expr) => {{ + let __bind = $value; + $crate::metrics::DESERIALIZE_SIZE + .with_label_values(&["json", $crate::location!().to_string().as_str()]) + .observe(__bind.len() as f64); + $crate::observe!(serde_json::from_str(__bind)) + }}; +} +pub use json_from_str; + +/// Wraps `serde_json::to_vec` with observability. +#[macro_export] +macro_rules! json_from_slice { + ($value:expr) => {{ + let __bind = $value; + $crate::metrics::DESERIALIZE_SIZE + .with_label_values(&["json", $crate::location!().to_string().as_str()]) + .observe(__bind.len() as f64); + $crate::observe!(serde_json::from_slice($value)) + }}; +} +pub use json_from_slice; + +/// Wraps `serde_bare::to_vec` with observability. +#[macro_export] +macro_rules! bare_to_vec { + ($value:expr) => {{ + let __res = $crate::observe!(serde_bare::to_vec($value)); + if let std::result::Result::Ok(__res) = &__res { + $crate::metrics::SERIALIZE_SIZE + .with_label_values(&["bare", $crate::location!().to_string().as_str()]) + .observe(__res.len() as f64); + } + __res + }}; +} +pub use bare_to_vec; + +/// Wraps `serde_bare::to_vec` with observability. +#[macro_export] +macro_rules! bare_from_slice { + ($value:expr) => {{ + let __bind = $value; + $crate::metrics::DESERIALIZE_SIZE + .with_label_values(&["bare", $crate::location!().to_string().as_str()]) + .observe(__bind.len() as f64); + $crate::observe!(serde_bare::from_slice($value)) + }}; +} +pub use bare_from_slice; From 885cd0312c74a291e4df5ef413adbc8c6e249029 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 23 Jun 2026 13:00:10 -0700 Subject: [PATCH 04/19] Add observation for serde_bare --- Cargo.lock | 3 +- .../depot/src/conveyer/types/branch.rs | 20 +-- .../depot/src/conveyer/types/compaction.rs | 4 +- .../depot/src/conveyer/types/history_pin.rs | 4 +- .../depot/src/conveyer/types/policy.rs | 8 +- .../src/conveyer/types/restore_points.rs | 4 +- .../depot/src/conveyer/types/storage.rs | 12 +- .../packages/engine/src/commands/udb/cli.rs | 22 +-- engine/packages/epoxy/src/http_client.rs | 5 +- engine/packages/epoxy/src/http_routes.rs | 2 +- engine/packages/epoxy/src/keys/keys.rs | 12 +- engine/packages/epoxy/src/keys/replica.rs | 4 +- engine/packages/gasoline/src/workflow.rs | 2 +- engine/packages/pegboard-runner/Cargo.toml | 1 + .../pegboard-runner/src/ws_to_tunnel_task.rs | 4 +- engine/packages/pegboard/src/keys/actor_kv.rs | 4 +- .../packages/runner-protocol/src/versioned.rs | 116 ++++++++----- engine/packages/util/src/serde.rs | 4 +- .../src/versioned/namespace_runner_config.rs | 48 ++++-- .../sdks/rust/depot-protocol/src/versioned.rs | 4 +- engine/sdks/rust/envoy-protocol/Cargo.toml | 2 +- .../rust/envoy-protocol/src/versioned/mod.rs | 153 +++++++++--------- .../sdks/rust/epoxy-protocol/src/versioned.rs | 44 +++-- .../sdks/rust/ups-protocol/src/versioned.rs | 18 ++- 24 files changed, 290 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12ad23aba3..78308fa999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4147,6 +4147,7 @@ dependencies = [ "rivet-runner-protocol", "rivet-runtime", "rivet-types", + "rivet-util", "scc", "serde", "serde_bare", @@ -5516,7 +5517,7 @@ dependencies = [ "anyhow", "hex", "rand 0.8.5", - "rivet-util-serde", + "rivet-util", "rivet-vbare-compiler", "serde", "serde_bare", diff --git a/engine/packages/depot/src/conveyer/types/branch.rs b/engine/packages/depot/src/conveyer/types/branch.rs index f0c3faaf22..b0ee671ebf 100644 --- a/engine/packages/depot/src/conveyer/types/branch.rs +++ b/engine/packages/depot/src/conveyer/types/branch.rs @@ -77,14 +77,14 @@ impl OwnedVersionedData for VersionedDatabaseBranchRecord { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::Current(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::Current(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot DatabaseBranchRecord version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::Current(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::Current(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -108,14 +108,14 @@ impl OwnedVersionedData for VersionedDatabasePointer { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot DatabasePointer version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -139,14 +139,14 @@ impl OwnedVersionedData for VersionedBucketBranchRecord { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot BucketBranchRecord version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -170,14 +170,14 @@ impl OwnedVersionedData for VersionedBucketPointer { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot BucketPointer version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -201,14 +201,14 @@ impl OwnedVersionedData for VersionedPointerSnapshot { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot PointerSnapshot version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/packages/depot/src/conveyer/types/compaction.rs b/engine/packages/depot/src/conveyer/types/compaction.rs index 42b212bfb8..0d78e9b67a 100644 --- a/engine/packages/depot/src/conveyer/types/compaction.rs +++ b/engine/packages/depot/src/conveyer/types/compaction.rs @@ -61,14 +61,14 @@ macro_rules! impl_compaction_versioned_data { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot {} version: {version}", $name), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/packages/depot/src/conveyer/types/history_pin.rs b/engine/packages/depot/src/conveyer/types/history_pin.rs index b1592c0eda..f6a07c57e1 100644 --- a/engine/packages/depot/src/conveyer/types/history_pin.rs +++ b/engine/packages/depot/src/conveyer/types/history_pin.rs @@ -43,14 +43,14 @@ impl OwnedVersionedData for VersionedDbHistoryPin { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot DbHistoryPin version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/packages/depot/src/conveyer/types/policy.rs b/engine/packages/depot/src/conveyer/types/policy.rs index edbf5f2cda..a498dd78c3 100644 --- a/engine/packages/depot/src/conveyer/types/policy.rs +++ b/engine/packages/depot/src/conveyer/types/policy.rs @@ -59,14 +59,14 @@ impl OwnedVersionedData for VersionedPitrPolicy { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot PitrPolicy version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(policy) => serde_bare::to_vec(&policy).map_err(Into::into), + Self::V1(policy) => rivet_util::serde::bare_to_vec!(&policy).map_err(Into::into), } } } @@ -86,14 +86,14 @@ impl OwnedVersionedData for VersionedShardCachePolicy { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot ShardCachePolicy version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(policy) => serde_bare::to_vec(&policy).map_err(Into::into), + Self::V1(policy) => rivet_util::serde::bare_to_vec!(&policy).map_err(Into::into), } } } diff --git a/engine/packages/depot/src/conveyer/types/restore_points.rs b/engine/packages/depot/src/conveyer/types/restore_points.rs index 736365915d..f299e3cefa 100644 --- a/engine/packages/depot/src/conveyer/types/restore_points.rs +++ b/engine/packages/depot/src/conveyer/types/restore_points.rs @@ -161,14 +161,14 @@ impl OwnedVersionedData for VersionedRestorePointRecord { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot RestorePointRecord version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/packages/depot/src/conveyer/types/storage.rs b/engine/packages/depot/src/conveyer/types/storage.rs index 64ce085c4c..749244b365 100644 --- a/engine/packages/depot/src/conveyer/types/storage.rs +++ b/engine/packages/depot/src/conveyer/types/storage.rs @@ -53,14 +53,14 @@ impl OwnedVersionedData for VersionedDBHead { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot DBHead version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -84,14 +84,14 @@ impl OwnedVersionedData for VersionedCommitRow { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot CommitRow version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } @@ -115,14 +115,14 @@ impl OwnedVersionedData for VersionedMetaCompact { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot MetaCompact version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/packages/engine/src/commands/udb/cli.rs b/engine/packages/engine/src/commands/udb/cli.rs index a933ea0df4..c054c7eb72 100644 --- a/engine/packages/engine/src/commands/udb/cli.rs +++ b/engine/packages/engine/src/commands/udb/cli.rs @@ -929,21 +929,23 @@ impl SubCommand { // A v2 entry roundtrips byte-identically through the v2 // schema. v3 entries either fail to deserialize as v2 or // re-serialize to different bytes, so they are ignored. - let v2_entry: proto_v2::ChangelogEntry = - match serde_bare::from_slice(entry.value()) { - Ok(v) => v, - Err(_) => { - v3_count += 1; - continue; - } - }; - let reserialized = match serde_bare::to_vec(&v2_entry) { - Ok(b) => b, + let v2_entry: proto_v2::ChangelogEntry = match rivet_util::serde::bare_from_slice!( + entry.value() + ) { + Ok(v) => v, Err(_) => { v3_count += 1; continue; } }; + let reserialized = + match rivet_util::serde::bare_to_vec!(&v2_entry) { + Ok(b) => b, + Err(_) => { + v3_count += 1; + continue; + } + }; if reserialized != entry.value() { v3_count += 1; continue; diff --git a/engine/packages/epoxy/src/http_client.rs b/engine/packages/epoxy/src/http_client.rs index 539372d647..d495c3ed42 100644 --- a/engine/packages/epoxy/src/http_client.rs +++ b/engine/packages/epoxy/src/http_client.rs @@ -179,7 +179,8 @@ async fn send_request_to_address( let client = rivet_pools::reqwest::client().await?; // Create the request - let request = serde_bare::to_vec(&request).context("failed to serialize epoxy request")?; + let request = + rivet_util::serde::bare_to_vec!(&request).context("failed to serialize epoxy request")?; // Send the request let response_result = client @@ -223,7 +224,7 @@ async fn send_request_to_address( } let body = response.bytes().await?; - let response_body = serde_bare::from_slice(&body)?; + let response_body = rivet_util::serde::bare_from_slice!(&body)?; tracing::debug!( to_replica = to_replica_id, diff --git a/engine/packages/epoxy/src/http_routes.rs b/engine/packages/epoxy/src/http_routes.rs index 33c87586b2..865a860415 100644 --- a/engine/packages/epoxy/src/http_routes.rs +++ b/engine/packages/epoxy/src/http_routes.rs @@ -91,5 +91,5 @@ async fn handle_request(ctx: ApiCtx, request: protocol::Request) -> Result Result { - serde_bare::from_slice(raw).map_err(Into::into) + rivet_util::serde::bare_from_slice!(raw).map_err(Into::into) } fn serialize(&self, value: Self::Value) -> Result> { - serde_bare::to_vec(&value).map_err(Into::into) + rivet_util::serde::bare_to_vec!(&value).map_err(Into::into) } } @@ -203,11 +203,11 @@ impl FormalKey for KvAcceptedKey { type Value = KvAcceptedValue; fn deserialize(&self, raw: &[u8]) -> Result { - serde_bare::from_slice(raw).map_err(Into::into) + rivet_util::serde::bare_from_slice!(raw).map_err(Into::into) } fn serialize(&self, value: Self::Value) -> Result> { - serde_bare::to_vec(&value).map_err(Into::into) + rivet_util::serde::bare_to_vec!(&value).map_err(Into::into) } } @@ -368,11 +368,11 @@ impl FormalKey for ChangelogKey { // TODO: this is mistakenly not versioned. Transition to vbare so future // changes to ChangelogEntry don't require hand-rolled LegacyXxx fallbacks. fn deserialize(&self, raw: &[u8]) -> Result { - serde_bare::from_slice(raw).map_err(Into::into) + rivet_util::serde::bare_from_slice!(raw).map_err(Into::into) } fn serialize(&self, value: Self::Value) -> Result> { - serde_bare::to_vec(&value).map_err(Into::into) + rivet_util::serde::bare_to_vec!(&value).map_err(Into::into) } } diff --git a/engine/packages/epoxy/src/keys/replica.rs b/engine/packages/epoxy/src/keys/replica.rs index b0f7ce6988..beec8aa0d1 100644 --- a/engine/packages/epoxy/src/keys/replica.rs +++ b/engine/packages/epoxy/src/keys/replica.rs @@ -11,11 +11,11 @@ impl FormalKey for ConfigKey { // TODO: this is mistakenly not versioned. Transition to vbare so future // changes to ClusterConfig don't require hand-rolled LegacyXxx fallbacks. fn deserialize(&self, raw: &[u8]) -> Result { - serde_bare::from_slice(raw).map_err(Into::into) + rivet_util::serde::bare_from_slice!(raw).map_err(Into::into) } fn serialize(&self, value: Self::Value) -> Result> { - serde_bare::to_vec(&value).map_err(Into::into) + rivet_util::serde::bare_to_vec!(&value).map_err(Into::into) } } diff --git a/engine/packages/gasoline/src/workflow.rs b/engine/packages/gasoline/src/workflow.rs index bb16ded732..945f335177 100644 --- a/engine/packages/gasoline/src/workflow.rs +++ b/engine/packages/gasoline/src/workflow.rs @@ -33,7 +33,7 @@ impl<'a, T: DeserializeOwned + Serialize> StateGuard<'a, T> { pub(crate) fn new( guard: MutexGuard<'a, (Box, bool)>, ) -> Result { - let value = rivet_util::observe!(serde_json::from_str::(guard.0.get())?); + let value = rivet_util::serde::json_from_str!(guard.0.get())?; Ok(Self { guard, diff --git a/engine/packages/pegboard-runner/Cargo.toml b/engine/packages/pegboard-runner/Cargo.toml index d0d0e0e9eb..b23d7e0139 100644 --- a/engine/packages/pegboard-runner/Cargo.toml +++ b/engine/packages/pegboard-runner/Cargo.toml @@ -29,6 +29,7 @@ rivet-metrics.workspace = true rivet-runner-protocol.workspace = true rivet-runtime.workspace = true rivet-types.workspace = true +rivet-util.workspace = true scc.workspace = true serde_bare.workspace = true serde_json.workspace = true diff --git a/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs index a510172a75..483c53a598 100644 --- a/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-runner/src/ws_to_tunnel_task.rs @@ -1028,7 +1028,7 @@ async fn compat_ack_tunnel_message(conn: &Conn, payload: &[u8]) -> Result<()> { use rivet_runner_protocol::generated::v2 as protocol_v2; // Parse payload - let msg = serde_bare::from_slice::(&payload)?; + let msg: protocol_v2::ToServer = rivet_util::serde::bare_from_slice!(&payload)?; let protocol_v2::ToServer::ToServerTunnelMessage(msg) = msg else { return Ok(()); }; @@ -1036,7 +1036,7 @@ async fn compat_ack_tunnel_message(conn: &Conn, payload: &[u8]) -> Result<()> { tracing::debug!(?msg.request_id, ?msg.message_id, "sending v2 compat tunnel ack"); // Serialize response - let ack_msg = serde_bare::to_vec(&protocol_v2::ToClient::ToClientTunnelMessage( + let ack_msg = rivet_util::serde::bare_to_vec!(&protocol_v2::ToClient::ToClientTunnelMessage( protocol_v2::ToClientTunnelMessage { request_id: msg.request_id, message_id: msg.message_id, diff --git a/engine/packages/pegboard/src/keys/actor_kv.rs b/engine/packages/pegboard/src/keys/actor_kv.rs index c15e885813..82ce2072fd 100644 --- a/engine/packages/pegboard/src/keys/actor_kv.rs +++ b/engine/packages/pegboard/src/keys/actor_kv.rs @@ -150,11 +150,11 @@ impl FormalKey for EntryMetadataKey { // TODO: this is mistakenly not versioned. Transition to vbare so future // changes to KvMetadata don't require hand-rolled LegacyXxx fallbacks. fn deserialize(&self, raw: &[u8]) -> Result { - serde_bare::from_slice(raw).map_err(Into::into) + rivet_util::serde::bare_from_slice!(raw).map_err(Into::into) } fn serialize(&self, value: Self::Value) -> Result> { - serde_bare::to_vec(&value).map_err(Into::into) + rivet_util::serde::bare_to_vec!(&value).map_err(Into::into) } } diff --git a/engine/packages/runner-protocol/src/versioned.rs b/engine/packages/runner-protocol/src/versioned.rs index 1a94161592..92bc523a98 100644 --- a/engine/packages/runner-protocol/src/versioned.rs +++ b/engine/packages/runner-protocol/src/versioned.rs @@ -28,18 +28,24 @@ impl OwnedVersionedData for ToClientMk2 { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 4 => Ok(ToClientMk2::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(ToClientMk2::V5(serde_bare::from_slice(payload)?)), - 6 | 7 => Ok(ToClientMk2::V7(serde_bare::from_slice(payload)?)), + 4 => Ok(ToClientMk2::V4(rivet_util::serde::bare_from_slice!( + payload + )?)), + 5 => Ok(ToClientMk2::V5(rivet_util::serde::bare_from_slice!( + payload + )?)), + 6 | 7 => Ok(ToClientMk2::V7(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToClientMk2::V4(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToClientMk2::V5(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToClientMk2::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToClientMk2::V4(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToClientMk2::V5(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToClientMk2::V7(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -421,19 +427,25 @@ impl OwnedVersionedData for ToServerMk2 { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 4 => Ok(ToServerMk2::V4(serde_bare::from_slice(payload)?)), + 4 => Ok(ToServerMk2::V4(rivet_util::serde::bare_from_slice!( + payload + )?)), // v5 and v6 have the same ToServer binary format - 5 | 6 => Ok(ToServerMk2::V6(serde_bare::from_slice(payload)?)), - 7 => Ok(ToServerMk2::V7(serde_bare::from_slice(payload)?)), + 5 | 6 => Ok(ToServerMk2::V6(rivet_util::serde::bare_from_slice!( + payload + )?)), + 7 => Ok(ToServerMk2::V7(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToServerMk2::V4(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToServerMk2::V6(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToServerMk2::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToServerMk2::V4(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToServerMk2::V6(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToServerMk2::V7(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -1008,16 +1020,20 @@ impl OwnedVersionedData for ToRunnerMk2 { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 4 => Ok(ToRunnerMk2::V4(serde_bare::from_slice(payload)?)), - 5 | 6 | 7 => Ok(ToRunnerMk2::V7(serde_bare::from_slice(payload)?)), + 4 => Ok(ToRunnerMk2::V4(rivet_util::serde::bare_from_slice!( + payload + )?)), + 5 | 6 | 7 => Ok(ToRunnerMk2::V7(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToRunnerMk2::V4(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToRunnerMk2::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToRunnerMk2::V4(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToRunnerMk2::V7(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -1211,18 +1227,18 @@ impl OwnedVersionedData for ToClient { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(ToClient::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(ToClient::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(ToClient::V3(serde_bare::from_slice(payload)?)), + 1 => Ok(ToClient::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(ToClient::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(ToClient::V3(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToClient::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToClient::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToClient::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToClient::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToClient::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToClient::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -1561,18 +1577,18 @@ impl OwnedVersionedData for ToServer { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(ToServer::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(ToServer::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(ToServer::V3(serde_bare::from_slice(payload)?)), + 1 => Ok(ToServer::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(ToServer::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(ToServer::V3(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToServer::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToServer::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToServer::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToServer::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToServer::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToServer::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -1894,14 +1910,14 @@ impl OwnedVersionedData for ToRunner { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 | 2 | 3 => Ok(ToRunner::V3(serde_bare::from_slice(payload)?)), + 1 | 2 | 3 => Ok(ToRunner::V3(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToRunner::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToRunner::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -1939,16 +1955,16 @@ impl OwnedVersionedData for ToGateway { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 | 2 | 3 => Ok(ToGateway::V3(serde_bare::from_slice(payload)?)), - 4 | 5 | 6 | 7 => Ok(ToGateway::V7(serde_bare::from_slice(payload)?)), + 1 | 2 | 3 => Ok(ToGateway::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 | 5 | 6 | 7 => Ok(ToGateway::V7(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToGateway::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToGateway::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToGateway::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + ToGateway::V7(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -2044,16 +2060,24 @@ impl OwnedVersionedData for ToServerlessServer { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 | 2 | 3 => Ok(ToServerlessServer::V3(serde_bare::from_slice(payload)?)), - 4 | 5 | 6 | 7 => Ok(ToServerlessServer::V7(serde_bare::from_slice(payload)?)), + 1 | 2 | 3 => Ok(ToServerlessServer::V3(rivet_util::serde::bare_from_slice!( + payload + )?)), + 4 | 5 | 6 | 7 => Ok(ToServerlessServer::V7(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ToServerlessServer::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), - ToServerlessServer::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ToServerlessServer::V3(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + ToServerlessServer::V7(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } } } @@ -2123,16 +2147,24 @@ impl OwnedVersionedData for ActorCommandKeyData { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 4 => Ok(ActorCommandKeyData::V4(serde_bare::from_slice(payload)?)), - 5 | 6 | 7 => Ok(ActorCommandKeyData::V7(serde_bare::from_slice(payload)?)), + 4 => Ok(ActorCommandKeyData::V4( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 5 | 6 | 7 => Ok(ActorCommandKeyData::V7( + rivet_util::serde::bare_from_slice!(payload)?, + )), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - ActorCommandKeyData::V4(data) => serde_bare::to_vec(&data).map_err(Into::into), - ActorCommandKeyData::V7(data) => serde_bare::to_vec(&data).map_err(Into::into), + ActorCommandKeyData::V4(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + ActorCommandKeyData::V7(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } } } diff --git a/engine/packages/util/src/serde.rs b/engine/packages/util/src/serde.rs index 9d2de3f4b0..18a9823171 100644 --- a/engine/packages/util/src/serde.rs +++ b/engine/packages/util/src/serde.rs @@ -78,7 +78,7 @@ macro_rules! json_from_slice { } pub use json_from_slice; -/// Wraps `serde_bare::to_vec` with observability. +/// Wraps `rivet_util::serde::bare_to_vec!` with observability. #[macro_export] macro_rules! bare_to_vec { ($value:expr) => {{ @@ -93,7 +93,7 @@ macro_rules! bare_to_vec { } pub use bare_to_vec; -/// Wraps `serde_bare::to_vec` with observability. +/// Wraps `rivet_util::serde::bare_to_vec!` with observability. #[macro_export] macro_rules! bare_from_slice { ($value:expr) => {{ diff --git a/engine/sdks/rust/data/src/versioned/namespace_runner_config.rs b/engine/sdks/rust/data/src/versioned/namespace_runner_config.rs index 6c4a40ad23..f887175e90 100644 --- a/engine/sdks/rust/data/src/versioned/namespace_runner_config.rs +++ b/engine/sdks/rust/data/src/versioned/namespace_runner_config.rs @@ -30,24 +30,48 @@ impl OwnedVersionedData for NamespaceRunnerConfig { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(NamespaceRunnerConfig::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(NamespaceRunnerConfig::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(NamespaceRunnerConfig::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(NamespaceRunnerConfig::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(NamespaceRunnerConfig::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(NamespaceRunnerConfig::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(NamespaceRunnerConfig::V1( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 2 => Ok(NamespaceRunnerConfig::V2( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 3 => Ok(NamespaceRunnerConfig::V3( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 4 => Ok(NamespaceRunnerConfig::V4( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 5 => Ok(NamespaceRunnerConfig::V5( + rivet_util::serde::bare_from_slice!(payload)?, + )), + 6 => Ok(NamespaceRunnerConfig::V6( + rivet_util::serde::bare_from_slice!(payload)?, + )), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - NamespaceRunnerConfig::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), - NamespaceRunnerConfig::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - NamespaceRunnerConfig::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), - NamespaceRunnerConfig::V4(data) => serde_bare::to_vec(&data).map_err(Into::into), - NamespaceRunnerConfig::V5(data) => serde_bare::to_vec(&data).map_err(Into::into), - NamespaceRunnerConfig::V6(data) => serde_bare::to_vec(&data).map_err(Into::into), + NamespaceRunnerConfig::V1(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + NamespaceRunnerConfig::V2(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + NamespaceRunnerConfig::V3(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + NamespaceRunnerConfig::V4(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + NamespaceRunnerConfig::V5(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } + NamespaceRunnerConfig::V6(data) => { + rivet_util::serde::bare_to_vec!(&data).map_err(Into::into) + } } } diff --git a/engine/sdks/rust/depot-protocol/src/versioned.rs b/engine/sdks/rust/depot-protocol/src/versioned.rs index c8432da5ae..d90a617eec 100644 --- a/engine/sdks/rust/depot-protocol/src/versioned.rs +++ b/engine/sdks/rust/depot-protocol/src/versioned.rs @@ -22,14 +22,14 @@ impl OwnedVersionedData for DBHead { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid depot db head version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + Self::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } } diff --git a/engine/sdks/rust/envoy-protocol/Cargo.toml b/engine/sdks/rust/envoy-protocol/Cargo.toml index 4e3a222a97..907353804d 100644 --- a/engine/sdks/rust/envoy-protocol/Cargo.toml +++ b/engine/sdks/rust/envoy-protocol/Cargo.toml @@ -12,7 +12,7 @@ description = "Versioned Envoy protocol types for Rivet actor hosts" anyhow.workspace = true hex.workspace = true rand.workspace = true -rivet-util-serde.workspace = true +rivet-util.workspace = true serde_bare.workspace = true serde.workspace = true utoipa.workspace = true diff --git a/engine/sdks/rust/envoy-protocol/src/versioned/mod.rs b/engine/sdks/rust/envoy-protocol/src/versioned/mod.rs index ce618c0c9d..5357809472 100644 --- a/engine/sdks/rust/envoy-protocol/src/versioned/mod.rs +++ b/engine/sdks/rust/envoy-protocol/src/versioned/mod.rs @@ -129,24 +129,24 @@ impl OwnedVersionedData for ToEnvoy { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -261,24 +261,24 @@ impl OwnedVersionedData for ToRivet { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -393,24 +393,24 @@ impl OwnedVersionedData for ToEnvoyConn { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -525,24 +525,24 @@ impl OwnedVersionedData for ToGateway { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -657,24 +657,24 @@ impl OwnedVersionedData for ToOutbound { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -789,24 +789,24 @@ impl OwnedVersionedData for ActorCommandKeyData { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(Self::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(Self::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Self::V3(serde_bare::from_slice(payload)?)), - 4 => Ok(Self::V4(serde_bare::from_slice(payload)?)), - 5 => Ok(Self::V5(serde_bare::from_slice(payload)?)), - 6 => Ok(Self::V6(serde_bare::from_slice(payload)?)), + 1 => Ok(Self::V1(rivet_util::serde::bare_from_slice!(payload)?)), + 2 => Ok(Self::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Self::V3(rivet_util::serde::bare_from_slice!(payload)?)), + 4 => Ok(Self::V4(rivet_util::serde::bare_from_slice!(payload)?)), + 5 => Ok(Self::V5(rivet_util::serde::bare_from_slice!(payload)?)), + 6 => Ok(Self::V6(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Self::V1(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V2(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V3(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V4(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V5(x) => serde_bare::to_vec(&x).map_err(Into::into), - Self::V6(x) => serde_bare::to_vec(&x).map_err(Into::into), + Self::V1(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V2(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V3(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V4(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V5(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), + Self::V6(x) => rivet_util::serde::bare_to_vec!(&x).map_err(Into::into), } } @@ -934,8 +934,8 @@ mod tests { #[test] fn v1_start_command_deserializes_into_latest_without_sqlite_startup_data() -> Result<()> { - let payload = - serde_bare::to_vec(&v1::ToEnvoy::ToEnvoyCommands(vec![v1::CommandWrapper { + let payload = rivet_util::serde::bare_to_vec!(&v1::ToEnvoy::ToEnvoyCommands(vec![ + v1::CommandWrapper { checkpoint: v1::ActorCheckpoint { actor_id: "actor".into(), generation: 7, @@ -951,7 +951,8 @@ mod tests { hibernating_requests: Vec::new(), preloaded_kv: None, }), - }]))?; + } + ]))?; let decoded = ToEnvoy::deserialize(&payload, 1)?; let v6::ToEnvoy::ToEnvoyCommands(commands) = decoded else { @@ -969,7 +970,7 @@ mod tests { #[test] fn v2_sqlite_response_does_not_deserialize_to_stateless_protocol() -> Result<()> { - let payload = serde_bare::to_vec(&v2::ToEnvoy::ToEnvoySqliteCommitResponse( + let payload = rivet_util::serde::bare_to_vec!(&v2::ToEnvoy::ToEnvoySqliteCommitResponse( v2::ToEnvoySqliteCommitResponse { request_id: 1, data: v2::SqliteCommitResponse::SqliteErrorResponse(v2::SqliteErrorResponse { diff --git a/engine/sdks/rust/epoxy-protocol/src/versioned.rs b/engine/sdks/rust/epoxy-protocol/src/versioned.rs index eedebf7662..2dd501bbb8 100644 --- a/engine/sdks/rust/epoxy-protocol/src/versioned.rs +++ b/engine/sdks/rust/epoxy-protocol/src/versioned.rs @@ -26,16 +26,20 @@ impl OwnedVersionedData for CommittedValue { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 2 => Ok(CommittedValue::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(CommittedValue::V3(serde_bare::from_slice(payload)?)), + 2 => Ok(CommittedValue::V2(rivet_util::serde::bare_from_slice!( + payload + )?)), + 3 => Ok(CommittedValue::V3(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - CommittedValue::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - CommittedValue::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + CommittedValue::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + CommittedValue::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -89,16 +93,20 @@ impl OwnedVersionedData for CachedValue { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 2 => Ok(CachedValue::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(CachedValue::V3(serde_bare::from_slice(payload)?)), + 2 => Ok(CachedValue::V2(rivet_util::serde::bare_from_slice!( + payload + )?)), + 3 => Ok(CachedValue::V3(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - CachedValue::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - CachedValue::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + CachedValue::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + CachedValue::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -153,16 +161,20 @@ impl OwnedVersionedData for AcceptedValue { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 2 => Ok(AcceptedValue::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(AcceptedValue::V3(serde_bare::from_slice(payload)?)), + 2 => Ok(AcceptedValue::V2(rivet_util::serde::bare_from_slice!( + payload + )?)), + 3 => Ok(AcceptedValue::V3(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - AcceptedValue::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - AcceptedValue::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + AcceptedValue::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + AcceptedValue::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } @@ -216,16 +228,16 @@ impl OwnedVersionedData for Request { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 2 => Ok(Request::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(Request::V3(serde_bare::from_slice(payload)?)), + 2 => Ok(Request::V2(rivet_util::serde::bare_from_slice!(payload)?)), + 3 => Ok(Request::V3(rivet_util::serde::bare_from_slice!(payload)?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - Request::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - Request::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + Request::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + Request::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } diff --git a/engine/sdks/rust/ups-protocol/src/versioned.rs b/engine/sdks/rust/ups-protocol/src/versioned.rs index e94dff3890..ee756e64bc 100644 --- a/engine/sdks/rust/ups-protocol/src/versioned.rs +++ b/engine/sdks/rust/ups-protocol/src/versioned.rs @@ -26,18 +26,24 @@ impl OwnedVersionedData for UpsMessage { fn deserialize_version(payload: &[u8], version: u16) -> Result { match version { - 1 => Ok(UpsMessage::V1(serde_bare::from_slice(payload)?)), - 2 => Ok(UpsMessage::V2(serde_bare::from_slice(payload)?)), - 3 => Ok(UpsMessage::V3(serde_bare::from_slice(payload)?)), + 1 => Ok(UpsMessage::V1(rivet_util::serde::bare_from_slice!( + payload + )?)), + 2 => Ok(UpsMessage::V2(rivet_util::serde::bare_from_slice!( + payload + )?)), + 3 => Ok(UpsMessage::V3(rivet_util::serde::bare_from_slice!( + payload + )?)), _ => bail!("invalid version: {version}"), } } fn serialize_version(self, _version: u16) -> Result> { match self { - UpsMessage::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), - UpsMessage::V2(data) => serde_bare::to_vec(&data).map_err(Into::into), - UpsMessage::V3(data) => serde_bare::to_vec(&data).map_err(Into::into), + UpsMessage::V1(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + UpsMessage::V2(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), + UpsMessage::V3(data) => rivet_util::serde::bare_to_vec!(&data).map_err(Into::into), } } From 7717f5dc251f54d87be4e29fde3e88fb7236edf2 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 23 Jun 2026 14:24:25 -0700 Subject: [PATCH 05/19] [SLOP(claude-opus-4-8)] feat(rivetkit-core): add serde duration and size metrics --- .../rivetkit-core/src/actor/persist.rs | 16 +- .../packages/rivetkit-core/src/lib.rs | 1 + .../rivetkit-core/src/registry/http.rs | 100 +++++++---- .../rivetkit-core/src/serde_metrics.rs | 163 ++++++++++++++++++ 4 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 rivetkit-rust/packages/rivetkit-core/src/serde_metrics.rs diff --git a/rivetkit-rust/packages/rivetkit-core/src/actor/persist.rs b/rivetkit-rust/packages/rivetkit-core/src/actor/persist.rs index 52612c9dbf..ec29e45f8e 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/actor/persist.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/actor/persist.rs @@ -1,6 +1,8 @@ use anyhow::{Context, Result}; use vbare::OwnedVersionedData; +use crate::serde_metrics; + pub(crate) fn encode_latest_with_embedded_version( latest: T::Latest, version: u16, @@ -9,9 +11,11 @@ pub(crate) fn encode_latest_with_embedded_version( where T: OwnedVersionedData, { - T::wrap_latest(latest) - .serialize_with_embedded_version(version) - .with_context(|| format!("encode {label} versioned bare payload")) + serde_metrics::measure_serialize("bare", label, || { + T::wrap_latest(latest) + .serialize_with_embedded_version(version) + .with_context(|| format!("encode {label} versioned bare payload")) + }) } pub(crate) fn decode_latest_with_embedded_version( @@ -21,6 +25,8 @@ pub(crate) fn decode_latest_with_embedded_version( where T: OwnedVersionedData, { - ::deserialize_with_embedded_version(payload) - .with_context(|| format!("decode {label} versioned bare payload")) + serde_metrics::measure_deserialize("bare", label, payload.len(), || { + ::deserialize_with_embedded_version(payload) + .with_context(|| format!("decode {label} versioned bare payload")) + }) } diff --git a/rivetkit-rust/packages/rivetkit-core/src/lib.rs b/rivetkit-rust/packages/rivetkit-core/src/lib.rs index 7604aad0ab..8212027a1b 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/lib.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/lib.rs @@ -12,6 +12,7 @@ pub mod inspector_bundle; pub mod metrics_endpoint; pub mod registry; pub mod runtime; +pub(crate) mod serde_metrics; pub mod serverless; #[cfg(feature = "native-runtime")] pub mod serverless_http; diff --git a/rivetkit-rust/packages/rivetkit-core/src/registry/http.rs b/rivetkit-rust/packages/rivetkit-core/src/registry/http.rs index 9839d79247..088debd34c 100644 --- a/rivetkit-rust/packages/rivetkit-core/src/registry/http.rs +++ b/rivetkit-rust/packages/rivetkit-core/src/registry/http.rs @@ -2,6 +2,7 @@ use super::dispatch::*; use super::inspector::*; use super::*; use crate::error::{ProtocolError, client_error_message, client_error_metadata}; +use crate::serde_metrics; use ::http; const HEADER_RIVET_ACTOR: &str = "x-rivet-actor"; @@ -773,6 +774,15 @@ pub(super) fn content_type_for_encoding(encoding: HttpResponseEncoding) -> &'sta } } +/// Bounded serde metric `format` label for the request/response encoding. +fn encoding_format_label(encoding: HttpResponseEncoding) -> &'static str { + match encoding { + HttpResponseEncoding::Json => "json", + HttpResponseEncoding::Cbor => "cbor", + HttpResponseEncoding::Bare => "bare", + } +} + pub(super) fn serialize_http_response_error( encoding: HttpResponseEncoding, group: &str, @@ -830,26 +840,31 @@ pub(super) fn decode_http_action_args( encoding: HttpResponseEncoding, body: &[u8], ) -> Result> { - match encoding { - HttpResponseEncoding::Json => { - let request: HttpActionRequestJson = - serde_json::from_slice(body).context("decode json HTTP action request")?; - let args = normalize_json_args(request.args); - encode_json_as_cbor(&args) - } - HttpResponseEncoding::Cbor => { - let request: HttpActionRequestJson = ciborium::from_reader(Cursor::new(body)) - .context("decode cbor HTTP action request")?; - let args = normalize_json_args(request.args); - encode_json_as_cbor(&args) - } - HttpResponseEncoding::Bare => { - let request = - ::deserialize_with_embedded_version(body) - .context("decode bare HTTP action request")?; - Ok(request.args) - } - } + serde_metrics::measure_deserialize( + encoding_format_label(encoding), + "http_action_request", + body.len(), + || match encoding { + HttpResponseEncoding::Json => { + let request: HttpActionRequestJson = + serde_json::from_slice(body).context("decode json HTTP action request")?; + let args = normalize_json_args(request.args); + encode_json_as_cbor(&args) + } + HttpResponseEncoding::Cbor => { + let request: HttpActionRequestJson = ciborium::from_reader(Cursor::new(body)) + .context("decode cbor HTTP action request")?; + let args = normalize_json_args(request.args); + encode_json_as_cbor(&args) + } + HttpResponseEncoding::Bare => { + let request = + ::deserialize_with_embedded_version(body) + .context("decode bare HTTP action request")?; + Ok(request.args) + } + }, + ) } fn normalize_json_args(args: JsonValue) -> Vec { @@ -900,25 +915,34 @@ pub(super) fn encode_http_action_response( encoding: HttpResponseEncoding, output: Vec, ) -> Result { - let body = match encoding { - HttpResponseEncoding::Json => serde_json::to_vec(&json!({ - "output": decode_cbor_json_or_null(&output), - }))?, - HttpResponseEncoding::Cbor => { - let mut out = Vec::new(); - ciborium::into_writer( - &json!({ + let body = serde_metrics::measure_serialize( + encoding_format_label(encoding), + "http_action_response", + || { + let body = match encoding { + HttpResponseEncoding::Json => serde_json::to_vec(&json!({ "output": decode_cbor_json_or_null(&output), - }), - &mut out, - )?; - out - } - HttpResponseEncoding::Bare => client_protocol::versioned::HttpActionResponse::wrap_latest( - client_protocol::HttpActionResponse { output }, - ) - .serialize_with_embedded_version(client_protocol::PROTOCOL_VERSION)?, - }; + }))?, + HttpResponseEncoding::Cbor => { + let mut out = Vec::new(); + ciborium::into_writer( + &json!({ + "output": decode_cbor_json_or_null(&output), + }), + &mut out, + )?; + out + } + HttpResponseEncoding::Bare => { + client_protocol::versioned::HttpActionResponse::wrap_latest( + client_protocol::HttpActionResponse { output }, + ) + .serialize_with_embedded_version(client_protocol::PROTOCOL_VERSION)? + } + }; + Ok(body) + }, + )?; Ok(HttpResponse { status: StatusCode::OK.as_u16(), headers: HashMap::from([( diff --git a/rivetkit-rust/packages/rivetkit-core/src/serde_metrics.rs b/rivetkit-rust/packages/rivetkit-core/src/serde_metrics.rs new file mode 100644 index 0000000000..b105c19f44 --- /dev/null +++ b/rivetkit-rust/packages/rivetkit-core/src/serde_metrics.rs @@ -0,0 +1,163 @@ +//! Duration and size metrics for serialization and deserialization hot paths. +//! +//! These mirror the engine-side serde observability but follow rivetkit's +//! metric conventions: `rivetkit_`-prefixed names registered through a +//! `LazyLock` collector struct, and `crate::time::Instant` so the same code +//! compiles for the wasm runtime. +//! +//! The `format` label is the wire format (`bare`, `json`, `cbor`). The +//! `location` label identifies the call site and must be a bounded, code-defined +//! string, never user input. + +use std::sync::LazyLock; +use std::time::Duration; + +use rivet_metrics::{ + MICRO_BUCKETS, + prometheus::{HistogramOpts, HistogramVec, Registry}, +}; + +use crate::time::Instant; + +const SERDE_LABELS: &[&str] = &["format", "location"]; + +/// Byte-size buckets shared by serialize and deserialize size histograms. +fn serde_size_buckets() -> Vec { + vec![ + 16.0, 32.0, 64.0, 128.0, 256.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0, + 4194304.0, 16777216.0, + ] +} + +struct SerdeMetricCollectors { + serialize_size: HistogramVec, + deserialize_size: HistogramVec, + serialize_duration_seconds: HistogramVec, + deserialize_duration_seconds: HistogramVec, +} + +static METRICS: LazyLock = LazyLock::new(SerdeMetricCollectors::new); + +impl SerdeMetricCollectors { + fn new() -> Self { + let serialize_size = HistogramVec::new( + HistogramOpts::new( + "rivetkit_serialize_size", + "size in bytes for any serialization", + ) + .buckets(serde_size_buckets()), + SERDE_LABELS, + ) + .expect("create rivetkit_serialize_size histogram"); + let deserialize_size = HistogramVec::new( + HistogramOpts::new( + "rivetkit_deserialize_size", + "size in bytes for any deserialization", + ) + .buckets(serde_size_buckets()), + SERDE_LABELS, + ) + .expect("create rivetkit_deserialize_size histogram"); + let serialize_duration_seconds = HistogramVec::new( + HistogramOpts::new( + "rivetkit_serialize_duration_seconds", + "duration in seconds for any serialization", + ) + .buckets(MICRO_BUCKETS.to_vec()), + SERDE_LABELS, + ) + .expect("create rivetkit_serialize_duration_seconds histogram"); + let deserialize_duration_seconds = HistogramVec::new( + HistogramOpts::new( + "rivetkit_deserialize_duration_seconds", + "duration in seconds for any deserialization", + ) + .buckets(MICRO_BUCKETS.to_vec()), + SERDE_LABELS, + ) + .expect("create rivetkit_deserialize_duration_seconds histogram"); + + register_metric(&rivet_metrics::REGISTRY, serialize_size.clone()); + register_metric(&rivet_metrics::REGISTRY, deserialize_size.clone()); + register_metric(&rivet_metrics::REGISTRY, serialize_duration_seconds.clone()); + register_metric( + &rivet_metrics::REGISTRY, + deserialize_duration_seconds.clone(), + ); + + Self { + serialize_size, + deserialize_size, + serialize_duration_seconds, + deserialize_duration_seconds, + } + } +} + +/// Records the duration and output size of a serialization producing `Vec`. +/// +/// The size is only recorded when the closure succeeds. +pub(crate) fn measure_serialize( + format: &str, + location: &str, + f: impl FnOnce() -> anyhow::Result>, +) -> anyhow::Result> { + let started = Instant::now(); + let result = f(); + observe( + &METRICS.serialize_duration_seconds, + format, + location, + started.elapsed(), + ); + if let Ok(bytes) = &result { + observe_size(&METRICS.serialize_size, format, location, bytes.len()); + } + result +} + +/// Records the duration and input size of a deserialization. +/// +/// The input size is recorded unconditionally because the bytes are available +/// regardless of whether decoding succeeds. +pub(crate) fn measure_deserialize( + format: &str, + location: &str, + input_len: usize, + f: impl FnOnce() -> anyhow::Result, +) -> anyhow::Result { + observe_size(&METRICS.deserialize_size, format, location, input_len); + let started = Instant::now(); + let result = f(); + observe( + &METRICS.deserialize_duration_seconds, + format, + location, + started.elapsed(), + ); + result +} + +fn observe(metric: &HistogramVec, format: &str, location: &str, elapsed: Duration) { + metric + .with_label_values(&[format, location]) + .observe(elapsed.as_secs_f64()); +} + +fn observe_size(metric: &HistogramVec, format: &str, location: &str, size: usize) { + metric + .with_label_values(&[format, location]) + .observe(size as f64); +} + +fn register_metric(registry: &Registry, metric: M) +where + M: rivet_metrics::prometheus::core::Collector + Clone + Send + Sync + 'static, +{ + if let Err(error) = registry.register(Box::new(metric)) { + tracing::warn!( + ?error, + "serde metric registration failed, using existing collector" + ); + } +} From c6eeb2a3fd5718849f2a3b8d1d241f5ccf08afb5 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 23 Jun 2026 17:28:30 -0700 Subject: [PATCH 06/19] [SLOP(claude-opus-4-8)] feat(util): add rate limiter primitive and ingress throttles for actor create and gateway websocket --- Cargo.lock | 1 + .../errors/actor.creation_rate_limit.json | 5 + engine/packages/config/src/config/pegboard.rs | 30 ++ engine/packages/gasoline/src/ctx/message.rs | 2 +- engine/packages/gasoline/src/error.rs | 2 +- .../packages/guard-core/src/proxy_service.rs | 16 +- engine/packages/guard-core/src/utils.rs | 44 +- engine/packages/pegboard-gateway2/src/lib.rs | 1 + .../pegboard-gateway2/src/shared_state.rs | 2 +- .../src/ws_to_tunnel_task.rs | 16 + engine/packages/pegboard/Cargo.toml | 1 + engine/packages/pegboard/src/errors.rs | 6 + .../packages/pegboard/src/ops/actor/create.rs | 34 ++ .../pegboard/src/workflows/actor/runtime.rs | 4 +- .../pegboard/src/workflows/actor2/runtime.rs | 2 +- .../workflows/runner_pool_metadata_poller.rs | 2 +- .../pegboard/src/workflows/serverless/conn.rs | 4 +- .../src/driver/postgres/mod.rs | 2 +- engine/packages/universalpubsub/src/pubsub.rs | 2 +- engine/packages/util/src/backoff.rs | 109 ---- engine/packages/util/src/lib.rs | 2 +- engine/packages/util/src/throttle.rs | 487 ++++++++++++++++++ 22 files changed, 606 insertions(+), 168 deletions(-) create mode 100644 engine/artifacts/errors/actor.creation_rate_limit.json delete mode 100644 engine/packages/util/src/backoff.rs create mode 100644 engine/packages/util/src/throttle.rs diff --git a/Cargo.lock b/Cargo.lock index 78308fa999..72af92dd43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3938,6 +3938,7 @@ dependencies = [ "futures-util", "gasoline", "lazy_static", + "moka", "namespace", "nix 0.30.1", "portpicker", diff --git a/engine/artifacts/errors/actor.creation_rate_limit.json b/engine/artifacts/errors/actor.creation_rate_limit.json new file mode 100644 index 0000000000..ff4ee74181 --- /dev/null +++ b/engine/artifacts/errors/actor.creation_rate_limit.json @@ -0,0 +1,5 @@ +{ + "code": "creation_rate_limit", + "group": "actor", + "message": "Too many actors created at once. Try again later." +} \ No newline at end of file diff --git a/engine/packages/config/src/config/pegboard.rs b/engine/packages/config/src/config/pegboard.rs index 616af06a36..f05a98319f 100644 --- a/engine/packages/config/src/config/pegboard.rs +++ b/engine/packages/config/src/config/pegboard.rs @@ -117,6 +117,12 @@ pub struct Pegboard { pub gateway_hws_max_pending_size: Option, /// Max HTTP request body size in bytes for requests to actors. pub gateway_http_max_request_body_size: Option, + /// Max burst of inbound WebSocket messages on a single connection before throttling. + pub gateway_websocket_rate_limit_requests: Option, + /// Time to regain one inbound WebSocket message token on a single connection. + /// + /// Unit is in milliseconds. + pub gateway_websocket_rate_limit_drip_rate_ms: Option, // === Envoy Settings === /// How long to wait before considering an envoy lost and evicting all of its actors. @@ -162,6 +168,14 @@ pub struct Pegboard { /// /// Unit is in bytes. Default: 1,048,576 (1 MiB). pub preload_max_total_bytes: Option, + + // === Rate Limiting === + /// Max burst of actor creations per namespace before throttling. + pub actor_create_rate_limit_requests: Option, + /// Time to regain one actor creation token per namespace. + /// + /// Unit is in milliseconds. + pub actor_create_rate_limit_drip_rate_ms: Option, } impl Pegboard { @@ -369,6 +383,22 @@ impl Pegboard { self.serverless_drain_grace_period.unwrap_or(10_000) } + pub fn gateway_websocket_rate_limit_requests(&self) -> u64 { + self.gateway_websocket_rate_limit_requests.unwrap_or(2_000) + } + + pub fn gateway_websocket_rate_limit_drip_rate_ms(&self) -> u64 { + self.gateway_websocket_rate_limit_drip_rate_ms.unwrap_or(10) + } + + pub fn actor_create_rate_limit_requests(&self) -> u64 { + self.actor_create_rate_limit_requests.unwrap_or(500) + } + + pub fn actor_create_rate_limit_drip_rate_ms(&self) -> u64 { + self.actor_create_rate_limit_drip_rate_ms.unwrap_or(10) + } + pub fn preload_max_total_bytes(&self) -> u64 { self.preload_max_total_bytes.unwrap_or(1_048_576) } diff --git a/engine/packages/gasoline/src/ctx/message.rs b/engine/packages/gasoline/src/ctx/message.rs index a6edb2fe32..4c7fcf5e50 100644 --- a/engine/packages/gasoline/src/ctx/message.rs +++ b/engine/packages/gasoline/src/ctx/message.rs @@ -139,7 +139,7 @@ impl MessageCtx { M: Message, { // Infinite backoff since we want to wait until the service reboots. - let mut backoff = rivet_util::backoff::Backoff::default_infinite(); + let mut backoff = rivet_util::throttle::Backoff::default_infinite(); loop { // Ignore for infinite backoff backoff.tick().await; diff --git a/engine/packages/gasoline/src/error.rs b/engine/packages/gasoline/src/error.rs index 2fa7c686b9..1df67e17ad 100644 --- a/engine/packages/gasoline/src/error.rs +++ b/engine/packages/gasoline/src/error.rs @@ -193,7 +193,7 @@ impl WorkflowError { | WorkflowError::ActivityTimeout(_, error_count) | WorkflowError::OperationTimeout(_, error_count) => { // NOTE: Max retry is handled in `WorkflowCtx::activity` - let mut backoff = rivet_util::backoff::Backoff::new_at( + let mut backoff = rivet_util::throttle::Backoff::new_at( 8, None, RETRY_TIMEOUT_MS, diff --git a/engine/packages/guard-core/src/proxy_service.rs b/engine/packages/guard-core/src/proxy_service.rs index 8f53836ad0..9e94695509 100644 --- a/engine/packages/guard-core/src/proxy_service.rs +++ b/engine/packages/guard-core/src/proxy_service.rs @@ -33,7 +33,7 @@ use crate::RouteTarget; use crate::request_context::RequestContext; use crate::response_body::ResponseBody; use crate::route::{CacheKeyFn, ResolveRouteOutput, RouteCache, RoutingFn, RoutingOutput}; -use crate::utils::{InFlightCounter, RateLimiter}; +use crate::utils::InFlightCounter; use crate::{ WebSocketHandle, custom_serve::HibernationResult, errors, metrics, task_group::TaskGroup, utils, }; @@ -63,7 +63,7 @@ pub struct ProxyState { >, route_cache: RouteCache, // We use moka::Cache instead of scc::HashMap because it automatically handles TTL and capacity - rate_limiters: Cache>>, + rate_limiters: Cache>>, in_flight_counters: Cache>>, in_flight_requests: Cache, @@ -105,11 +105,11 @@ impl ProxyState { route_cache: RouteCache::new(route_cache_ttl), rate_limiters: Cache::builder() .max_capacity(10_000) - .time_to_live(PROXY_STATE_CACHE_TTL) + .time_to_idle(PROXY_STATE_CACHE_TTL) .build(), in_flight_counters: Cache::builder() .max_capacity(10_000) - .time_to_live(PROXY_STATE_CACHE_TTL) + .time_to_idle(PROXY_STATE_CACHE_TTL) .build(), in_flight_requests: Cache::builder().max_capacity(10_000_000).build(), tasks: TaskGroup::new(), @@ -224,9 +224,11 @@ impl ProxyState { if let Some(existing_limiter) = self.rate_limiters.get(&req_ctx.client_ip).await { existing_limiter } else { - let new_limiter = Arc::new(Mutex::new(RateLimiter::new( - req_ctx.rate_limit.requests, - req_ctx.rate_limit.period, + let new_limiter = Arc::new(Mutex::new(rivet_util::throttle::RateLimiter::new( + rivet_util::throttle::RateLimitMethod::FixedWindow { + requests: req_ctx.rate_limit.requests, + period: Duration::from_secs(req_ctx.rate_limit.period), + }, ))); self.rate_limiters .insert(req_ctx.client_ip, new_limiter.clone()) diff --git a/engine/packages/guard-core/src/utils.rs b/engine/packages/guard-core/src/utils.rs index 5503611dcc..7c5b3af382 100644 --- a/engine/packages/guard-core/src/utils.rs +++ b/engine/packages/guard-core/src/utils.rs @@ -7,7 +7,7 @@ use hyper::header::HeaderName; use rivet_api_builder::{ErrorResponse, RawErrorResponse}; use rivet_error::{INTERNAL_ERROR, RivetError}; use rivet_util::Id; -use std::time::{Duration, Instant}; +use std::time::Duration; use tokio_tungstenite::tungstenite::protocol::{CloseFrame, frame::coding::CloseCode}; use url::Url; @@ -19,7 +19,7 @@ const X_RIVET_TARGET: HeaderName = HeaderName::from_static("x-rivet-target"); const X_RIVET_ACTOR: HeaderName = HeaderName::from_static("x-rivet-actor"); const X_RIVET_TOKEN: HeaderName = HeaderName::from_static("x-rivet-token"); -// In-flight requests counter +// In-flight requests counter (semaphore) pub(crate) struct InFlightCounter { count: usize, max: usize, @@ -44,43 +44,6 @@ impl InFlightCounter { } } -// Rate limiter -pub(crate) struct RateLimiter { - requests_remaining: u64, - reset_time: Instant, - requests_limit: u64, - period: Duration, -} - -impl RateLimiter { - pub(crate) fn new(requests: u64, period_seconds: u64) -> Self { - Self { - requests_remaining: requests, - reset_time: Instant::now() + Duration::from_secs(period_seconds), - requests_limit: requests, - period: Duration::from_secs(period_seconds), - } - } - - pub(crate) fn try_acquire(&mut self) -> bool { - let now = Instant::now(); - - // Check if we need to reset the counter - if now >= self.reset_time { - self.requests_remaining = self.requests_limit; - self.reset_time = now + self.period; - } - - // Try to consume a request - if self.requests_remaining > 0 { - self.requests_remaining -= 1; - true - } else { - false - } - } -} - // Calculate backoff duration for a given retry attempt pub(crate) fn calculate_backoff(attempt: u32, initial_interval: u64) -> Duration { Duration::from_millis(initial_interval * 2u64.pow(attempt - 1)) @@ -177,7 +140,6 @@ pub(crate) fn err_into_response(err: anyhow::Error) -> Result StatusCode::BAD_GATEWAY, ("guard", "request_timeout") => StatusCode::GATEWAY_TIMEOUT, ("guard", "retry_attempts_exceeded") => StatusCode::BAD_GATEWAY, - ("actor", "not_found") => StatusCode::NOT_FOUND, ("guard", "service_unavailable") => StatusCode::SERVICE_UNAVAILABLE, ("guard", "actor_stopped_while_waiting") => StatusCode::SERVICE_UNAVAILABLE, ("guard", "tunnel_request_aborted") => StatusCode::SERVICE_UNAVAILABLE, @@ -188,6 +150,8 @@ pub(crate) fn err_into_response(err: anyhow::Error) -> Result StatusCode::NOT_FOUND, ("guard", "invalid_request_body") => StatusCode::PAYLOAD_TOO_LARGE, ("guard", "invalid_response_body") => StatusCode::BAD_GATEWAY, + ("actor", "creation_rate_limit") => StatusCode::TOO_MANY_REQUESTS, + ("actor", "not_found") => StatusCode::NOT_FOUND, _ => StatusCode::BAD_REQUEST, }; diff --git a/engine/packages/pegboard-gateway2/src/lib.rs b/engine/packages/pegboard-gateway2/src/lib.rs index 4bd33f15cd..e370270334 100644 --- a/engine/packages/pegboard-gateway2/src/lib.rs +++ b/engine/packages/pegboard-gateway2/src/lib.rs @@ -674,6 +674,7 @@ impl PegboardGateway2 { ); let ws_to_tunnel = tokio::spawn( ws_to_tunnel_task::task( + ctx.clone(), in_flight_req.clone(), ws_rx, ingress_bytes.clone(), diff --git a/engine/packages/pegboard-gateway2/src/shared_state.rs b/engine/packages/pegboard-gateway2/src/shared_state.rs index ff870d330d..67ad84a7b8 100644 --- a/engine/packages/pegboard-gateway2/src/shared_state.rs +++ b/engine/packages/pegboard-gateway2/src/shared_state.rs @@ -661,7 +661,7 @@ impl InFlightRequestHandle { // Cap retries so a permanently-gone receiver fails fast instead of pinning the // request forever. Worst-case backoff total is ~19s, which stays under the default // tunnel ping timeout (30s) so the ping path can take over if the receiver is truly lost. - let mut backoff = rivet_util::backoff::Backoff::new(6, Some(8), 100, 5); + let mut backoff = rivet_util::throttle::Backoff::new(6, Some(8), 100, 5); let first_attempt_at = Instant::now(); let mut attempt = 0; loop { diff --git a/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs index 1ebcff0e94..66ac4836d3 100644 --- a/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs @@ -1,11 +1,13 @@ use anyhow::Result; use futures_util::TryStreamExt; +use gas::prelude::*; use rivet_envoy_protocol as protocol; use rivet_guard_core::websocket_handle::WebSocketReceiver; use std::sync::{ Arc, atomic::{AtomicU64, Ordering}, }; +use std::time::Duration; use tokio::sync::{Mutex, watch}; use tokio_tungstenite::tungstenite::Message; @@ -14,6 +16,7 @@ use crate::shared_state::{InFlightRequestHandle, display_id}; #[tracing::instrument(name = "ws_to_tunnel_task", skip_all)] pub async fn task( + ctx: StandaloneCtx, in_flight_req: InFlightRequestHandle, ws_rx: Arc>, ingress_bytes: Arc, @@ -21,7 +24,20 @@ pub async fn task( ) -> Result { let mut ws_rx = ws_rx.lock().await; + // Leaky bucket rate limit on consuming ws messages + let pegboard_config = ctx.config().pegboard(); + let mut rate_limit = rivet_util::throttle::RateLimiter::new( + rivet_util::throttle::RateLimitMethod::LeakyBucket { + requests: pegboard_config.gateway_websocket_rate_limit_requests(), + drip_rate: Duration::from_millis( + pegboard_config.gateway_websocket_rate_limit_drip_rate_ms(), + ), + }, + ); + loop { + rate_limit.acquire().await; + tokio::select! { res = ws_rx.try_next() => { if let Some(msg) = res? { diff --git a/engine/packages/pegboard/Cargo.toml b/engine/packages/pegboard/Cargo.toml index 3d878d6b1d..6e9701246f 100644 --- a/engine/packages/pegboard/Cargo.toml +++ b/engine/packages/pegboard/Cargo.toml @@ -17,6 +17,7 @@ foundationdb-tuple.workspace = true futures-util.workspace = true gas.workspace = true lazy_static.workspace = true +moka.workspace = true namespace.workspace = true nix.workspace = true rand.workspace = true diff --git a/engine/packages/pegboard/src/errors.rs b/engine/packages/pegboard/src/errors.rs index 13e21b55cb..45fb31fbd8 100644 --- a/engine/packages/pegboard/src/errors.rs +++ b/engine/packages/pegboard/src/errors.rs @@ -13,6 +13,12 @@ pub enum Actor { #[error("namespace_not_found", "The namespace does not exist.")] NamespaceNotFound, + #[error( + "creation_rate_limit", + "Too many actors created at once. Try again later." + )] + CreationRateLimit, + #[error( "input_too_large", "Actor input too large.", diff --git a/engine/packages/pegboard/src/ops/actor/create.rs b/engine/packages/pegboard/src/ops/actor/create.rs index c21e878e80..a0cb51bca0 100644 --- a/engine/packages/pegboard/src/ops/actor/create.rs +++ b/engine/packages/pegboard/src/ops/actor/create.rs @@ -1,7 +1,15 @@ use anyhow::{Context, Result}; use gas::prelude::*; +use moka::future::Cache; use rivet_api_util::{Method, request_remote_datacenter}; use rivet_types::actors::{Actor, CrashPolicy}; +use std::sync::{Arc, OnceLock}; +use std::time::Duration; +use tokio::sync::Mutex; + +const RATE_LIMITER_CACHE_TTL: Duration = Duration::from_secs(60 * 60); +static RATE_LIMITERS: OnceLock>>> = + OnceLock::new(); #[derive(Debug)] pub struct Input { @@ -29,6 +37,32 @@ pub struct Output { #[operation] pub async fn pegboard_actor_create(ctx: &OperationCtx, input: &Input) -> Result { + let rate_limiter = RATE_LIMITERS + .get_or_init(|| { + Cache::builder() + .max_capacity(10_000) + .time_to_idle(RATE_LIMITER_CACHE_TTL) + .build() + }) + .entry(input.namespace_id) + .or_insert_with(async { + let pegboard_config = ctx.config().pegboard(); + Arc::new(Mutex::new(rivet_util::throttle::RateLimiter::new( + rivet_util::throttle::RateLimitMethod::LeakyBucket { + requests: pegboard_config.actor_create_rate_limit_requests(), + drip_rate: Duration::from_millis( + pegboard_config.actor_create_rate_limit_drip_rate_ms(), + ), + }, + ))) + }) + .await; + + // Limit actor creation per namespace id + if !rate_limiter.value().lock().await.try_acquire() { + return Err(crate::errors::Actor::CreationRateLimit.build()); + } + // Set up subscriptions before dispatching workflow let ( mut create_sub, diff --git a/engine/packages/pegboard/src/workflows/actor/runtime.rs b/engine/packages/pegboard/src/workflows/actor/runtime.rs index 9965b59d5d..505f0afa81 100644 --- a/engine/packages/pegboard/src/workflows/actor/runtime.rs +++ b/engine/packages/pegboard/src/workflows/actor/runtime.rs @@ -1307,8 +1307,8 @@ fn reschedule_backoff( retry_count: usize, base_retry_timeout: usize, max_exponent: usize, -) -> util::backoff::Backoff { - util::backoff::Backoff::new_at(max_exponent, None, base_retry_timeout, 500, retry_count) +) -> util::throttle::Backoff { + util::throttle::Backoff::new_at(max_exponent, None, base_retry_timeout, 500, retry_count) } #[derive(Debug, Serialize, Deserialize)] diff --git a/engine/packages/pegboard/src/workflows/actor2/runtime.rs b/engine/packages/pegboard/src/workflows/actor2/runtime.rs index e8e1d063e1..3eb8dd9a04 100644 --- a/engine/packages/pegboard/src/workflows/actor2/runtime.rs +++ b/engine/packages/pegboard/src/workflows/actor2/runtime.rs @@ -830,7 +830,7 @@ async fn compare_retry( if reset { state.reschedule_ts = None; } else { - let backoff = util::backoff::Backoff::new_at( + let backoff = util::throttle::Backoff::new_at( ctx.config().pegboard().reschedule_backoff_max_exponent(), None, ctx.config().pegboard().base_retry_timeout(), diff --git a/engine/packages/pegboard/src/workflows/runner_pool_metadata_poller.rs b/engine/packages/pegboard/src/workflows/runner_pool_metadata_poller.rs index bcb600f784..47f5e6e262 100644 --- a/engine/packages/pegboard/src/workflows/runner_pool_metadata_poller.rs +++ b/engine/packages/pegboard/src/workflows/runner_pool_metadata_poller.rs @@ -164,7 +164,7 @@ async fn poll_metadata(ctx: &ActivityCtx, input: &PollMetadataInput) -> Result

util::backoff::Backoff { - util::backoff::Backoff::new_at(max_exponent, None, base_retry_timeout, 500, retry_count) +) -> util::throttle::Backoff { + util::throttle::Backoff::new_at(max_exponent, None, base_retry_timeout, 500, retry_count) } /// Report an error to the error tracker workflow. diff --git a/engine/packages/universalpubsub/src/driver/postgres/mod.rs b/engine/packages/universalpubsub/src/driver/postgres/mod.rs index 3ef8f02beb..52bc219b2a 100644 --- a/engine/packages/universalpubsub/src/driver/postgres/mod.rs +++ b/engine/packages/universalpubsub/src/driver/postgres/mod.rs @@ -5,7 +5,7 @@ use base64::engine::general_purpose::STANDARD_NO_PAD as BASE64; use deadpool_postgres::{Config, ManagerConfig, Pool, PoolConfig, RecyclingMethod, Runtime}; use futures_util::future::poll_fn; use rivet_postgres_util::build_tls_config; -use rivet_util::backoff::Backoff; +use rivet_util::throttle::Backoff; use scc::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; use std::path::PathBuf; diff --git a/engine/packages/universalpubsub/src/pubsub.rs b/engine/packages/universalpubsub/src/pubsub.rs index 1640307e91..d1bc921af0 100644 --- a/engine/packages/universalpubsub/src/pubsub.rs +++ b/engine/packages/universalpubsub/src/pubsub.rs @@ -8,7 +8,7 @@ use scc::HashMap; use tokio::sync::broadcast; use uuid::Uuid; -use rivet_util::backoff::Backoff; +use rivet_util::throttle::Backoff; use crate::chunking::{ChunkTracker, FastPath, encode_chunk, split_payload_into_chunks}; use crate::driver::{PubSubDriverHandle, PublishOpts, SubscriberDriverHandle}; diff --git a/engine/packages/util/src/backoff.rs b/engine/packages/util/src/backoff.rs deleted file mode 100644 index 183f25042e..0000000000 --- a/engine/packages/util/src/backoff.rs +++ /dev/null @@ -1,109 +0,0 @@ -use rand::Rng; -use tokio::time::{Duration, Instant}; - -pub struct Backoff { - /// Maximum exponent for the backoff. - max_exponent: usize, - - /// Maximum amount of retries. - max_retries: Option, - - /// Base wait time in ms. - wait: usize, - - /// Maximum randomness. - randomness: usize, - - /// Iteration of the backoff. - i: usize, - - /// Timestamp to sleep until in ms. - sleep_until: Instant, -} - -impl Backoff { - pub fn new( - max_exponent: usize, - max_retries: Option, - wait: usize, - randomness: usize, - ) -> Backoff { - Backoff { - max_exponent, - max_retries, - wait, - randomness, - i: 0, - sleep_until: Instant::now(), - } - } - - pub fn new_at( - max_exponent: usize, - max_retries: Option, - wait: usize, - randomness: usize, - i: usize, - ) -> Backoff { - Backoff { - max_exponent, - max_retries, - wait, - randomness, - i, - sleep_until: Instant::now(), - } - } - - pub fn tick_index(&self) -> usize { - self.i - } - - /// Waits for the next backoff tick. - /// - /// Returns false if the index is greater than `max_retries`. - pub async fn tick(&mut self) -> bool { - if self.max_retries.map_or(false, |x| self.i > x) { - return false; - } - - tokio::time::sleep_until(self.sleep_until).await; - - let next_wait = self.current_duration() + rand::thread_rng().gen_range(0..self.randomness); - self.sleep_until += Duration::from_millis(next_wait as u64); - - self.i += 1; - - true - } - - /// Returns the instant of the next backoff tick. Does not wait. - /// - /// Returns None if the index is greater than `max_retries`. - pub fn step(&mut self) -> Option { - if self.max_retries.map_or(false, |x| self.i > x) { - return None; - } - - let next_wait = self.current_duration() + rand::thread_rng().gen_range(0..self.randomness); - self.sleep_until += Duration::from_millis(next_wait as u64); - - self.i += 1; - - Some(self.sleep_until) - } - - pub fn current_duration(&self) -> usize { - self.wait * 2usize.pow(self.i.min(self.max_exponent) as u32) - } - - pub fn default_infinite() -> Backoff { - Backoff::new(8, None, 1_000, 1_000) - } -} - -impl Default for Backoff { - fn default() -> Backoff { - Backoff::new(5, Some(16), 1_000, 1_000) - } -} diff --git a/engine/packages/util/src/lib.rs b/engine/packages/util/src/lib.rs index 9b77b4b487..0c088b9df8 100644 --- a/engine/packages/util/src/lib.rs +++ b/engine/packages/util/src/lib.rs @@ -4,7 +4,6 @@ pub use id::Id; pub use rivet_util_id as id; pub mod async_counter; -pub mod backoff; pub mod billing; pub mod build_meta; pub mod check; @@ -19,6 +18,7 @@ pub mod req; pub mod serde; pub mod size; pub mod sort; +pub mod throttle; pub mod timestamp; pub mod url; diff --git a/engine/packages/util/src/throttle.rs b/engine/packages/util/src/throttle.rs new file mode 100644 index 0000000000..38295f99f6 --- /dev/null +++ b/engine/packages/util/src/throttle.rs @@ -0,0 +1,487 @@ +use rand::Rng; +use tokio::time::{Duration, Instant}; + +pub struct Backoff { + /// Maximum exponent for the backoff. + max_exponent: usize, + + /// Maximum amount of retries. + max_retries: Option, + + /// Base wait time in ms. + wait: usize, + + /// Maximum randomness. + randomness: usize, + + /// Iteration of the backoff. + i: usize, + + /// Timestamp to sleep until in ms. + sleep_until: Instant, +} + +impl Backoff { + pub fn new( + max_exponent: usize, + max_retries: Option, + wait: usize, + randomness: usize, + ) -> Backoff { + Backoff { + max_exponent, + max_retries, + wait, + randomness, + i: 0, + sleep_until: Instant::now(), + } + } + + pub fn new_at( + max_exponent: usize, + max_retries: Option, + wait: usize, + randomness: usize, + i: usize, + ) -> Backoff { + Backoff { + max_exponent, + max_retries, + wait, + randomness, + i, + sleep_until: Instant::now(), + } + } + + pub fn tick_index(&self) -> usize { + self.i + } + + /// Waits for the next backoff tick. + /// + /// Returns false if the index is greater than `max_retries`. + pub async fn tick(&mut self) -> bool { + if self.max_retries.map_or(false, |x| self.i > x) { + return false; + } + + tokio::time::sleep_until(self.sleep_until).await; + + let next_wait = self.current_duration() + rand::thread_rng().gen_range(0..self.randomness); + self.sleep_until += Duration::from_millis(next_wait as u64); + + self.i += 1; + + true + } + + /// Returns the instant of the next backoff tick. Does not wait. + /// + /// Returns None if the index is greater than `max_retries`. + pub fn step(&mut self) -> Option { + if self.max_retries.map_or(false, |x| self.i > x) { + return None; + } + + let next_wait = self.current_duration() + rand::thread_rng().gen_range(0..self.randomness); + self.sleep_until += Duration::from_millis(next_wait as u64); + + self.i += 1; + + Some(self.sleep_until) + } + + pub fn current_duration(&self) -> usize { + self.wait * 2usize.pow(self.i.min(self.max_exponent) as u32) + } + + pub fn default_infinite() -> Backoff { + Backoff::new(8, None, 1_000, 1_000) + } +} + +impl Default for Backoff { + fn default() -> Backoff { + Backoff::new(5, Some(16), 1_000, 1_000) + } +} + +pub enum RateLimitMethod { + FixedWindow { + requests: u64, + period: Duration, + }, + LeakyBucket { + requests: u64, + /// How quickly to regain requests. 1 / drip_rate + drip_rate: Duration, + }, +} + +enum RateLimitState { + FixedWindow { + requests_remaining: u64, + requests_limit: u64, + reset_time: Instant, + period: Duration, + }, + LeakyBucket { + requests_remaining: u64, + requests_limit: u64, + last_acquire: Instant, + drip_rate: Duration, + accum_drip: f32, + }, +} + +pub struct RateLimiter { + state: RateLimitState, +} + +impl RateLimiter { + pub fn new(method: RateLimitMethod) -> Self { + Self { + state: match method { + RateLimitMethod::FixedWindow { requests, period } => RateLimitState::FixedWindow { + requests_remaining: requests, + requests_limit: requests, + reset_time: Instant::now() + period, + period, + }, + RateLimitMethod::LeakyBucket { + requests, + drip_rate, + } => RateLimitState::LeakyBucket { + requests_remaining: requests, + requests_limit: requests, + last_acquire: Instant::now(), + drip_rate: drip_rate, + accum_drip: 0.0, + }, + }, + } + } + + pub fn try_acquire(&mut self) -> bool { + match &mut self.state { + RateLimitState::FixedWindow { + requests_remaining, + requests_limit, + reset_time, + period, + } => { + let now = Instant::now(); + // Check if we need to reset the counter + if now >= *reset_time { + *requests_remaining = *requests_limit; + *reset_time = now + *period; + } + + // Try to consume a request + if *requests_remaining > 0 { + *requests_remaining -= 1; + true + } else { + false + } + } + RateLimitState::LeakyBucket { + requests_remaining, + requests_limit, + last_acquire, + drip_rate, + accum_drip, + } => { + let now = Instant::now(); + let dt = now - *last_acquire; + *last_acquire = now; + + // Drip bucket + if requests_remaining < requests_limit { + *accum_drip += dt.div_duration_f32(*drip_rate); + + *requests_remaining += + (*accum_drip as u64).min(*requests_limit - *requests_remaining); + + if *accum_drip >= 1.0 { + *accum_drip = accum_drip.fract(); + } + } + + if *requests_remaining > 0 { + *requests_remaining -= 1; + true + } else { + false + } + } + } + } + + pub async fn acquire(&mut self) { + match &mut self.state { + RateLimitState::FixedWindow { + requests_remaining, + requests_limit, + reset_time, + period, + } => { + let now = Instant::now(); + // Check if we need to reset the counter + if now >= *reset_time { + *requests_remaining = *requests_limit; + *reset_time = now + *period; + } + + // Try to consume a request + if *requests_remaining > 0 { + *requests_remaining -= 1; + } else { + tokio::time::sleep(*period).await; + + *requests_remaining = *requests_limit; + *reset_time = Instant::now() + *period; + } + } + RateLimitState::LeakyBucket { + requests_remaining, + requests_limit, + last_acquire, + drip_rate, + accum_drip, + } => { + let now = Instant::now(); + let dt = now - *last_acquire; + *last_acquire = now; + + // Drip bucket + if requests_remaining < requests_limit { + *accum_drip += dt.div_duration_f32(*drip_rate); + + *requests_remaining += + (*accum_drip as u64).min(*requests_limit - *requests_remaining); + + if *accum_drip >= 1.0 { + *accum_drip = accum_drip.fract(); + } + } + + if *requests_remaining > 0 { + *requests_remaining -= 1; + } else { + let deficit = 1.0 - *accum_drip; + tokio::time::sleep(drip_rate.mul_f32(deficit)).await; + + *last_acquire = Instant::now(); + *accum_drip = 0.0; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{RateLimitMethod, RateLimiter}; + use tokio::time::{Duration, Instant}; + + // MARK: FixedWindow / try_acquire + + #[tokio::test(start_paused = true)] + async fn fixed_window_allows_full_burst_then_blocks() { + let mut rl = RateLimiter::new(RateLimitMethod::FixedWindow { + requests: 3, + period: Duration::from_millis(100), + }); + + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + // Limit reached within the window. + assert!(!rl.try_acquire()); + } + + #[tokio::test(start_paused = true)] + async fn fixed_window_does_not_refill_before_period() { + let mut rl = RateLimiter::new(RateLimitMethod::FixedWindow { + requests: 2, + period: Duration::from_millis(100), + }); + + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + + // Just shy of a full period: still no refill. The window is + // all-or-nothing, it does not drip partial credit. + tokio::time::advance(Duration::from_millis(99)).await; + assert!(!rl.try_acquire()); + } + + #[tokio::test(start_paused = true)] + async fn fixed_window_resets_to_full_after_period() { + let mut rl = RateLimiter::new(RateLimitMethod::FixedWindow { + requests: 2, + period: Duration::from_millis(100), + }); + + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + + // After a full period the window resets to its full allowance. + tokio::time::advance(Duration::from_millis(100)).await; + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + } + + // MARK: LeakyBucket / try_acquire + + #[tokio::test(start_paused = true)] + async fn leaky_bucket_allows_full_burst_then_blocks() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 3, + drip_rate: Duration::from_millis(10), + }); + + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + } + + #[tokio::test(start_paused = true)] + async fn leaky_bucket_drips_exactly_one_token_per_rate() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 3, + drip_rate: Duration::from_millis(10), + }); + + // Drain the bucket. + for _ in 0..3 { + assert!(rl.try_acquire()); + } + assert!(!rl.try_acquire()); + + // Exactly one drip period yields exactly one token, no more. + tokio::time::advance(Duration::from_millis(10)).await; + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + } + + #[tokio::test(start_paused = true)] + async fn leaky_bucket_refill_is_capped_at_capacity() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 3, + drip_rate: Duration::from_millis(10), + }); + + for _ in 0..3 { + assert!(rl.try_acquire()); + } + assert!(!rl.try_acquire()); + + // Idle far longer than it takes to refill the whole bucket. Credit must + // not accumulate past capacity, so only `requests` tokens are available. + tokio::time::advance(Duration::from_millis(1_000)).await; + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + } + + #[tokio::test(start_paused = true)] + async fn leaky_bucket_accumulates_fractional_drip_across_calls() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 1, + drip_rate: Duration::from_millis(10), + }); + + // Consume the only token. + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + + // Half a drip period: less than one whole token, still blocked. + tokio::time::advance(Duration::from_millis(5)).await; + assert!(!rl.try_acquire()); + + // Another half period: the fractional credit from the previous interval + // must carry over and complete one whole token. + tokio::time::advance(Duration::from_millis(5)).await; + assert!(rl.try_acquire()); + assert!(!rl.try_acquire()); + } + + // MARK: acquire (blocking) + + #[tokio::test(start_paused = true)] + async fn acquire_returns_immediately_while_tokens_remain() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 3, + drip_rate: Duration::from_millis(10), + }); + + let start = Instant::now(); + rl.acquire().await; + rl.acquire().await; + rl.acquire().await; + // Burst is served without waiting. + assert_eq!(start.elapsed(), Duration::ZERO); + } + + #[tokio::test(start_paused = true)] + async fn acquire_blocks_until_a_token_is_available() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 1, + drip_rate: Duration::from_millis(10), + }); + + // Drain the single token. + rl.acquire().await; + + // The next acquire must wait one full drip period for a token. + let start = Instant::now(); + rl.acquire().await; + assert!(start.elapsed() >= Duration::from_millis(10)); + } + + #[tokio::test(start_paused = true)] + async fn acquire_sustains_the_drip_rate_without_doubling() { + let mut rl = RateLimiter::new(RateLimitMethod::LeakyBucket { + requests: 1, + drip_rate: Duration::from_millis(10), + }); + + // Drain the initial burst token so every subsequent acquire starts empty. + rl.acquire().await; + + let start = Instant::now(); + // Five acquires, each starting from an empty bucket, must each cost one + // drip period, so the total is at least 5 * drip_rate. A limiter that + // admits the post-sleep request without debiting a token finishes in + // ~3 periods, effectively doubling the sustained rate. + for _ in 0..5 { + rl.acquire().await; + } + assert!(start.elapsed() >= Duration::from_millis(50)); + } + + #[tokio::test(start_paused = true)] + async fn fixed_window_acquire_blocks_until_window_resets() { + let mut rl = RateLimiter::new(RateLimitMethod::FixedWindow { + requests: 2, + period: Duration::from_millis(100), + }); + + rl.acquire().await; + rl.acquire().await; + + // The window is exhausted, so the next acquire must wait for the reset. + let start = Instant::now(); + rl.acquire().await; + assert!(start.elapsed() >= Duration::from_millis(100)); + } +} From 5c7f16e8623708127a4e1b9706745e0eb9a73d95 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Wed, 24 Jun 2026 12:00:17 -0700 Subject: [PATCH 07/19] [SLOP(claude-opus-4-8)] feat(pegboard-envoy): rate-limit envoy ws ingress and cap get_pages, trim unused metrics --- Cargo.lock | 1 + engine/artifacts/config-schema.json | 54 +++++++++++++++ engine/packages/config/src/config/pegboard.rs | 16 +++++ engine/packages/guard/src/routing/envoy.rs | 2 +- engine/packages/pegboard-envoy/Cargo.toml | 1 + engine/packages/pegboard-envoy/src/lib.rs | 8 +-- engine/packages/pegboard-envoy/src/metrics.rs | 69 ------------------- .../pegboard-envoy/src/ws_to_tunnel_task.rs | 35 +++++++++- .../packages/pegboard-gateway2/src/metrics.rs | 39 ----------- .../pegboard-gateway2/src/shared_state.rs | 1 - .../src/ws_to_tunnel_task.rs | 17 +++-- engine/packages/pegboard-outbound/src/lib.rs | 2 - .../packages/pegboard-outbound/src/metrics.rs | 47 ------------- .../packages/pegboard/src/ops/actor/create.rs | 4 +- 14 files changed, 122 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72af92dd43..b21a5d2859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4011,6 +4011,7 @@ dependencies = [ "rivet-pools", "rivet-runtime", "rivet-types", + "rivet-util", "rusqlite", "scc", "serde", diff --git a/engine/artifacts/config-schema.json b/engine/artifacts/config-schema.json index ead08e9e6f..998771acc1 100644 --- a/engine/artifacts/config-schema.json +++ b/engine/artifacts/config-schema.json @@ -855,6 +855,24 @@ ], "format": "int64" }, + "actor_create_rate_limit_drip_rate_ms": { + "description": "Time to regain one actor creation token per namespace.\n\nUnit is in milliseconds.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "actor_create_rate_limit_requests": { + "description": "Max burst of actor creations per namespace before throttling.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, "actor_retry_duration_threshold": { "description": "How long to wait after starting to attempt to reallocate before before setting actor to sleep.\n\nUnit is in milliseconds.", "type": [ @@ -986,6 +1004,24 @@ "format": "uint64", "minimum": 0.0 }, + "envoy_websocket_rate_limit_drip_rate_us": { + "description": "Time to regain one inbound WebSocket message token on a single envoy connection.\n\nUnit is in microseconds. The envoy connection multiplexes every actor on a runner, so the sustained ceiling is far higher than the per-client gateway limit and needs sub-millisecond granularity to express.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "envoy_websocket_rate_limit_requests": { + "description": "Max burst of inbound WebSocket messages on a single envoy connection before throttling.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, "gateway_gc_interval_ms": { "description": "GC interval for in-flight requests in milliseconds.", "type": [ @@ -1057,6 +1093,24 @@ "format": "uint64", "minimum": 0.0 }, + "gateway_websocket_rate_limit_drip_rate_ms": { + "description": "Time to regain one inbound WebSocket message token on a single connection.\n\nUnit is in milliseconds.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "gateway_websocket_rate_limit_requests": { + "description": "Max burst of inbound WebSocket messages on a single connection before throttling.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, "hibernating_request_eligible_threshold": { "description": "How long after last ping before considering a hibernating request disconnected.\n\nUnit is in milliseconds.", "type": [ diff --git a/engine/packages/config/src/config/pegboard.rs b/engine/packages/config/src/config/pegboard.rs index f05a98319f..f0b660401e 100644 --- a/engine/packages/config/src/config/pegboard.rs +++ b/engine/packages/config/src/config/pegboard.rs @@ -149,6 +149,14 @@ pub struct Pegboard { pub envoy_expire_scheduler_max_concurrent_expires: Option, /// Maximum pending envoys tracked by the read-path envoy expire scheduler. pub envoy_expire_scheduler_max_pending: Option, + /// Max burst of inbound WebSocket messages on a single envoy connection before throttling. + pub envoy_websocket_rate_limit_requests: Option, + /// Time to regain one inbound WebSocket message token on a single envoy connection. + /// + /// Unit is in microseconds. The envoy connection multiplexes every actor on a runner, so the + /// sustained ceiling is far higher than the per-client gateway limit and needs sub-millisecond + /// granularity to express. + pub envoy_websocket_rate_limit_drip_rate_us: Option, // === Serverless Settings === /// **Deprecated** Configure the drain period in the runner config. @@ -391,6 +399,14 @@ impl Pegboard { self.gateway_websocket_rate_limit_drip_rate_ms.unwrap_or(10) } + pub fn envoy_websocket_rate_limit_requests(&self) -> u64 { + self.envoy_websocket_rate_limit_requests.unwrap_or(16_384) + } + + pub fn envoy_websocket_rate_limit_drip_rate_us(&self) -> u64 { + self.envoy_websocket_rate_limit_drip_rate_us.unwrap_or(200) + } + pub fn actor_create_rate_limit_requests(&self) -> u64 { self.actor_create_rate_limit_requests.unwrap_or(500) } diff --git a/engine/packages/guard/src/routing/envoy.rs b/engine/packages/guard/src/routing/envoy.rs index f2f6ae6ea7..c7895068bf 100644 --- a/engine/packages/guard/src/routing/envoy.rs +++ b/engine/packages/guard/src/routing/envoy.rs @@ -93,6 +93,6 @@ async fn route_envoy_internal( tracing::debug!("authenticated envoy connection"); } - let tunnel = pegboard_envoy::PegboardEnvoyWs::new(ctx.clone()); + let tunnel = pegboard_envoy::PegboardEnvoyWs::new(&ctx); Ok(RoutingOutput::CustomServe(Arc::new(tunnel))) } diff --git a/engine/packages/pegboard-envoy/Cargo.toml b/engine/packages/pegboard-envoy/Cargo.toml index 29ad9a85be..4d7f5f54ab 100644 --- a/engine/packages/pegboard-envoy/Cargo.toml +++ b/engine/packages/pegboard-envoy/Cargo.toml @@ -32,6 +32,7 @@ depot-client.workspace = true depot-client-embedded.workspace = true rivet-runtime.workspace = true rivet-types.workspace = true +rivet-util.workspace = true scc.workspace = true serde_bare.workspace = true serde_json.workspace = true diff --git a/engine/packages/pegboard-envoy/src/lib.rs b/engine/packages/pegboard-envoy/src/lib.rs index bcb5d2c77d..99ebe04cc7 100644 --- a/engine/packages/pegboard-envoy/src/lib.rs +++ b/engine/packages/pegboard-envoy/src/lib.rs @@ -45,12 +45,8 @@ pub struct PegboardEnvoyWs { } impl PegboardEnvoyWs { - pub fn new(ctx: StandaloneCtx) -> Self { - metrics::prepopulate(); - - let service = Self { ctx: ctx.clone() }; - - service + pub fn new(ctx: &StandaloneCtx) -> Self { + Self { ctx: ctx.clone() } } } diff --git a/engine/packages/pegboard-envoy/src/metrics.rs b/engine/packages/pegboard-envoy/src/metrics.rs index d44f177081..ad8303ac40 100644 --- a/engine/packages/pegboard-envoy/src/metrics.rs +++ b/engine/packages/pegboard-envoy/src/metrics.rs @@ -371,72 +371,3 @@ pub fn set_envoy_connection_state( (None, None) => {} } } - -pub fn prepopulate() { - ENVOY_CONNECTED.with_label_values(&["", ""]).set(0); - for state in EnvoyState::ALL { - ENVOY_CONNECTIONS_BY_STATE - .with_label_values(&["", "", "", state.as_str()]) - .set(0); - } - for (state, reasons) in [ - (EnvoyState::Starting, &["websocket_accepted"][..]), - (EnvoyState::Connected, &["init_complete"][..]), - (EnvoyState::Stopping, &["envoy_reported_stopping"][..]), - ( - EnvoyState::Disconnected, - &[ - "init_failed", - "websocket_closed", - "evicted", - "going_away", - "connection_error", - ][..], - ), - (EnvoyState::Lost, &["ping_timeout"][..]), - (EnvoyState::Stopped, &["graceful_shutdown_complete"][..]), - ] { - for reason in reasons { - ENVOY_STATE_TRANSITION_TOTAL - .with_label_values(&["", "", "", state.as_str(), reason]) - .inc_by(0); - } - } - let _ = ENVOY_LIFETIME_SECONDS.with_label_values(&["", ""]); - let _ = ENVOY_PING_LAG_SECONDS.with_label_values(&["", ""]); - for result in ["ok", "no_subscribers", "error"] { - TUNNEL_PUBLISH_TOTAL - .with_label_values(&["", "", result]) - .inc_by(0); - } - TUNNEL_TASKS_ACTIVE.with_label_values(&["", ""]).set(0); - WS_RESPONSES_IN_FLIGHT.set(0); - for task_kind in ["kv", "sqlite_page", "remote_sqlite", "tunnel_message"] { - ACTOR_TASKS_ACTIVE.with_label_values(&[task_kind]).set(0); - } - for branch in ["ws_msg", "completed_task"] { - let _ = WS_TO_TUNNEL_BRANCH_DURATION.with_label_values(&[branch]); - } - for result in ["ok", "error", "timeout"] { - let _ = ACTOR_WAKE_DURATION.with_label_values(&["", "", result]); - } - let _ = SQLITE_COMMIT_ENVOY_DISPATCH_DURATION.with_label_values(&["", ""]); - let _ = SQLITE_COMMIT_ENVOY_RESPONSE_DURATION.with_label_values(&["", ""]); - for request_type in ["get_pages", "commit", "exec", "execute"] { - for result in ["ok", "error"] { - SQLITE_REQUEST_TOTAL - .with_label_values(&["", "", request_type, result]) - .inc_by(0); - let _ = SQLITE_REQUEST_DURATION.with_label_values(&["", "", request_type, result]); - } - for direction in ["request", "response"] { - let _ = SQLITE_REQUEST_PAGES.with_label_values(&["", "", request_type, direction]); - } - let _ = SQLITE_REQUEST_DIRTY_PAGES.with_label_values(&["", "", request_type]); - for direction in ["request", "response"] { - SQLITE_REQUEST_PAYLOAD_BYTES - .with_label_values(&["", "", request_type, direction]) - .inc_by(0); - } - } -} diff --git a/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs index 785daf8c41..fcbe313453 100644 --- a/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs @@ -45,6 +45,13 @@ use crate::{ const MAX_REMOTE_SQL_BIND_BYTES: usize = 128 * 1024; +/// Max number of pages a single `get_pages` request may ask for. Each requested page number is ~4 +/// bytes on the wire but forces the engine to fetch and materialize up to a full 4 KiB page inside +/// one UDB transaction, so an uncapped list is a large cost-asymmetry amplifier from an untrusted +/// runner. Sized well above observed production read batches (max ~1024 pages); the commit path has +/// its own lower cap (`MAX_COMMIT_DIRTY_PAGES`) since writes batch smaller than reads. +const MAX_GET_PAGES_PER_REQUEST: usize = 8192; + /// Wall-clock threshold above which a single handle_message invocation is logged as a head-of-line /// blocking risk. The ws_to_tunnel_task loop is strictly serial per envoy, so any handler that /// spends longer than this delays every subsequent WS message from the same envoy (including @@ -461,9 +468,25 @@ pub async fn task_inner( let mut term_signal = rivet_runtime::TermSignal::get(); let mut task_manager = TaskManager::new(ctx.clone(), conn.clone()); + // Leaky bucket rate limit on consuming envoy ws messages. The envoy connection multiplexes + // every actor on a runner, so this bounds the rate at which a single untrusted runner can drive + // engine work (task spawns, KV/SQLite ops). Reads are paused while empty, applying TCP + // backpressure to the runner rather than dropping protocol messages. + let mut rate_limit = rivet_util::throttle::RateLimiter::new( + rivet_util::throttle::RateLimitMethod::LeakyBucket { + requests: ctx.config().pegboard().envoy_websocket_rate_limit_requests(), + drip_rate: Duration::from_micros( + ctx.config().pegboard().envoy_websocket_rate_limit_drip_rate_us(), + ), + }, + ); + loop { tokio::select! { - recv = recv_msg(&mut ws_rx, &mut ws_to_tunnel_abort_rx, &mut term_signal) => { + recv = async { + rate_limit.acquire().await; + recv_msg(&mut ws_rx, &mut ws_to_tunnel_abort_rx, &mut term_signal).await + } => { let branch_start = Instant::now(); let branch_result: Result> = async { match recv? { @@ -1461,6 +1484,16 @@ async fn handle_sqlite_get_pages( conn: &Conn, request: protocol::SqliteGetPagesRequest, ) -> Result { + if request.pgnos.len() > MAX_GET_PAGES_PER_REQUEST { + return Ok(protocol::SqliteGetPagesResponse::SqliteErrorResponse( + sqlite_protocol_error_response(&format!( + "sqlite get_pages requested {} pages, exceeding limit {}", + request.pgnos.len(), + MAX_GET_PAGES_PER_REQUEST, + )), + )); + } + validate_sqlite_actor_for_request(ctx, conn, &request.actor_id, request.expected_generation) .await?; diff --git a/engine/packages/pegboard-gateway2/src/metrics.rs b/engine/packages/pegboard-gateway2/src/metrics.rs index e9c7a4acfe..244df7f882 100644 --- a/engine/packages/pegboard-gateway2/src/metrics.rs +++ b/engine/packages/pegboard-gateway2/src/metrics.rs @@ -66,42 +66,3 @@ lazy_static::lazy_static! { *REGISTRY ).unwrap(); } - -pub fn prepopulate() { - const RESULTS: &[&str] = &[ - "success", - "client_disconnect", - "actor_ready_timeout", - "request_timeout", - "envoy_error", - ]; - - for protocol in ["http", "websocket"] { - IN_FLIGHT.with_label_values(&["", "", protocol]).set(0); - IN_FLIGHT_DROPPED_TOTAL - .with_label_values(&["", "", protocol, "client_disconnect"]) - .inc_by(0); - TUNNEL_PING_DURATION.with_label_values(&["", "", protocol]); - LAST_PONG_AGE_SECONDS.with_label_values(&["", "", protocol]); - REQUEST_RETRIES_TOTAL.with_label_values(&["", "", protocol, "1"]); - - for result in RESULTS { - REQUEST_DURATION_SECONDS.with_label_values(&["", "", protocol, result]); - } - - for reason in [ - "server_close", - "client_close", - "abort", - "gc_timeout", - "shutdown", - ] { - CLOSE_SENT_TOTAL - .with_label_values(&["", "", protocol, reason]) - .inc_by(0); - } - } - for result in ["ok", "error", "timeout"] { - WEBSOCKET_OPEN_WAIT_SECONDS.with_label_values(&["", "", result]); - } -} diff --git a/engine/packages/pegboard-gateway2/src/shared_state.rs b/engine/packages/pegboard-gateway2/src/shared_state.rs index 67ad84a7b8..b3de6e5887 100644 --- a/engine/packages/pegboard-gateway2/src/shared_state.rs +++ b/engine/packages/pegboard-gateway2/src/shared_state.rs @@ -160,7 +160,6 @@ pub struct SharedState(Arc); impl SharedState { pub fn new(config: &rivet_config::Config, ups: PubSub) -> Self { - metrics::prepopulate(); init_slow_ping_threshold_from_env(); let gateway_id = protocol::util::generate_gateway_id(); diff --git a/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs index 66ac4836d3..27dec6914c 100644 --- a/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs @@ -25,21 +25,26 @@ pub async fn task( let mut ws_rx = ws_rx.lock().await; // Leaky bucket rate limit on consuming ws messages - let pegboard_config = ctx.config().pegboard(); let mut rate_limit = rivet_util::throttle::RateLimiter::new( rivet_util::throttle::RateLimitMethod::LeakyBucket { - requests: pegboard_config.gateway_websocket_rate_limit_requests(), + requests: ctx + .config() + .pegboard() + .gateway_websocket_rate_limit_requests(), drip_rate: Duration::from_millis( - pegboard_config.gateway_websocket_rate_limit_drip_rate_ms(), + ctx.config() + .pegboard() + .gateway_websocket_rate_limit_drip_rate_ms(), ), }, ); loop { - rate_limit.acquire().await; - tokio::select! { - res = ws_rx.try_next() => { + res = async { + rate_limit.acquire().await; + ws_rx.try_next().await + } => { if let Some(msg) = res? { ingress_bytes.fetch_add(msg.len() as u64, Ordering::AcqRel); diff --git a/engine/packages/pegboard-outbound/src/lib.rs b/engine/packages/pegboard-outbound/src/lib.rs index 5d3f3a325d..00697ea7b0 100644 --- a/engine/packages/pegboard-outbound/src/lib.rs +++ b/engine/packages/pegboard-outbound/src/lib.rs @@ -25,8 +25,6 @@ const SSE_OPEN_WARN_THRESHOLD: Duration = Duration::from_secs(5); #[tracing::instrument(skip_all)] pub async fn start(config: rivet_config::Config, pools: rivet_pools::Pools) -> Result<()> { - metrics::prepopulate(); - let cache = rivet_cache::CacheInner::from_env(&config, pools.clone())?; let ctx = StandaloneCtx::new( db::DatabaseKv::new(config.clone(), pools.clone()).await?, diff --git a/engine/packages/pegboard-outbound/src/metrics.rs b/engine/packages/pegboard-outbound/src/metrics.rs index 3f74c822e7..a61cd4fefc 100644 --- a/engine/packages/pegboard-outbound/src/metrics.rs +++ b/engine/packages/pegboard-outbound/src/metrics.rs @@ -42,50 +42,3 @@ lazy_static::lazy_static! { *REGISTRY ).unwrap(); } - -pub fn prepopulate() { - const ERRORS: &[&str] = &[ - "http_error", - "connection_error", - "stream_ended_early", - "invalid_payload", - "downgrade", - "internal", - ]; - const STATUSES: &[&str] = &["429", "503", "5xx", "4xx", "2xx", "other", ""]; - const RESULTS: &[&str] = &[ - "success", - "error_http_429", - "error_http_503", - "error_http_5xx", - "error_http_4xx", - "error_http_other", - "error_connection", - "error_stream_ended", - "error_invalid_payload", - "error_downgrade", - "error_internal", - ]; - const DRAIN_REASONS: &[&str] = &[ - "lifespan_reached", - "going_away", - "actor_lost", - "connection_lost", - "term_signal", - "", - ]; - - for error in ERRORS { - for status in STATUSES { - REQ_ERROR_TOTAL - .with_label_values(&["", "", error, status]) - .inc_by(0); - } - } - - for result in RESULTS { - for drain_reason in DRAIN_REASONS { - REQ_DURATION_SECONDS.with_label_values(&["", "", result, drain_reason]); - } - } -} diff --git a/engine/packages/pegboard/src/ops/actor/create.rs b/engine/packages/pegboard/src/ops/actor/create.rs index a0cb51bca0..b2695c16d9 100644 --- a/engine/packages/pegboard/src/ops/actor/create.rs +++ b/engine/packages/pegboard/src/ops/actor/create.rs @@ -37,7 +37,7 @@ pub struct Output { #[operation] pub async fn pegboard_actor_create(ctx: &OperationCtx, input: &Input) -> Result { - let rate_limiter = RATE_LIMITERS + let rate_limit = RATE_LIMITERS .get_or_init(|| { Cache::builder() .max_capacity(10_000) @@ -59,7 +59,7 @@ pub async fn pegboard_actor_create(ctx: &OperationCtx, input: &Input) -> Result< .await; // Limit actor creation per namespace id - if !rate_limiter.value().lock().await.try_acquire() { + if !rate_limit.value().lock().await.try_acquire() { return Err(crate::errors::Actor::CreationRateLimit.build()); } From 3063bd1b112995653525d469b10b55be044de3d7 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Wed, 24 Jun 2026 15:50:19 -0700 Subject: [PATCH 08/19] [SLOP(claude-opus-4-8-high)] feat(universaldb): postgres leader-resolver driver overhaul --- Cargo.lock | 17 + Cargo.toml | 4 + .../packages/test-deps-docker/src/database.rs | 2 +- engine/packages/universaldb/Cargo.toml | 5 + ...onflict_tracker.rs => conflict_tracker.rs} | 20 +- .../universaldb/src/driver/postgres/codec.rs | 167 +++++++++ .../universaldb/src/driver/postgres/commit.rs | 185 ++++++++++ .../src/driver/postgres/database.rs | 241 +++++++------ .../src/driver/postgres/listener.rs | 228 ++++++++++++ .../universaldb/src/driver/postgres/mod.rs | 5 + .../src/driver/postgres/resolver/apply.rs | 139 ++++++++ .../src/driver/postgres/resolver/lease.rs | 82 +++++ .../src/driver/postgres/resolver/mod.rs | 336 ++++++++++++++++++ .../universaldb/src/driver/postgres/shared.rs | 161 +++++++++ .../src/driver/postgres/transaction.rs | 22 +- .../src/driver/postgres/transaction_task.rs | 283 +++------------ .../src/driver/rocksdb/database.rs | 6 +- .../universaldb/src/driver/rocksdb/mod.rs | 1 - .../src/driver/rocksdb/transaction.rs | 7 +- .../src/driver/rocksdb/transaction_task.rs | 6 +- engine/packages/universaldb/src/lib.rs | 1 + .../packages/universaldb/tests/integration.rs | 10 +- engine/sdks/rust/depot-protocol/Cargo.toml | 1 + .../sdks/rust/universaldb-commit/Cargo.toml | 16 + engine/sdks/rust/universaldb-commit/build.rs | 64 ++++ .../rust/universaldb-commit/src/generated.rs | 1 + .../sdks/rust/universaldb-commit/src/lib.rs | 6 + .../rust/universaldb-commit/src/versioned.rs | 38 ++ .../sdks/schemas/universaldb-commit/v1.bare | 70 ++++ scripts/run/postgres.sh | 2 +- scripts/run/restore-postgres.sh | 2 +- self-host/compose/dev-host/docker-compose.yml | 2 +- .../dev-multidc-multinode/docker-compose.yml | 6 +- .../compose/dev-multidc/docker-compose.yml | 6 +- .../compose/dev-multinode/docker-compose.yml | 2 +- self-host/compose/dev/docker-compose.yml | 2 +- .../compose/template/src/docker-compose.ts | 2 +- .../k8s/engine/12-postgres-statefulset.yaml | 2 +- .../docs/self-hosting/docker-compose.mdx | 2 +- .../docs/self-hosting/docker-container.mdx | 2 +- 40 files changed, 1747 insertions(+), 407 deletions(-) rename engine/packages/universaldb/src/{driver/rocksdb/transaction_conflict_tracker.rs => conflict_tracker.rs} (69%) create mode 100644 engine/packages/universaldb/src/driver/postgres/codec.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/commit.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/listener.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/resolver/apply.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/resolver/lease.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/resolver/mod.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/shared.rs create mode 100644 engine/sdks/rust/universaldb-commit/Cargo.toml create mode 100644 engine/sdks/rust/universaldb-commit/build.rs create mode 100644 engine/sdks/rust/universaldb-commit/src/generated.rs create mode 100644 engine/sdks/rust/universaldb-commit/src/lib.rs create mode 100644 engine/sdks/rust/universaldb-commit/src/versioned.rs create mode 100644 engine/sdks/schemas/universaldb-commit/v1.bare diff --git a/Cargo.lock b/Cargo.lock index b21a5d2859..7b0053c042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5385,6 +5385,7 @@ name = "rivet-depot-protocol" version = "2.3.7" dependencies = [ "anyhow", + "rivet-util", "rivet-vbare-compiler", "serde", "serde_bare", @@ -5946,6 +5947,17 @@ dependencies = [ "vbare", ] +[[package]] +name = "rivet-universaldb-commit" +version = "2.3.2" +dependencies = [ + "anyhow", + "rivet-vbare-compiler", + "serde", + "serde_bare", + "vbare", +] + [[package]] name = "rivet-ups-broadcast" version = "0.1.0" @@ -8254,6 +8266,7 @@ version = "2.3.7" dependencies = [ "anyhow", "async-trait", + "base64 0.22.1", "deadpool-postgres", "foundationdb-tuple", "futures-util", @@ -8267,17 +8280,21 @@ dependencies = [ "rivet-postgres-util", "rivet-test-deps-docker", "rivet-tracing-utils", + "rivet-universaldb-commit", "rocksdb", + "scc", "serde", "tempfile", "thiserror 1.0.69", "tokio", "tokio-postgres", "tokio-postgres-rustls", + "tokio-util", "tracing", "tracing-subscriber", "url", "uuid", + "vbare", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 511aec200f..a9048c159f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ members = [ "engine/sdks/rust/depot-protocol", "engine/sdks/rust/test-envoy", "engine/sdks/rust/ups-protocol", + "engine/sdks/rust/universaldb-commit", "rivetkit-rust/packages/actor-persist", "rivetkit-rust/packages/client", "rivetkit-rust/packages/engine-process", @@ -655,6 +656,9 @@ members = [ [workspace.dependencies.rivet-ups-protocol] path = "engine/sdks/rust/ups-protocol" + [workspace.dependencies.rivet-universaldb-commit] + path = "engine/sdks/rust/universaldb-commit" + [profile.dev] overflow-checks = false # "line-tables-only" produces just the line-number DWARF needed for stack diff --git a/engine/packages/test-deps-docker/src/database.rs b/engine/packages/test-deps-docker/src/database.rs index 11532ebf25..955e4d7897 100644 --- a/engine/packages/test-deps-docker/src/database.rs +++ b/engine/packages/test-deps-docker/src/database.rs @@ -61,7 +61,7 @@ impl TestDatabase { }); let docker_config = DockerRunConfig { - image: "postgres:17".to_string(), + image: "postgres:18".to_string(), container_name: container_name.clone(), port_mapping: (port, 5432), env_vars: vec![ diff --git a/engine/packages/universaldb/Cargo.toml b/engine/packages/universaldb/Cargo.toml index 1fdc9b1c99..b2f6b5f045 100644 --- a/engine/packages/universaldb/Cargo.toml +++ b/engine/packages/universaldb/Cargo.toml @@ -9,6 +9,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true async-trait.workspace = true +base64.workspace = true deadpool-postgres.workspace = true foundationdb-tuple.workspace = true futures-util.workspace = true @@ -18,16 +19,20 @@ rand.workspace = true rivet-metrics.workspace = true rivet-postgres-util.workspace = true rivet-tracing-utils.workspace = true +rivet-universaldb-commit.workspace = true rocksdb.workspace = true +scc.workspace = true serde.workspace = true tempfile.workspace = true thiserror.workspace = true tokio-postgres-rustls.workspace = true tokio-postgres.workspace = true +tokio-util.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true uuid.workspace = true +vbare.workspace = true [dev-dependencies] rivet-config.workspace = true diff --git a/engine/packages/universaldb/src/driver/rocksdb/transaction_conflict_tracker.rs b/engine/packages/universaldb/src/conflict_tracker.rs similarity index 69% rename from engine/packages/universaldb/src/driver/rocksdb/transaction_conflict_tracker.rs rename to engine/packages/universaldb/src/conflict_tracker.rs index 370240760a..127e2bf615 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/transaction_conflict_tracker.rs +++ b/engine/packages/universaldb/src/conflict_tracker.rs @@ -11,7 +11,7 @@ use tokio::sync::Mutex; use crate::options::ConflictRangeType; // Transactions cannot live longer than 5 seconds so we don't need to store transaction conflicts longer than -// that +// that. const TXN_CONFLICT_TTL: Duration = Duration::from_secs(10); #[derive(Debug)] @@ -22,6 +22,15 @@ struct PreviousTransaction { conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, } +/// In-process FoundationDB-style resolver. Holds the last `TXN_CONFLICT_TTL` of committed +/// transactions and rejects a committing transaction if any retained transaction has both an +/// overlapping version window and an overlapping conflict range of a differing type. +/// +/// Used by the rocksdb driver (single process) and by the postgres leader-resolver. The two +/// differ only in where the commit version comes from: rocksdb generates it from the in-process +/// `global_version` counter, while the postgres leader assigns it from the durable +/// `udb_version_seq` so it survives leader failover and matches the versionstamp. For that reason +/// `check_and_insert` takes the commit version from the caller instead of generating it. #[derive(Clone)] pub struct TransactionConflictTracker { // NOTE: We use a mutex because we need to lock reads across all active txns. This could be optimized to @@ -40,18 +49,23 @@ impl TransactionConflictTracker { } } - /// Each number returned is unique. + /// Each number returned is unique. Used by the in-process rocksdb driver to assign both start + /// and commit versions. The postgres leader does not use this; it assigns versions from the + /// durable Postgres sequence. pub fn next_global_version(&self) -> u64 { self.global_version.fetch_add(1, Ordering::SeqCst) } + /// Returns `true` on conflict (same polarity as the original rocksdb tracker). The caller + /// supplies `commit_version` (e.g. `nextval('udb_version_seq')` on the postgres leader, or + /// `next_global_version()` on rocksdb) so version assignment stays the caller's responsibility. pub async fn check_and_insert( &self, txn1_start_version: u64, + txn1_commit_version: u64, txn1_conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, ) -> bool { let mut txns = self.txns.lock().await; - let txn1_commit_version = self.next_global_version(); // Prune old entries txns.retain(|txn| txn.insert_instant.elapsed() < TXN_CONFLICT_TTL); diff --git a/engine/packages/universaldb/src/driver/postgres/codec.rs b/engine/packages/universaldb/src/driver/postgres/codec.rs new file mode 100644 index 0000000000..432ede2f34 --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/codec.rs @@ -0,0 +1,167 @@ +use anyhow::Result; +use rivet_universaldb_commit::{self as proto, versioned}; +use vbare::OwnedVersionedData; + +use crate::{ + options::{ConflictRangeType, MutationType}, + tx_ops::Operation, +}; + +/// Decoded form of a `udb_commit_requests.payload` blob. +/// +/// `read_version` is intentionally omitted: it is also denormalized into the `read_version` column, +/// which is what the leader's drain reads, so decoding it here would be dead. +pub struct DecodedCommit { + pub conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, + pub operations: Vec, +} + +/// Encode a follower's commit request to the versioned BARE wire format with an embedded version +/// header so a leader running older or newer code can still decode it during a rolling deploy. +pub fn encode_commit_request( + read_version: u64, + conflict_ranges: &[(Vec, Vec, ConflictRangeType)], + operations: &[Operation], +) -> Result> { + let request = proto::CommitRequest { + read_version, + conflict_ranges: conflict_ranges + .iter() + .map(|(begin, end, kind)| proto::ConflictRange { + begin: begin.clone(), + end: end.clone(), + kind: conflict_range_type_to_proto(*kind), + }) + .collect(), + operations: operations.iter().map(operation_to_proto).collect(), + }; + + versioned::CommitRequest::wrap_latest(request) + .serialize_with_embedded_version(proto::PROTOCOL_VERSION) +} + +/// Decode a `udb_commit_requests.payload` blob produced by [`encode_commit_request`]. +pub fn decode_commit_request(payload: &[u8]) -> Result { + let request = versioned::CommitRequest::deserialize_with_embedded_version(payload)?; + + let conflict_ranges = request + .conflict_ranges + .into_iter() + .map(|range| { + ( + range.begin, + range.end, + conflict_range_type_from_proto(range.kind), + ) + }) + .collect(); + + let operations = request + .operations + .into_iter() + .map(operation_from_proto) + .collect(); + + Ok(DecodedCommit { + conflict_ranges, + operations, + }) +} + +fn conflict_range_type_to_proto(kind: ConflictRangeType) -> proto::ConflictRangeType { + match kind { + ConflictRangeType::Read => proto::ConflictRangeType::Read, + ConflictRangeType::Write => proto::ConflictRangeType::Write, + } +} + +fn conflict_range_type_from_proto(kind: proto::ConflictRangeType) -> ConflictRangeType { + match kind { + proto::ConflictRangeType::Read => ConflictRangeType::Read, + proto::ConflictRangeType::Write => ConflictRangeType::Write, + } +} + +fn operation_to_proto(op: &Operation) -> proto::Operation { + match op { + Operation::SetValue { key, value } => proto::Operation::SetValue(proto::SetValue { + key: key.clone(), + value: value.clone(), + }), + Operation::Clear { key } => proto::Operation::Clear(proto::Clear { key: key.clone() }), + Operation::ClearRange { begin, end } => proto::Operation::ClearRange(proto::ClearRange { + begin: begin.clone(), + end: end.clone(), + }), + Operation::AtomicOp { + key, + param, + op_type, + } => proto::Operation::AtomicOp(proto::AtomicOp { + key: key.clone(), + param: param.clone(), + op_type: mutation_type_to_proto(*op_type), + }), + } +} + +fn operation_from_proto(op: proto::Operation) -> Operation { + match op { + proto::Operation::SetValue(proto::SetValue { key, value }) => { + Operation::SetValue { key, value } + } + proto::Operation::Clear(proto::Clear { key }) => Operation::Clear { key }, + proto::Operation::ClearRange(proto::ClearRange { begin, end }) => { + Operation::ClearRange { begin, end } + } + proto::Operation::AtomicOp(proto::AtomicOp { + key, + param, + op_type, + }) => Operation::AtomicOp { + key, + param, + op_type: mutation_type_from_proto(op_type), + }, + } +} + +fn mutation_type_to_proto(op_type: MutationType) -> proto::MutationType { + match op_type { + MutationType::Add => proto::MutationType::Add, + MutationType::And => proto::MutationType::And, + MutationType::BitAnd => proto::MutationType::BitAnd, + MutationType::Or => proto::MutationType::Or, + MutationType::BitOr => proto::MutationType::BitOr, + MutationType::Xor => proto::MutationType::Xor, + MutationType::BitXor => proto::MutationType::BitXor, + MutationType::AppendIfFits => proto::MutationType::AppendIfFits, + MutationType::Max => proto::MutationType::Max, + MutationType::Min => proto::MutationType::Min, + MutationType::SetVersionstampedKey => proto::MutationType::SetVersionstampedKey, + MutationType::SetVersionstampedValue => proto::MutationType::SetVersionstampedValue, + MutationType::ByteMin => proto::MutationType::ByteMin, + MutationType::ByteMax => proto::MutationType::ByteMax, + MutationType::CompareAndClear => proto::MutationType::CompareAndClear, + } +} + +fn mutation_type_from_proto(op_type: proto::MutationType) -> MutationType { + match op_type { + proto::MutationType::Add => MutationType::Add, + proto::MutationType::And => MutationType::And, + proto::MutationType::BitAnd => MutationType::BitAnd, + proto::MutationType::Or => MutationType::Or, + proto::MutationType::BitOr => MutationType::BitOr, + proto::MutationType::Xor => MutationType::Xor, + proto::MutationType::BitXor => MutationType::BitXor, + proto::MutationType::AppendIfFits => MutationType::AppendIfFits, + proto::MutationType::Max => MutationType::Max, + proto::MutationType::Min => MutationType::Min, + proto::MutationType::SetVersionstampedKey => MutationType::SetVersionstampedKey, + proto::MutationType::SetVersionstampedValue => MutationType::SetVersionstampedValue, + proto::MutationType::ByteMin => MutationType::ByteMin, + proto::MutationType::ByteMax => MutationType::ByteMax, + proto::MutationType::CompareAndClear => MutationType::CompareAndClear, + } +} diff --git a/engine/packages/universaldb/src/driver/postgres/commit.rs b/engine/packages/universaldb/src/driver/postgres/commit.rs new file mode 100644 index 0000000000..e61714dd0e --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/commit.rs @@ -0,0 +1,185 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::{Context, Result}; + +use crate::{error::DatabaseError, options::ConflictRangeType, tx_ops::Operation}; + +use super::{ + codec, + shared::{LeaseInfo, PostgresShared, commit_channel, reply_channel}, +}; + +/// How long to wait for a leader to be elected before giving up a submit as retryable. +const LEADER_WAIT_TIMEOUT: Duration = Duration::from_secs(5); +/// Backstop poll cadence while waiting for a commit result, in case a reply NOTIFY is missed. +const RESULT_POLL_INTERVAL: Duration = Duration::from_millis(250); + +/// Submit a follower transaction's commit to the leader and await the result. +/// +/// `read_version` is the watermark captured when this transaction opened its read snapshot. A pure +/// snapshot read-only transaction (no operations and no read conflict ranges) submits nothing. +pub async fn submit( + shared: &Arc, + read_version: i64, + operations: Vec, + conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, +) -> Result<()> { + // A transaction with no writes and no serializable read ranges has nothing to order or + // validate; it never needs the leader. + if operations.is_empty() + && conflict_ranges + .iter() + .all(|(_, _, kind)| matches!(kind, ConflictRangeType::Write)) + { + return Ok(()); + } + + let lease = wait_for_leader(shared).await?; + let payload = + codec::encode_commit_request(read_version.max(0) as u64, &conflict_ranges, &operations) + .context("failed to encode commit request")?; + let reply_channel = reply_channel(&shared.node_id); + + // Subscribe to our reply channel before inserting so we cannot miss the leader's NOTIFY. + let mut reply_rx = shared.listener.listen(&reply_channel).await; + + let conn = shared + .pool + .get() + .await + .context("failed to get connection for commit submit")?; + + let id: i64 = conn + .query_one( + "INSERT INTO udb_commit_requests (epoch, read_version, payload, reply_channel) + VALUES ($1, $2, $3, $4) + RETURNING id", + &[&lease.epoch, &read_version, &payload, &reply_channel], + ) + .await + .context("failed to enqueue commit request")? + .get(0); + + // Wake the leader's drain loop. + if let Err(err) = conn + .execute( + "SELECT pg_notify($1, $2)", + &[&commit_channel(&lease.leader_addr), &id.to_string()], + ) + .await + { + tracing::debug!( + ?err, + "failed to notify leader; relying on its poll backstop" + ); + } + + // Release the connection before waiting so a long wait does not pin a pool slot. The request + // row is durable, so await_result re-acquires a connection per poll. + drop(conn); + + await_result(shared, id, lease.epoch, &mut reply_rx).await +} + +/// Wait for a known leader, returning a retryable error if none is elected in time. +async fn wait_for_leader(shared: &Arc) -> Result { + let deadline = Instant::now() + LEADER_WAIT_TIMEOUT; + loop { + if let Some(lease) = shared.current_lease() { + return Ok(lease); + } + if Instant::now() >= deadline { + return Err(DatabaseError::NotCommitted.into()); + } + tokio::time::sleep(RESULT_POLL_INTERVAL).await; + } +} + +/// Poll the request row until it reaches a terminal status, woken by reply NOTIFYs with a polling +/// backstop. Bails as retryable if the leader epoch advances (our request is now orphaned and will +/// never be applied, so it is definitively not committed). +async fn await_result( + shared: &Arc, + id: i64, + submit_epoch: i64, + reply_rx: &mut tokio::sync::broadcast::Receiver, +) -> Result<()> { + loop { + // Re-acquire a connection per poll: the request row is durable, so a transient pool/query + // error just means we retry the poll rather than failing a possibly-applied commit. + match read_status(shared, id).await { + Ok(Some(Status::Committed)) => return Ok(()), + Ok(Some(Status::Conflict)) => return Err(DatabaseError::NotCommitted.into()), + Ok(Some(Status::Pending)) => {} + Ok(None) => { + // The row was GC'd before we observed a terminal status. Treat as not committed + // and let the retry loop resubmit. + return Err(DatabaseError::NotCommitted.into()); + } + Err(err) => { + tracing::debug!(?err, "transient error polling commit status, retrying"); + } + } + + // If a new leader took over, our old-epoch request will never be claimed. + if let Some(current) = shared.current_lease() { + if current.epoch != submit_epoch { + return Err(DatabaseError::NotCommitted.into()); + } + } + + tokio::select! { + res = reply_rx.recv() => { + match res { + Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + *reply_rx = shared + .listener + .listen(&reply_channel(&shared.node_id)) + .await; + } + } + } + _ = tokio::time::sleep(RESULT_POLL_INTERVAL) => {} + } + } +} + +enum Status { + Pending, + Committed, + Conflict, +} + +/// Read the current status of a commit request. `Ok(None)` means the row no longer exists. +async fn read_status(shared: &Arc, id: i64) -> Result> { + let conn = shared + .pool + .get() + .await + .context("failed to get connection for commit status poll")?; + + let row = conn + .query_opt( + "SELECT status FROM udb_commit_requests WHERE id = $1", + &[&id], + ) + .await + .context("failed to read commit request status")?; + + let Some(row) = row else { + return Ok(None); + }; + + let status: String = row.get(0); + let status = match status.as_str() { + "committed" => Status::Committed, + "conflict" => Status::Conflict, + // 'pending' or any in-flight state. + _ => Status::Pending, + }; + Ok(Some(status)) +} diff --git a/engine/packages/universaldb/src/driver/postgres/database.rs b/engine/packages/universaldb/src/driver/postgres/database.rs index 4d50d08ab3..6e24cf647d 100644 --- a/engine/packages/universaldb/src/driver/postgres/database.rs +++ b/engine/packages/universaldb/src/driver/postgres/database.rs @@ -13,6 +13,7 @@ use rivet_postgres_util::build_tls_config; use tokio::task::JoinHandle; use tokio_postgres_rustls::MakeRustlsConnect; use url::Url; +use uuid::Uuid; use crate::{ RetryableTransaction, Transaction, @@ -22,9 +23,15 @@ use crate::{ utils::{MaybeCommitted, calculate_tx_retry_backoff}, }; -use super::transaction::PostgresTransactionDriver; +use super::{ + listener::PgListener, resolver, shared::PostgresShared, transaction::PostgresTransactionDriver, +}; -const GC_INTERVAL: Duration = Duration::from_secs(5); +const GC_INTERVAL: Duration = Duration::from_secs(30); +/// Terminal and orphaned commit-request rows older than this are garbage collected. Must be well +/// beyond the longest a follower could spend awaiting a result, so a result is never deleted before +/// it is observed. +const COMMIT_ROW_MAX_AGE_SECS: i64 = 60; #[derive(Clone, Debug)] pub struct PostgresConfig { @@ -50,7 +57,7 @@ impl PostgresConfig { } pub struct PostgresDatabaseDriver { - pool: Pool, + shared: Arc, max_retries: AtomicI32, gc_handle: JoinHandle<()>, } @@ -63,7 +70,60 @@ impl PostgresDatabaseDriver { "creating PostgresDatabaseDriver" ); - // Create deadpool config from connection string + let ssl_disabled = if let Ok(url) = Url::parse(&config.connection_string) { + url.query_pairs() + .any(|(k, v)| k == "sslmode" && v == "disable") + } else { + false + }; + + let pool = Self::build_pool(&config, ssl_disabled)?; + + // Initialize the schema (idempotent). + { + let conn = pool + .get() + .await + .context("failed to get connection from postgres pool")?; + Self::init_schema(&conn).await?; + } + + // Unique per-process node id (no hyphens) used to name this node's NOTIFY channels. Kept + // short so `udb_commit_` stays within Postgres's 63-byte identifier limit. + let node_id = Uuid::new_v4().simple().to_string(); + + let listener = PgListener::new( + config.connection_string.clone(), + ssl_disabled, + config + .ssl_config + .as_ref() + .and_then(|c| c.ssl_root_cert_path.clone()), + config + .ssl_config + .as_ref() + .and_then(|c| c.ssl_client_cert_path.clone()), + config + .ssl_config + .as_ref() + .and_then(|c| c.ssl_client_key_path.clone()), + ); + + let shared = PostgresShared::new(pool, node_id, listener); + + // Every node runs the resolver; only the elected leader drains the commit queue. + resolver::spawn(shared.clone()); + + let gc_handle = Self::spawn_gc(shared.clone()); + + Ok(PostgresDatabaseDriver { + shared, + max_retries: AtomicI32::new(100), + gc_handle, + }) + } + + fn build_pool(config: &PostgresConfig, ssl_disabled: bool) -> Result { let mut pool_config = Config::new(); pool_config.url = Some(config.connection_string.clone()); pool_config.pool = Some(PoolConfig { @@ -74,21 +134,10 @@ impl PostgresDatabaseDriver { recycling_method: RecyclingMethod::Fast, }); - tracing::debug!("creating Postgres pool"); - - let ssl_disabled = if let Ok(url) = Url::parse(&config.connection_string) { - url.query_pairs() - .any(|(k, v)| k == "sslmode" && v == "disable") - } else { - false - }; - - let pool = if ssl_disabled { - let tls = tokio_postgres::NoTls; - + if ssl_disabled { pool_config - .create_pool(Some(Runtime::Tokio1), tls) - .context("failed to create postgres connection pool")? + .create_pool(Some(Runtime::Tokio1), tokio_postgres::NoTls) + .context("failed to create postgres connection pool") } else { let tls_config = build_tls_config( config @@ -104,139 +153,87 @@ impl PostgresDatabaseDriver { .as_ref() .and_then(|c| c.ssl_client_key_path.as_ref()), )?; - let tls = MakeRustlsConnect::new(tls_config); - pool_config - .create_pool(Some(Runtime::Tokio1), tls) - .context("failed to create postgres connection pool")? - }; - - tracing::debug!("Getting Postgres connection from pool"); - // Get a connection from the pool to create the table - let conn = pool - .get() - .await - .context("failed to get connection from postgres pool")?; - - // Enable btree gist - conn.execute("CREATE EXTENSION IF NOT EXISTS btree_gist", &[]) - .await - .context("failed to create btree_gist extension")?; - - conn.execute("CREATE UNLOGGED SEQUENCE IF NOT EXISTS global_version_seq START WITH 1 INCREMENT BY 1 MINVALUE 1", &[]) - .await - .context("failed to create global version sequence")?; + .create_pool(Some(Runtime::Tokio1), MakeRustlsConnect::new(tls_config)) + .context("failed to create postgres connection pool") + } + } - // Create the KV table if it doesn't exist - conn.execute( + async fn init_schema(conn: &deadpool_postgres::Client) -> Result<()> { + // Durable latest-value store. + conn.batch_execute( "CREATE TABLE IF NOT EXISTS kv ( key BYTEA PRIMARY KEY, value BYTEA NOT NULL - )", - &[], - ) - .await - .context("failed to create kv table")?; - - // Create range_type type if it doesn't exist - conn.execute( - "DO $$ BEGIN - CREATE TYPE range_type AS ENUM ('read', 'write'); - EXCEPTION - WHEN duplicate_object THEN null; - END $$", - &[], - ) - .await - .context("failed to create range_type enum")?; - - // Create bytearange type if it doesn't exist - conn.execute( - "DO $$ BEGIN - CREATE TYPE bytearange AS RANGE ( - SUBTYPE = bytea, - SUBTYPE_OPCLASS = bytea_ops - ); - EXCEPTION - WHEN duplicate_object THEN null; - END $$", - &[], + ); + + CREATE TABLE IF NOT EXISTS udb_lease ( + id INT PRIMARY KEY DEFAULT 1 CHECK (id = 1), + epoch BIGINT NOT NULL, + leader_addr TEXT NOT NULL, + durable_version BIGINT NOT NULL DEFAULT 0, + expires_at TIMESTAMPTZ NOT NULL + ); + + CREATE SEQUENCE IF NOT EXISTS udb_version_seq AS BIGINT + START WITH 1 INCREMENT BY 1 MINVALUE 1; + + CREATE TABLE IF NOT EXISTS udb_commit_requests ( + id BIGSERIAL PRIMARY KEY, + epoch BIGINT NOT NULL, + read_version BIGINT NOT NULL, + payload BYTEA NOT NULL, + reply_channel TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + commit_version BIGINT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); + + CREATE INDEX IF NOT EXISTS udb_commit_requests_pending + ON udb_commit_requests (id) WHERE status = 'pending';", ) .await - .context("failed to create bytearange type")?; - - // Create the conflict ranges table for non-snapshot reads - // This enforces consistent reads for ranges by preventing overlapping conflict ranges - conn.execute( - "CREATE UNLOGGED TABLE IF NOT EXISTS conflict_ranges ( - range_data BYTEARANGE NOT NULL, - conflict_type range_type NOT NULL, - start_version BIGINT NOT NULL, - commit_version BIGINT NOT NULL, - ts timestamp NOT NULL DEFAULT now(), - - EXCLUDE USING gist ( - -- Conflict if byte range overlaps... - range_data WITH &&, - -- And if conflict types are different... - conflict_type WITH <>, - -- And if the txn versions overlap... - int8range(start_version, commit_version, '[]') WITH &&, - -- But not if the start_version is the same (from the same txn) - start_version WITH <> - ) - )", - &[], - ) - .await - .context("failed to create conflict_ranges table")?; + .context("failed to initialize postgres schema")?; - // Create index on ts column for efficient garbage collection - conn.execute( - "CREATE INDEX IF NOT EXISTS idx_conflict_ranges_ts ON conflict_ranges (ts)", - &[], - ) - .await - .context("failed to create index on conflict_ranges ts column")?; + Ok(()) + } - let pool2 = pool.clone(); - let gc_handle = tokio::spawn(async move { + fn spawn_gc(shared: Arc) -> JoinHandle<()> { + tokio::spawn(async move { let mut interval = tokio::time::interval(GC_INTERVAL); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { interval.tick().await; - tracing::debug!(status=?pool2.status(), "postgres pool status"); + let conn = match shared.pool.get().await { + Ok(conn) => conn, + Err(err) => { + tracing::debug!(?err, "failed to get connection for commit gc"); + continue; + } + }; - // NOTE: Transactions have a max limit of 5 seconds, we delete after 10 seconds for extra padding - // Delete old conflict ranges if let Err(err) = conn .execute( - "DELETE FROM conflict_ranges where ts < now() - interval '10 seconds'", - &[], + "DELETE FROM udb_commit_requests + WHERE created_at < now() - ($1::bigint * interval '1 second')", + &[&COMMIT_ROW_MAX_AGE_SECS], ) .await { - tracing::error!(?err, "failed postgres gc task"); + tracing::error!(?err, "failed postgres commit-queue gc"); } } - }); - - Ok(PostgresDatabaseDriver { - pool, - max_retries: AtomicI32::new(100), - gc_handle, }) } } impl DatabaseDriver for PostgresDatabaseDriver { fn create_txn(&self) -> Result { - // Pass the connection pool and config to the transaction driver - Ok(Transaction::new(Arc::new( - PostgresTransactionDriver::with_config(self.pool.clone()), - ))) + Ok(Transaction::new(Arc::new(PostgresTransactionDriver::new( + self.shared.clone(), + )))) } fn run<'a>( diff --git a/engine/packages/universaldb/src/driver/postgres/listener.rs b/engine/packages/universaldb/src/driver/postgres/listener.rs new file mode 100644 index 0000000000..e5a7919dfe --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/listener.rs @@ -0,0 +1,228 @@ +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use futures_util::future::poll_fn; +use rivet_postgres_util::build_tls_config; +use scc::HashMap; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + sync::{Mutex, broadcast}, +}; +use tokio_postgres::AsyncMessage; +use tokio_postgres_rustls::MakeRustlsConnect; + +/// How long to wait between reconnect attempts for the dedicated LISTEN connection. +const RECONNECT_BACKOFF: Duration = Duration::from_secs(1); +/// Capacity of each channel's broadcast buffer. Notifications are wakeup signals with a polling +/// backstop, so a lagged receiver only delays a wake, never drops a durable commit. +const BROADCAST_CAPACITY: usize = 1024; + +struct Subscription { + tx: broadcast::Sender, +} + +/// Owns a single dedicated Postgres connection used exclusively for `LISTEN`. Demultiplexes +/// incoming `NOTIFY` payloads to per-channel broadcast senders and re-`LISTEN`s every registered +/// channel after a reconnect. +/// +/// This is separate from the deadpool pool because deadpool recycles connections and drops the +/// async notification stream; LISTEN requires owning the connection's message stream directly. +pub struct PgListener { + conn_str: String, + ssl_disabled: bool, + ssl_root_cert_path: Option, + ssl_client_cert_path: Option, + ssl_client_key_path: Option, + channels: Arc>, + client: Arc>>, +} + +impl PgListener { + pub fn new( + conn_str: String, + ssl_disabled: bool, + ssl_root_cert_path: Option, + ssl_client_cert_path: Option, + ssl_client_key_path: Option, + ) -> Self { + let channels: Arc> = Arc::new(HashMap::new()); + let client: Arc>> = Arc::new(Mutex::new(None)); + + tokio::spawn(Self::connection_lifecycle( + conn_str.clone(), + ssl_disabled, + ssl_root_cert_path.clone(), + ssl_client_cert_path.clone(), + ssl_client_key_path.clone(), + channels.clone(), + client.clone(), + )); + + Self { + conn_str, + ssl_disabled, + ssl_root_cert_path, + ssl_client_cert_path, + ssl_client_key_path, + channels, + client, + } + } + + /// Subscribe to a channel, registering a `LISTEN` if this is the first subscriber. Returns a + /// broadcast receiver of notification payloads. Idempotent per channel. + pub async fn listen(&self, channel: &str) -> broadcast::Receiver { + match self.channels.entry_async(channel.to_string()).await { + scc::hash_map::Entry::Occupied(entry) => entry.get().tx.subscribe(), + scc::hash_map::Entry::Vacant(entry) => { + let (tx, rx) = broadcast::channel(BROADCAST_CAPACITY); + entry.insert_entry(Subscription { tx }); + + // Best-effort immediate LISTEN; the lifecycle task re-LISTENs on reconnect. + if let Some(client) = &*self.client.lock().await { + if let Err(err) = client.execute(&format!("LISTEN \"{channel}\""), &[]).await { + tracing::warn!(?err, %channel, "failed to LISTEN, will retry on reconnect"); + } + } + + rx + } + } + } + + async fn connection_lifecycle( + conn_str: String, + ssl_disabled: bool, + ssl_root_cert_path: Option, + ssl_client_cert_path: Option, + ssl_client_key_path: Option, + channels: Arc>, + client: Arc>>, + ) { + loop { + let connected = if ssl_disabled { + Self::connect_and_run(&conn_str, tokio_postgres::NoTls, &channels, &client).await + } else { + match build_tls_config( + ssl_root_cert_path.as_ref(), + ssl_client_cert_path.as_ref(), + ssl_client_key_path.as_ref(), + ) { + Ok(tls_config) => { + Self::connect_and_run( + &conn_str, + MakeRustlsConnect::new(tls_config), + &channels, + &client, + ) + .await + } + Err(err) => { + tracing::error!(?err, "failed to build listener TLS config"); + false + } + } + }; + + if !connected { + tokio::time::sleep(RECONNECT_BACKOFF).await; + } + } + } + + /// Connects, re-LISTENs all channels, then drives the notification poll loop until the + /// connection closes. Returns `true` if a connection was successfully established (so the caller + /// can skip the reconnect backoff). + async fn connect_and_run( + conn_str: &str, + tls: T, + channels: &Arc>, + client: &Arc>>, + ) -> bool + where + T: tokio_postgres::tls::MakeTlsConnect, + T::Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static, + T::TlsConnect: Send, + >::Future: Send, + { + let (new_client, connection) = match tokio_postgres::connect(conn_str, tls).await { + Ok(pair) => pair, + Err(err) => { + tracing::error!(?err, "failed to connect postgres listener"); + return false; + } + }; + + let channels_poll = channels.clone(); + let poll_handle = + tokio::spawn(async move { Self::poll_connection(connection, channels_poll).await }); + + // Re-LISTEN all registered channels on the fresh connection. + let mut registered = Vec::new(); + channels + .iter_async(|k, _| { + registered.push(k.clone()); + true + }) + .await; + for channel in ®istered { + if let Err(err) = new_client + .execute(&format!("LISTEN \"{channel}\""), &[]) + .await + { + tracing::error!(?err, %channel, "failed to re-LISTEN channel after reconnect"); + } + } + + *client.lock().await = Some(new_client); + + // Block until the poll loop ends (connection closed or errored). + let _ = poll_handle.await; + + *client.lock().await = None; + + true + } + + async fn poll_connection( + mut connection: tokio_postgres::Connection, + channels: Arc>, + ) where + S: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin, + { + loop { + match poll_fn(|cx| connection.poll_message(cx)).await { + Some(Ok(AsyncMessage::Notification(note))) => { + if let Some(sub) = channels.get_async(note.channel()).await { + // Ignore send errors: no active receiver just means no one is waiting + // right now; the polling backstop covers them. + let _ = sub.tx.send(note.payload().to_string()); + } + } + Some(Ok(_)) => {} + Some(Err(err)) => { + tracing::warn!(?err, "postgres listener connection error"); + break; + } + None => { + tracing::warn!("postgres listener connection closed"); + break; + } + } + } + } +} + +impl Clone for PgListener { + fn clone(&self) -> Self { + Self { + conn_str: self.conn_str.clone(), + ssl_disabled: self.ssl_disabled, + ssl_root_cert_path: self.ssl_root_cert_path.clone(), + ssl_client_cert_path: self.ssl_client_cert_path.clone(), + ssl_client_key_path: self.ssl_client_key_path.clone(), + channels: self.channels.clone(), + client: self.client.clone(), + } + } +} diff --git a/engine/packages/universaldb/src/driver/postgres/mod.rs b/engine/packages/universaldb/src/driver/postgres/mod.rs index 1c24f9cb94..64f4bbd1bf 100644 --- a/engine/packages/universaldb/src/driver/postgres/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/mod.rs @@ -1,4 +1,9 @@ +mod codec; +mod commit; mod database; +mod listener; +mod resolver; +mod shared; mod transaction; mod transaction_task; diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs b/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs new file mode 100644 index 0000000000..d9729de884 --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs @@ -0,0 +1,139 @@ +use anyhow::{Context, Result}; +use deadpool_postgres::Transaction; + +use crate::{ + atomic::apply_atomic_op, options::MutationType, tuple::Versionstamp, tx_ops::Operation, + versionstamp::substitute_raw_versionstamp, +}; + +/// Apply a winning transaction's operations to `kv` inside the leader's batch txn. +/// +/// `commit_version` is the Postgres-resolved version assigned to this commit (`nextval`). It is +/// substituted into the 8-byte committed-version slot of any versionstamped key/value so +/// versionstamps are globally monotonic with commit order across all follower processes. +pub async fn apply( + txn: &Transaction<'_>, + operations: Vec, + commit_version: u64, +) -> Result<()> { + // Distinguishes multiple versionstamped operations within a single commit so their 10-byte + // stamps stay unique (8-byte version shared, 2-byte counter incremented). + let mut versionstamp_counter: u16 = 0; + + for op in operations { + match op { + Operation::SetValue { key, value } => { + upsert(txn, &key, &value).await?; + } + Operation::Clear { key } => { + txn.execute("DELETE FROM kv WHERE key = $1", &[&key]) + .await + .context("failed to clear key")?; + } + Operation::ClearRange { begin, end } => { + txn.execute( + "DELETE FROM kv WHERE key >= $1 AND key < $2", + &[&begin, &end], + ) + .await + .context("failed to clear range")?; + } + Operation::AtomicOp { + key, + param, + op_type, + } => { + apply_atomic( + txn, + key, + param, + op_type, + commit_version, + &mut versionstamp_counter, + ) + .await?; + } + } + } + + Ok(()) +} + +async fn apply_atomic( + txn: &Transaction<'_>, + key: Vec, + param: Vec, + op_type: MutationType, + commit_version: u64, + versionstamp_counter: &mut u16, +) -> Result<()> { + match op_type { + MutationType::SetVersionstampedKey => { + let versionstamp = build_versionstamp(commit_version, versionstamp_counter); + let key = substitute_raw_versionstamp(key, &versionstamp) + .map_err(anyhow::Error::msg) + .context("failed substituting versionstamped key")?; + upsert(txn, &key, ¶m).await?; + } + MutationType::SetVersionstampedValue => { + let versionstamp = build_versionstamp(commit_version, versionstamp_counter); + let value = substitute_raw_versionstamp(param, &versionstamp) + .map_err(anyhow::Error::msg) + .context("failed substituting versionstamped value")?; + upsert(txn, &key, &value).await?; + } + // Read-modify-write atomics: the leader is the single writer, so reading the live value + // inside the apply txn and writing the result is serializable with no lost update. + MutationType::Add + | MutationType::And + | MutationType::BitAnd + | MutationType::Or + | MutationType::BitOr + | MutationType::Xor + | MutationType::BitXor + | MutationType::AppendIfFits + | MutationType::Max + | MutationType::Min + | MutationType::ByteMin + | MutationType::ByteMax + | MutationType::CompareAndClear => { + let current = txn + .query_opt("SELECT value FROM kv WHERE key = $1", &[&key]) + .await + .context("failed to read current value for atomic op")? + .map(|row| row.get::<_, Vec>(0)); + + let new_value = apply_atomic_op(current.as_deref(), ¶m, op_type); + + if let Some(new_value) = new_value { + upsert(txn, &key, &new_value).await?; + } else { + txn.execute("DELETE FROM kv WHERE key = $1", &[&key]) + .await + .context("failed to clear key after atomic op")?; + } + } + } + + Ok(()) +} + +async fn upsert(txn: &Transaction<'_>, key: &[u8], value: &[u8]) -> Result<()> { + txn.execute( + "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2", + &[&key, &value], + ) + .await + .context("failed to upsert kv")?; + Ok(()) +} + +/// Build a 10-byte versionstamp (plus the 2 user-version bytes the substitution helper ignores) +/// from the Postgres-resolved commit version and a per-commit counter. +fn build_versionstamp(commit_version: u64, counter: &mut u16) -> Versionstamp { + let mut bytes = [0u8; 12]; + bytes[0..8].copy_from_slice(&commit_version.to_be_bytes()); + bytes[8..10].copy_from_slice(&counter.to_be_bytes()); + *counter = counter.wrapping_add(1); + Versionstamp::from(bytes) +} diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs b/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs new file mode 100644 index 0000000000..a17b6c7759 --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs @@ -0,0 +1,82 @@ +use anyhow::{Context, Result}; +use deadpool_postgres::Pool; + +use crate::driver::postgres::shared::LEASE_ID; + +/// Lease time-to-live. A leader renews well within this; a candidate may take over only after it +/// expires. +pub const LEASE_TTL_SECS: i64 = 10; + +/// Outcome of a leadership acquisition attempt. +pub struct Acquired { + pub epoch: i64, +} + +/// Attempt to acquire or take over the leader lease via an epoch CAS. Succeeds if there is no lease +/// row yet, or the existing lease has expired. Bumps `epoch` on every successful acquisition so a +/// superseded old leader is fenced out. +pub async fn try_acquire(pool: &Pool, node_id: &str) -> Result> { + let conn = pool + .get() + .await + .context("failed to get connection for lease acquire")?; + + // Take over an expired (or absent) lease. The INSERT seeds the singleton row on first ever + // election; thereafter the UPDATE path runs. + let row = conn + .query_opt( + "INSERT INTO udb_lease (id, epoch, leader_addr, durable_version, expires_at) + VALUES ($1, 1, $2, 0, now() + ($3 || ' seconds')::interval) + ON CONFLICT (id) DO UPDATE + SET epoch = udb_lease.epoch + 1, + leader_addr = EXCLUDED.leader_addr, + expires_at = now() + ($3 || ' seconds')::interval + WHERE udb_lease.expires_at < now() + RETURNING epoch", + &[&LEASE_ID, &node_id, &LEASE_TTL_SECS.to_string()], + ) + .await + .context("failed to run lease acquire query")?; + + Ok(row.map(|row| Acquired { epoch: row.get(0) })) +} + +/// Renew the lease, fenced on this leader's epoch. Returns `false` if the lease was lost (another +/// node took over, bumping the epoch), in which case the caller must step down. +pub async fn renew(pool: &Pool, node_id: &str, epoch: i64) -> Result { + let conn = pool + .get() + .await + .context("failed to get connection for lease renew")?; + + let updated = conn + .execute( + "UPDATE udb_lease + SET expires_at = now() + ($3 || ' seconds')::interval + WHERE id = $1 AND epoch = $2 AND leader_addr = $4", + &[&LEASE_ID, &epoch, &LEASE_TTL_SECS.to_string(), &node_id], + ) + .await + .context("failed to renew lease")?; + + Ok(updated == 1) +} + +/// Read the current durable version (`udb_lease.durable_version`). Used by a freshly elected leader +/// to learn the watermark floor it must continue from. +pub async fn current_durable_version(pool: &Pool) -> Result { + let conn = pool + .get() + .await + .context("failed to get connection for durable version read")?; + + let row = conn + .query_opt( + "SELECT durable_version FROM udb_lease WHERE id = $1", + &[&LEASE_ID], + ) + .await + .context("failed to read durable version")?; + + Ok(row.map(|row| row.get::<_, i64>(0)).unwrap_or(0)) +} diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs new file mode 100644 index 0000000000..79f1f1f48b --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -0,0 +1,336 @@ +mod apply; +mod lease; + +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::{Context, Result}; + +use crate::{conflict_tracker::TransactionConflictTracker, transaction::TXN_TIMEOUT}; + +use super::shared::{LEASE_ID, LeaseInfo, PostgresShared, WATERMARK_CHANNEL, commit_channel}; + +/// Max commits resolved+applied per batch (group commit). Amortizes the resolver, Postgres +/// round-trips, and fsync across the batch. +const DRAIN_BATCH_SIZE: i64 = 256; + +/// How often a leader renews its lease. Must be comfortably under `LEASE_TTL_SECS`. +const RENEW_INTERVAL: Duration = Duration::from_secs(3); + +/// Backstop poll cadence so a missed `udb_commit` NOTIFY cannot stall the drain indefinitely. +const POLL_BACKSTOP: Duration = Duration::from_millis(50); + +/// How long a candidate waits before retrying election when another node holds the lease. +const ELECTION_RETRY: Duration = Duration::from_secs(2); + +enum DrainOutcome { + /// Processed zero or more requests; still leader. + Drained, + /// Lost the lease (epoch bumped by a new leader). Step down. + LostLease, +} + +/// Spawn the per-process resolver task. Every node runs this; only the elected leader drains the +/// commit queue. +pub fn spawn(shared: Arc) { + tokio::spawn(run(shared)); +} + +async fn run(shared: Arc) { + loop { + match lease::try_acquire(&shared.pool, &shared.node_id).await { + Ok(Some(acquired)) => { + tracing::info!(epoch = acquired.epoch, node_id = %shared.node_id, "acquired udb leader lease"); + if let Err(err) = lead(&shared, acquired.epoch).await { + tracing::error!(?err, "udb leader loop errored, stepping down"); + } + tracing::info!(epoch = acquired.epoch, "stepped down from udb leader"); + } + Ok(None) => { + tokio::time::sleep(ELECTION_RETRY).await; + } + Err(err) => { + tracing::warn!(?err, "failed udb lease acquire attempt"); + tokio::time::sleep(ELECTION_RETRY).await; + } + } + } +} + +/// Leader main loop: hold the lease, drain the commit queue on wake or poll, and renew the lease. +async fn lead(shared: &Arc, epoch: i64) -> Result<()> { + // Publish our own lease into the cache immediately so our local commits route to us. + shared.set_lease(LeaseInfo { + epoch, + leader_addr: shared.node_id.clone(), + }); + + // The recovery floor: a freshly elected leader has a cold conflict window, so reject commits + // whose read_version predates the floor until the window warms (one TXN_TIMEOUT), forcing + // those followers to take a fresh read_version. + let recovery_version = recovery_floor(shared).await?; + let recovery_deadline = Instant::now() + TXN_TIMEOUT; + + let tracker = TransactionConflictTracker::new(); + + let mut wake_rx = shared + .listener + .listen(&commit_channel(&shared.node_id)) + .await; + + let mut renew_interval = tokio::time::interval(RENEW_INTERVAL); + renew_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut poll_interval = tokio::time::interval(POLL_BACKSTOP); + poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + // Drain anything already queued before our first wake. + if matches!( + drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, + DrainOutcome::LostLease + ) { + return Ok(()); + } + + loop { + tokio::select! { + _ = renew_interval.tick() => { + if !lease::renew(&shared.pool, &shared.node_id, epoch).await? { + tracing::warn!(epoch, "lost udb lease on renew"); + return Ok(()); + } + } + res = wake_rx.recv() => { + match res { + Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + wake_rx = shared.listener.listen(&commit_channel(&shared.node_id)).await; + } + } + if matches!( + drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, + DrainOutcome::LostLease + ) { + return Ok(()); + } + } + _ = poll_interval.tick() => { + if matches!( + drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, + DrainOutcome::LostLease + ) { + return Ok(()); + } + } + } + } +} + +/// The version floor a freshly elected leader continues from: the higher of the durable watermark +/// and the sequence high-water. The LOGGED `udb_version_seq` is crash-safe, so this never regresses. +async fn recovery_floor(shared: &Arc) -> Result { + let durable = lease::current_durable_version(&shared.pool).await?; + + let conn = shared + .pool + .get() + .await + .context("failed to get connection for recovery floor")?; + let seq_high: i64 = conn + .query_one("SELECT last_value FROM udb_version_seq", &[]) + .await + .context("failed to read sequence high water")? + .get(0); + + Ok(durable.max(seq_high).max(0) as u64) +} + +/// Drain pending commit requests in id-ordered batches until none remain. Each batch resolves and +/// applies inside a single Postgres transaction (group commit), fenced on the leader's epoch. +async fn drain( + shared: &Arc, + epoch: i64, + tracker: &TransactionConflictTracker, + recovery_version: u64, + recovery_deadline: Instant, +) -> Result { + loop { + match drain_batch(shared, epoch, tracker, recovery_version, recovery_deadline).await? { + BatchOutcome::Empty => return Ok(DrainOutcome::Drained), + BatchOutcome::Processed => {} + BatchOutcome::LostLease => return Ok(DrainOutcome::LostLease), + } + } +} + +enum BatchOutcome { + Empty, + Processed, + LostLease, +} + +struct Reply { + channel: String, + id: i64, +} + +async fn drain_batch( + shared: &Arc, + epoch: i64, + tracker: &TransactionConflictTracker, + recovery_version: u64, + recovery_deadline: Instant, +) -> Result { + let mut conn = shared + .pool + .get() + .await + .context("failed to get connection for drain batch")?; + let txn = conn + .build_transaction() + .start() + .await + .context("failed to start drain batch txn")?; + + // Claim a batch in id order. FOR UPDATE SKIP LOCKED holds the rows for this txn so they are + // stamped terminal on COMMIT with no intermediate 'claimed' state to clean up. + let rows = txn + .query( + "SELECT id, read_version, payload, reply_channel + FROM udb_commit_requests + WHERE status = 'pending' AND epoch = $1 + ORDER BY id + LIMIT $2 + FOR UPDATE SKIP LOCKED", + &[&epoch, &DRAIN_BATCH_SIZE], + ) + .await + .context("failed to claim commit batch")?; + + if rows.is_empty() { + txn.rollback().await.ok(); + return Ok(BatchOutcome::Empty); + } + + let cold_window = Instant::now() < recovery_deadline; + let mut max_winner_cv: i64 = 0; + let mut replies = Vec::with_capacity(rows.len()); + + for row in &rows { + let id: i64 = row.get(0); + let read_version: i64 = row.get(1); + let payload: Vec = row.get(2); + let reply_channel: String = row.get(3); + + let decoded = super::codec::decode_commit_request(&payload) + .context("failed to decode commit payload")?; + + let commit_version: i64 = txn + .query_one("SELECT nextval('udb_version_seq')", &[]) + .await + .context("failed to get next commit version")? + .get(0); + + let start_version = read_version.max(0) as u64; + + // Cold-window guard: a commit whose read_version predates the recovery floor cannot be + // safely resolved against this leader's empty window. Reject it as retryable. + let conflicted = if cold_window && start_version < recovery_version { + true + } else { + tracker + .check_and_insert( + start_version, + commit_version.max(0) as u64, + decoded.conflict_ranges, + ) + .await + }; + + if conflicted { + txn.execute( + "UPDATE udb_commit_requests SET status = 'conflict' WHERE id = $1", + &[&id], + ) + .await + .context("failed to stamp conflict")?; + } else { + apply::apply(&txn, decoded.operations, commit_version.max(0) as u64) + .await + .context("failed to apply commit")?; + txn.execute( + "UPDATE udb_commit_requests SET status = 'committed', commit_version = $1 WHERE id = $2", + &[&commit_version, &id], + ) + .await + .context("failed to stamp committed")?; + max_winner_cv = max_winner_cv.max(commit_version); + } + + replies.push(Reply { + channel: reply_channel, + id, + }); + } + + // Advance the watermark, fenced on our epoch. A zombie old leader whose epoch was bumped sees + // zero rows updated and must step down before any of its writes become visible. + let new_durable: i64 = match txn + .query_opt( + "UPDATE udb_lease + SET durable_version = GREATEST(durable_version, $1) + WHERE id = $2 AND epoch = $3 + RETURNING durable_version", + &[&max_winner_cv, &LEASE_ID, &epoch], + ) + .await + .context("failed to advance watermark")? + { + Some(row) => row.get(0), + None => { + txn.rollback().await.ok(); + return Ok(BatchOutcome::LostLease); + } + }; + + txn.commit().await.context("failed to commit drain batch")?; + + // Watermark advances strictly after the apply txn is durably committed and visible, so a + // reader handed this read_version can never miss a write with commit_version <= read_version. + shared.advance_durable_version(new_durable); + + notify_after_commit(&conn, new_durable, &replies).await; + + Ok(BatchOutcome::Processed) +} + +/// Wake watermark listeners and the followers waiting on each processed request. Best-effort: a +/// missed NOTIFY is covered by the follower's polling backstop and the watermark refresh timer. +async fn notify_after_commit( + conn: &deadpool_postgres::Client, + new_durable: i64, + replies: &[Reply], +) { + if let Err(err) = conn + .execute( + "SELECT pg_notify($1, $2)", + &[&WATERMARK_CHANNEL, &new_durable.to_string()], + ) + .await + { + tracing::debug!(?err, "failed to notify watermark"); + } + + let channels: Vec<&str> = replies.iter().map(|r| r.channel.as_str()).collect(); + let ids: Vec = replies.iter().map(|r| r.id.to_string()).collect(); + if let Err(err) = conn + .execute( + "SELECT pg_notify(c, p) FROM unnest($1::text[], $2::text[]) AS t(c, p)", + &[&channels, &ids], + ) + .await + { + tracing::debug!(?err, "failed to notify commit replies"); + } +} diff --git a/engine/packages/universaldb/src/driver/postgres/shared.rs b/engine/packages/universaldb/src/driver/postgres/shared.rs new file mode 100644 index 0000000000..0ab2963158 --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/shared.rs @@ -0,0 +1,161 @@ +use std::{ + sync::{ + Arc, + atomic::{AtomicI64, Ordering}, + }, + time::Duration, +}; + +use deadpool_postgres::Pool; +use tokio::sync::{Notify, watch}; + +use super::listener::PgListener; + +/// The singleton row id of `udb_lease`. +pub const LEASE_ID: i32 = 1; + +/// How often the follower refreshes its cached lease row (epoch, leader channel, watermark) as a +/// backstop to the `udb_watermark` NOTIFY. A stale-but-older watermark only widens the conflict +/// window, so this can be loose. +const LEASE_REFRESH_INTERVAL: Duration = Duration::from_millis(500); + +/// Channel a follower NOTIFYs (and the leader LISTENs) to wake the leader's drain loop. +pub fn commit_channel(node_id: &str) -> String { + format!("udb_commit_{node_id}") +} + +/// Channel the leader NOTIFYs (and a follower LISTENs) to deliver a commit result. +pub fn reply_channel(node_id: &str) -> String { + format!("udb_reply_{node_id}") +} + +/// Channel the leader NOTIFYs on every watermark advance; all nodes LISTEN. +pub const WATERMARK_CHANNEL: &str = "udb_watermark"; + +/// Cached view of the current leader lease, as seen by a follower. +#[derive(Clone, Debug)] +pub struct LeaseInfo { + pub epoch: i64, + /// Node id of the current leader, used to build its commit channel. + pub leader_addr: String, +} + +/// Process-wide state shared by the follower transaction tasks and the leader resolver. Every node +/// is both a follower (it submits its own commits) and a candidate leader. +pub struct PostgresShared { + pub pool: Pool, + /// Unique per-process id used to name this node's NOTIFY channels. + pub node_id: String, + pub listener: PgListener, + /// Highest durable commit version (`udb_lease.durable_version`); the follower read version. + durable_version: AtomicI64, + /// Pinged whenever `durable_version` advances. + watermark_notify: Notify, + lease_tx: watch::Sender>, + lease_rx: watch::Receiver>, +} + +impl PostgresShared { + pub fn new(pool: Pool, node_id: String, listener: PgListener) -> Arc { + let (lease_tx, lease_rx) = watch::channel(None); + let shared = Arc::new(Self { + pool, + node_id, + listener, + durable_version: AtomicI64::new(0), + watermark_notify: Notify::new(), + lease_tx, + lease_rx, + }); + + tokio::spawn(Self::cache_refresh_task(shared.clone())); + + shared + } + + /// The cached follower read version (`durable_version`). + pub fn read_version(&self) -> i64 { + self.durable_version.load(Ordering::SeqCst) + } + + /// Advance the cached watermark monotonically and wake any waiters. + pub fn advance_durable_version(&self, version: i64) { + let prev = self.durable_version.fetch_max(version, Ordering::SeqCst); + if version > prev { + self.watermark_notify.notify_waiters(); + } + } + + /// Current cached lease, if known. + pub fn current_lease(&self) -> Option { + self.lease_rx.borrow().clone() + } + + /// Publish a freshly observed/elected lease into the cache. + pub fn set_lease(&self, lease: LeaseInfo) { + let _ = self.lease_tx.send(Some(lease)); + } + + /// Background task: keep `durable_version` and the cached lease fresh via the `udb_watermark` + /// NOTIFY plus a periodic poll of `udb_lease`. + async fn cache_refresh_task(shared: Arc) { + let mut watermark_rx = shared.listener.listen(WATERMARK_CHANNEL).await; + let mut interval = tokio::time::interval(LEASE_REFRESH_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + tokio::select! { + notify = watermark_rx.recv() => { + match notify { + Ok(payload) => { + if let Ok(version) = payload.parse::() { + shared.advance_durable_version(version); + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + // Re-subscribe; the listener recreates the channel on reconnect. + watermark_rx = shared.listener.listen(WATERMARK_CHANNEL).await; + } + } + } + _ = interval.tick() => { + shared.refresh_lease_row().await; + } + } + } + } + + async fn refresh_lease_row(&self) { + let conn = match self.pool.get().await { + Ok(conn) => conn, + Err(err) => { + tracing::debug!(?err, "failed to get connection for lease refresh"); + return; + } + }; + + let row = conn + .query_opt( + "SELECT epoch, leader_addr, durable_version FROM udb_lease WHERE id = $1", + &[&LEASE_ID], + ) + .await; + + match row { + Ok(Some(row)) => { + let epoch: i64 = row.get(0); + let leader_addr: String = row.get(1); + let durable_version: i64 = row.get(2); + self.advance_durable_version(durable_version); + self.set_lease(LeaseInfo { epoch, leader_addr }); + } + Ok(None) => { + // No lease row yet; no leader elected. + } + Err(err) => { + tracing::debug!(?err, "failed to refresh lease row"); + } + } + } +} diff --git a/engine/packages/universaldb/src/driver/postgres/transaction.rs b/engine/packages/universaldb/src/driver/postgres/transaction.rs index f80cbba393..cc315feba4 100644 --- a/engine/packages/universaldb/src/driver/postgres/transaction.rs +++ b/engine/packages/universaldb/src/driver/postgres/transaction.rs @@ -1,11 +1,13 @@ use std::{ future::Future, pin::Pin, - sync::atomic::{AtomicBool, Ordering}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, }; use anyhow::{Context, Result}; -use deadpool_postgres::Pool; use tokio::sync::{OnceCell, mpsc, oneshot}; use crate::{ @@ -18,33 +20,35 @@ use crate::{ value::{Slice, Value, Values}, }; -use super::transaction_task::{TransactionCommand, TransactionTask}; +use super::{ + shared::PostgresShared, + transaction_task::{TransactionCommand, TransactionTask}, +}; pub struct PostgresTransactionDriver { - pool: Pool, + shared: Arc, operations: TransactionOperations, committed: AtomicBool, tx_sender: OnceCell>, } impl PostgresTransactionDriver { - pub fn with_config(pool: Pool) -> Self { + pub fn new(shared: Arc) -> Self { PostgresTransactionDriver { - pool, + shared, operations: TransactionOperations::default(), committed: AtomicBool::new(false), tx_sender: OnceCell::new(), } } - /// Get or create the transaction task + /// Get or create the transaction task that owns this transaction's read snapshot. async fn ensure_transaction(&self) -> Result<&mpsc::UnboundedSender> { self.tx_sender .get_or_try_init(|| async { let (sender, receiver) = mpsc::unbounded_channel(); - // Spawn the transaction task with serializable isolation - let task = TransactionTask::new(self.pool.clone(), receiver); + let task = TransactionTask::new(self.shared.clone(), receiver); tokio::spawn(task.run()); anyhow::Ok(sender) diff --git a/engine/packages/universaldb/src/driver/postgres/transaction_task.rs b/engine/packages/universaldb/src/driver/postgres/transaction_task.rs index 297751f608..c4d3e3f9f6 100644 --- a/engine/packages/universaldb/src/driver/postgres/transaction_task.rs +++ b/engine/packages/universaldb/src/driver/postgres/transaction_task.rs @@ -1,17 +1,18 @@ -use anyhow::{Context, Result, anyhow, bail}; -use deadpool_postgres::{Pool, Transaction}; +use std::sync::Arc; + +use anyhow::{Result, anyhow, bail}; +use deadpool_postgres::Transaction; use tokio::sync::{mpsc, oneshot}; use tokio_postgres::IsolationLevel; use crate::{ - atomic::apply_atomic_op, - error::DatabaseError, - options::{ConflictRangeType, MutationType}, + options::ConflictRangeType, tx_ops::Operation, value::{KeyValue, Slice, Values}, - versionstamp::{generate_versionstamp, substitute_raw_versionstamp}, }; +use super::{commit, shared::PostgresShared}; + pub enum TransactionCommand { // Read operations Get { @@ -48,70 +49,57 @@ pub enum TransactionCommand { }, } -/// TransactionTask runs in a separate tokio task to manage a PostgreSQL transaction. -/// -/// This design is necessary because PostgreSQL transactions have lifetime constraints -/// that don't work well with the FoundationDB-style API. Specifically: -/// - The transaction must outlive all references to it -/// - We can't store the transaction in a mutex due to lifetime issues with the connection +/// TransactionTask runs in a separate tokio task to own a single pinned PostgreSQL `REPEATABLE READ` +/// snapshot connection for the lifetime of a follower transaction. /// -/// By running in a separate task and communicating via channels, we avoid these lifetime -/// issues while maintaining a single serializable transaction for all operations. +/// Reads go directly against this snapshot (they never involve the leader). Commits delegate to +/// [`commit::submit`], which enqueues the request on the leader and awaits the result. The +/// `read_version` is captured from the cached watermark before the snapshot is opened, so no write +/// with `commit_version <= read_version` can be invisible to the snapshot. pub struct TransactionTask { - pool: Pool, + shared: Arc, receiver: mpsc::UnboundedReceiver, } impl TransactionTask { - pub fn new(pool: Pool, receiver: mpsc::UnboundedReceiver) -> Self { - Self { pool, receiver } + pub fn new( + shared: Arc, + receiver: mpsc::UnboundedReceiver, + ) -> Self { + Self { shared, receiver } } pub async fn run(mut self) { - // Get connection from pool - let mut conn = match self.pool.get().await { + // Capture the read version BEFORE opening the snapshot so the snapshot reflects every write + // with commit_version <= read_version. + let read_version = self.shared.read_version(); + + let mut conn = match self.shared.pool.get().await { Ok(conn) => conn, Err(_) => { - // If we can't get a connection, respond to all pending commands with errors self.fail_receiver().await; return; } }; - // Start the read transaction let tx = match conn .build_transaction() .isolation_level(IsolationLevel::RepeatableRead) + .read_only(true) .start() .await { Ok(tx) => tx, Err(_) => { - // If we can't start a transaction, respond to all pending commands with errors self.fail_receiver().await; return; } }; - // TODO: Parallelize future - let start_version = match tx - .query_one("SELECT nextval('global_version_seq')", &[]) - .await - { - Ok(row) => row.get::<_, i64>(0), - Err(err) => { - tracing::error!(?err, "failed to get postgres txn start_version"); - self.fail_receiver().await; - return; - } - }; - - // Process commands while let Some(cmd) = self.receiver.recv().await { match cmd { TransactionCommand::Get { key, response } => { let result = self.handle_get(&tx, &key).await; - let _ = response.send(result); } TransactionCommand::GetKey { @@ -121,7 +109,6 @@ impl TransactionTask { response, } => { let result = self.handle_get_key(&tx, &key, or_equal, offset).await; - let _ = response.send(result); } TransactionCommand::GetRange { @@ -148,7 +135,6 @@ impl TransactionTask { reverse, ) .await; - let _ = response.send(result); } TransactionCommand::Commit { @@ -156,14 +142,12 @@ impl TransactionTask { conflict_ranges, response, } => { - let (_, result) = tokio::join!( - // Read-only txn, we don't care about the result - tx.commit(), - self.handle_commit(start_version, operations, conflict_ranges), - ); - + // The read snapshot is read-only; release it and submit the commit to the leader. + let _ = tx.commit().await; + let result = + commit::submit(&self.shared, read_version, operations, conflict_ranges) + .await; let _ = response.send(result); - // Exit after commit return; } TransactionCommand::GetEstimatedRangeSize { @@ -174,13 +158,12 @@ impl TransactionTask { let result = self .handle_get_estimated_range_size(&tx, &begin, &end) .await; - let _ = response.send(result); } } } - // If the channel is closed, the transaction will be rolled back when dropped + // If the channel is closed, the snapshot transaction is rolled back when dropped. } async fn handle_get(&mut self, tx: &Transaction<'_>, key: &[u8]) -> Result> { @@ -234,27 +217,18 @@ impl TransactionTask { reverse: bool, ) -> Result { // Determine SQL operators based on key selector types - // For begin selector: - // first_greater_or_equal: or_equal = false, offset = 1 -> ">=" - // first_greater_than: or_equal = true, offset = 1 -> ">" let begin_op = if begin_offset == 1 { if begin_or_equal { ">" } else { ">=" } } else { - // This shouldn't happen for begin in range queries ">=" }; - // For end selector: - // first_greater_than: or_equal = true, offset = 1 -> "<=" - // first_greater_or_equal: or_equal = false, offset = 1 -> "<" let end_op = if end_offset == 1 { if end_or_equal { "<=" } else { "<" } } else { - // This shouldn't happen for end in range queries "<" }; - // Build query with CTE that adds conflict range let query = if reverse { if let Some(limit) = limit { format!( @@ -301,22 +275,22 @@ impl TransactionTask { begin: &[u8], end: &[u8], ) -> Result { - // Sample's 1% of the range + // Sample 1% of the range. let query = " WITH range_stats AS ( - SELECT + SELECT COUNT(*) as estimated_count, COALESCE(SUM(pg_column_size(key) + pg_column_size(value)), 0) as sample_size - FROM kv TABLESAMPLE SYSTEM(1) + FROM kv TABLESAMPLE SYSTEM(1) WHERE key >= $1 AND key < $2 ), table_stats AS ( - SELECT reltuples::bigint as total_rows - FROM pg_class + SELECT reltuples::bigint as total_rows + FROM pg_class WHERE relname = 'kv' AND relkind = 'r' ) - SELECT - CASE + SELECT + CASE WHEN r.estimated_count = 0 THEN 0 ELSE (r.sample_size * 100)::bigint END as estimated_size @@ -329,165 +303,6 @@ impl TransactionTask { .map_err(map_postgres_error) } - async fn handle_commit( - &mut self, - start_version: i64, - operations: Vec, - conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, - ) -> Result<()> { - // Get connection from pool - let mut conn = self.pool.get().await?; - - // Start write transaction - let tx = conn - .build_transaction() - .isolation_level(IsolationLevel::ReadCommitted) - .start() - .await - .context("failed to start write txn")?; - - let mut begins = Vec::with_capacity(conflict_ranges.len()); - let mut ends = Vec::with_capacity(conflict_ranges.len()); - let mut conflict_types = Vec::with_capacity(conflict_ranges.len()); - - for (begin, end, conflict_type) in conflict_ranges { - let conflict_type = match conflict_type { - ConflictRangeType::Read => "read", - ConflictRangeType::Write => "write", - }; - - begins.push(begin); - ends.push(end); - conflict_types.push(conflict_type); - } - - let query = " - WITH data AS ( - SELECT nextval('global_version_seq') AS commit_version - ) - INSERT INTO conflict_ranges (range_data, conflict_type, start_version, commit_version) - SELECT - bytearange(begin_key, end_key, '[)'), - conflict_type::range_type, - $4, - data.commit_version - FROM UNNEST($1::bytea[], $2::bytea[], $3::text[]) AS t(begin_key, end_key, conflict_type), data"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - // Insert all conflict ranges at once - tx.execute(&stmt, &[&begins, &ends, &conflict_types, &start_version]) - .await - .map_err(map_postgres_error)?; - - let transaction_versionstamp = generate_versionstamp(0); - - for op in operations { - match op { - Operation::SetValue { key, value } => { - let query = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key, &value]) - .await - .map_err(map_postgres_error)?; - } - Operation::Clear { key } => { - let query = "DELETE FROM kv WHERE key = $1"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key]) - .await - .map_err(map_postgres_error)?; - } - Operation::ClearRange { begin, end } => { - let query = "DELETE FROM kv WHERE key >= $1 AND key < $2"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&begin, &end]) - .await - .map_err(map_postgres_error)?; - } - Operation::AtomicOp { - key, - param, - op_type, - } => { - if matches!(op_type, MutationType::SetVersionstampedKey) { - let key = substitute_raw_versionstamp(key, &transaction_versionstamp) - .map_err(anyhow::Error::msg) - .context("failed substituting versionstamped key")?; - let query = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key, ¶m]) - .await - .map_err(map_postgres_error)?; - continue; - } - - if matches!(op_type, MutationType::SetVersionstampedValue) { - let value = substitute_raw_versionstamp(param, &transaction_versionstamp) - .map_err(anyhow::Error::msg) - .context("failed substituting versionstamped value")?; - let query = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2"; - let stmt = tx.prepare_cached(query).await.map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key, &value]) - .await - .map_err(map_postgres_error)?; - continue; - } - - // TODO: All operations need to be done on the sql side, not in rust - - // Get current value from database - let current_query = "SELECT value FROM kv WHERE key = $1"; - let stmt = tx - .prepare_cached(current_query) - .await - .map_err(map_postgres_error)?; - - let current_row = tx - .query_opt(&stmt, &[&key]) - .await - .map_err(map_postgres_error)?; - - // Extract current value or use None if key doesn't exist - let current_value = current_row.map(|row| row.get::<_, Vec>(0)); - let current_slice = current_value.as_deref(); - - // Apply atomic operation - let new_value = apply_atomic_op(current_slice, ¶m, op_type); - - // Store the result - if let Some(new_value) = new_value { - let update_query = "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2"; - let stmt = tx - .prepare_cached(update_query) - .await - .map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key, &new_value]) - .await - .map_err(map_postgres_error)?; - } else { - let update_query = "DELETE FROM kv WHERE key = $1"; - let stmt = tx - .prepare_cached(update_query) - .await - .map_err(map_postgres_error)?; - - tx.execute(&stmt, &[&key]) - .await - .map_err(map_postgres_error)?; - } - } - } - } - - tx.commit().await.map_err(map_postgres_error) - } - async fn fail_receiver(&mut self) { while let Some(cmd) = self.receiver.recv().await { match cmd { @@ -511,31 +326,19 @@ impl TransactionTask { } } -/// Maps PostgreSQL error to DatabaseError +/// Maps a PostgreSQL error from the read path to a `DatabaseError` where appropriate. fn map_postgres_error(err: tokio_postgres::Error) -> anyhow::Error { - let error_str = if let Some(err) = err.as_db_error() { - err.to_string() - } else { - err.to_string() - }; + let error_str = err.to_string(); - if error_str.contains("exclusion_violation") - || error_str.contains("violates exclusion constraint") - { - // Retryable - another transaction has a conflicting range - DatabaseError::NotCommitted.into() - } else if error_str.contains("serialization failure") + if error_str.contains("serialization failure") || error_str.contains("could not serialize") || error_str.contains("deadlock detected") { - // Retryable - transaction conflict - DatabaseError::NotCommitted.into() + crate::error::DatabaseError::NotCommitted.into() } else if error_str.contains("current transaction is aborted") { - // Returned by the rest of the commands in a txn if it failed for exclusion reasons - DatabaseError::NotCommitted.into() + crate::error::DatabaseError::NotCommitted.into() } else { tracing::error!(%err, "postgres error"); - // Non-retryable error anyhow::Error::new(err) } } diff --git a/engine/packages/universaldb/src/driver/rocksdb/database.rs b/engine/packages/universaldb/src/driver/rocksdb/database.rs index ec0175ee5a..ba186d872e 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/database.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/database.rs @@ -17,9 +17,9 @@ use crate::{ utils::{MaybeCommitted, calculate_tx_retry_backoff}, }; -use super::{ - transaction::RocksDbTransactionDriver, transaction_conflict_tracker::TransactionConflictTracker, -}; +use crate::conflict_tracker::TransactionConflictTracker; + +use super::transaction::RocksDbTransactionDriver; pub struct RocksDbDatabaseDriver { db: Arc, diff --git a/engine/packages/universaldb/src/driver/rocksdb/mod.rs b/engine/packages/universaldb/src/driver/rocksdb/mod.rs index a24bd72603..b18e28edca 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/mod.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/mod.rs @@ -1,6 +1,5 @@ mod database; mod transaction; -mod transaction_conflict_tracker; mod transaction_task; pub use database::RocksDbDatabaseDriver; diff --git a/engine/packages/universaldb/src/driver/rocksdb/transaction.rs b/engine/packages/universaldb/src/driver/rocksdb/transaction.rs index e62c3eda79..85cf5edf5c 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/transaction.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/transaction.rs @@ -21,10 +21,9 @@ use crate::{ value::{Slice, Value, Values}, }; -use super::{ - transaction_conflict_tracker::TransactionConflictTracker, - transaction_task::{TransactionCommand, TransactionTask}, -}; +use crate::conflict_tracker::TransactionConflictTracker; + +use super::transaction_task::{TransactionCommand, TransactionTask}; pub struct RocksDbTransactionDriver { db: Arc, diff --git a/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs b/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs index 69835eb6a7..986e6ba2bd 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs @@ -6,9 +6,9 @@ use rocksdb::{ }; use tokio::sync::{mpsc, oneshot}; -use super::transaction_conflict_tracker::TransactionConflictTracker; use crate::{ atomic::apply_atomic_op, + conflict_tracker::TransactionConflictTracker, error::DatabaseError, key_selector::KeySelector, options::{ConflictRangeType, MutationType}, @@ -412,9 +412,11 @@ impl TransactionTask { } } + // rocksdb generates both start and commit versions from the in-process counter. + let commit_version = self.txn_conflict_tracker.next_global_version(); if self .txn_conflict_tracker - .check_and_insert(start_version, conflict_ranges) + .check_and_insert(start_version, commit_version, conflict_ranges) .await { return Err(DatabaseError::NotCommitted.into()); diff --git a/engine/packages/universaldb/src/lib.rs b/engine/packages/universaldb/src/lib.rs index 96260a177d..1b6fb78b87 100644 --- a/engine/packages/universaldb/src/lib.rs +++ b/engine/packages/universaldb/src/lib.rs @@ -1,4 +1,5 @@ pub(crate) mod atomic; +pub(crate) mod conflict_tracker; mod database; pub mod driver; pub mod error; diff --git a/engine/packages/universaldb/tests/integration.rs b/engine/packages/universaldb/tests/integration.rs index 52fa703116..45a5b3bdf7 100644 --- a/engine/packages/universaldb/tests/integration.rs +++ b/engine/packages/universaldb/tests/integration.rs @@ -137,11 +137,9 @@ async fn test_database_options(db: &Database) { use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use universaldb::error::DatabaseError; - use universaldb::options::DatabaseOption; // Test setting transaction retry limit - db.set_option(DatabaseOption::TransactionRetryLimit(5)) - .unwrap(); + db.txn_retry_limit(5).unwrap(); // Test that retry limit is respected by forcing conflicts let conflict_counter = Arc::new(AtomicU32::new(0)); @@ -172,8 +170,7 @@ async fn test_database_options(db: &Database) { assert_eq!(final_attempts, 3, "Should have taken 3 attempts"); // Now set a very low retry limit and verify it fails - db.set_option(DatabaseOption::TransactionRetryLimit(1)) - .unwrap(); + db.txn_retry_limit(1).unwrap(); let conflict_counter2 = Arc::new(AtomicU32::new(0)); let counter_clone2 = conflict_counter2.clone(); @@ -204,8 +201,7 @@ async fn test_database_options(db: &Database) { assert!(attempts <= 2, "Should not retry more than limit + 1"); // Reset to a reasonable retry limit - db.set_option(DatabaseOption::TransactionRetryLimit(100)) - .unwrap(); + db.txn_retry_limit(100).unwrap(); } async fn clear_test_namespace(db: &Database) -> Result<()> { diff --git a/engine/sdks/rust/depot-protocol/Cargo.toml b/engine/sdks/rust/depot-protocol/Cargo.toml index 274836220a..99d8117349 100644 --- a/engine/sdks/rust/depot-protocol/Cargo.toml +++ b/engine/sdks/rust/depot-protocol/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true +rivet-util.workspace = true serde_bare.workspace = true serde.workspace = true vbare.workspace = true diff --git a/engine/sdks/rust/universaldb-commit/Cargo.toml b/engine/sdks/rust/universaldb-commit/Cargo.toml new file mode 100644 index 0000000000..126c5cb35e --- /dev/null +++ b/engine/sdks/rust/universaldb-commit/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rivet-universaldb-commit" +publish = false +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +serde_bare.workspace = true +serde.workspace = true +vbare.workspace = true + +[build-dependencies] +vbare-compiler.workspace = true diff --git a/engine/sdks/rust/universaldb-commit/build.rs b/engine/sdks/rust/universaldb-commit/build.rs new file mode 100644 index 0000000000..6400be6f2b --- /dev/null +++ b/engine/sdks/rust/universaldb-commit/build.rs @@ -0,0 +1,64 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +fn main() -> Result<(), Box> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + let out_dir = PathBuf::from(std::env::var("OUT_DIR")?); + let workspace_root = Path::new(&manifest_dir) + .parent() + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + .ok_or("Failed to find workspace root")?; + + let schema_dir = workspace_root + .join("sdks") + .join("schemas") + .join("universaldb-commit"); + println!("cargo:rerun-if-changed={}", schema_dir.display()); + + let (highest_version, _) = find_highest_version(&schema_dir); + + let cfg = vbare_compiler::Config::default(); + vbare_compiler::process_schemas_with_config(&schema_dir, &cfg)?; + + // Append protocol version constant to generated file + let combined_imports_path = out_dir.join("combined_imports.rs"); + let mut combined = fs::read_to_string(&combined_imports_path)?; + combined.push_str(&format!( + "\npub const PROTOCOL_VERSION: u16 = {};\n", + highest_version + )); + fs::write(combined_imports_path, combined)?; + + Ok(()) +} + +fn find_highest_version(schema_dir: &Path) -> (u32, PathBuf) { + let mut highest_version = 0; + let mut highest_version_path = PathBuf::new(); + + for entry in fs::read_dir(schema_dir).unwrap().flatten() { + if !entry.path().is_dir() { + let path = entry.path(); + let bare_name = path + .file_name() + .unwrap() + .to_str() + .unwrap() + .split_once('.') + .unwrap() + .0; + + if let Ok(version) = bare_name[1..].parse::() { + if version > highest_version { + highest_version = version; + highest_version_path = path; + } + } + } + } + + (highest_version, highest_version_path) +} diff --git a/engine/sdks/rust/universaldb-commit/src/generated.rs b/engine/sdks/rust/universaldb-commit/src/generated.rs new file mode 100644 index 0000000000..84801af8dc --- /dev/null +++ b/engine/sdks/rust/universaldb-commit/src/generated.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/combined_imports.rs")); diff --git a/engine/sdks/rust/universaldb-commit/src/lib.rs b/engine/sdks/rust/universaldb-commit/src/lib.rs new file mode 100644 index 0000000000..41954bbe75 --- /dev/null +++ b/engine/sdks/rust/universaldb-commit/src/lib.rs @@ -0,0 +1,6 @@ +pub mod generated; +pub mod versioned; + +// Re-export latest +pub use generated::PROTOCOL_VERSION; +pub use generated::v1::*; diff --git a/engine/sdks/rust/universaldb-commit/src/versioned.rs b/engine/sdks/rust/universaldb-commit/src/versioned.rs new file mode 100644 index 0000000000..a9d637aa81 --- /dev/null +++ b/engine/sdks/rust/universaldb-commit/src/versioned.rs @@ -0,0 +1,38 @@ +use anyhow::{Ok, Result, bail}; +use vbare::OwnedVersionedData; + +use crate::generated::v1; + +// Only v1 exists today. When adding v2+, generate converters with +// `scripts/vbare-gen-converters` (see the envoy-protocol package for the +// resulting `versioned/` module layout) and wire them in here. +pub enum CommitRequest { + V1(v1::CommitRequest), +} + +impl OwnedVersionedData for CommitRequest { + type Latest = v1::CommitRequest; + + fn wrap_latest(latest: v1::CommitRequest) -> Self { + CommitRequest::V1(latest) + } + + fn unwrap_latest(self) -> Result { + match self { + CommitRequest::V1(data) => Ok(data), + } + } + + fn deserialize_version(payload: &[u8], version: u16) -> Result { + match version { + 1 => Ok(CommitRequest::V1(serde_bare::from_slice(payload)?)), + _ => bail!("invalid version: {version}"), + } + } + + fn serialize_version(self, _version: u16) -> Result> { + match self { + CommitRequest::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + } + } +} diff --git a/engine/sdks/schemas/universaldb-commit/v1.bare b/engine/sdks/schemas/universaldb-commit/v1.bare new file mode 100644 index 0000000000..2377312f84 --- /dev/null +++ b/engine/sdks/schemas/universaldb-commit/v1.bare @@ -0,0 +1,70 @@ +# Commit-queue wire format for the Postgres leader-resolver UDB driver. +# +# Followers encode a CommitRequest into the `payload` column of +# `udb_commit_requests`; the leader decodes it to resolve and apply. +# Rust-only (never leaves the engine), but versioned so rolling deploys can +# skew follower vs leader code. + +type ConflictRangeType enum { + READ + WRITE +} + +type ConflictRange struct { + begin: data + end: data + kind: ConflictRangeType +} + +# Order MUST match universaldb::options::MutationType declaration order so the +# enum tag round-trips. 15 variants today; never reorder, only append. +type MutationType enum { + ADD + AND + BIT_AND + OR + BIT_OR + XOR + BIT_XOR + APPEND_IF_FITS + MAX + MIN + SET_VERSIONSTAMPED_KEY + SET_VERSIONSTAMPED_VALUE + BYTE_MIN + BYTE_MAX + COMPARE_AND_CLEAR +} + +type SetValue struct { + key: data + value: data +} + +type Clear struct { + key: data +} + +type ClearRange struct { + begin: data + end: data +} + +type AtomicOp struct { + key: data + param: data + opType: MutationType +} + +type Operation union { + SetValue | + Clear | + ClearRange | + AtomicOp +} + +type CommitRequest struct { + readVersion: u64 + conflictRanges: list + operations: list +} diff --git a/scripts/run/postgres.sh b/scripts/run/postgres.sh index 30acd498f1..2ac817b598 100755 --- a/scripts/run/postgres.sh +++ b/scripts/run/postgres.sh @@ -2,7 +2,7 @@ set -euo pipefail CONTAINER_NAME="rivet-engine-postgres" -POSTGRES_IMAGE="postgres:17" +POSTGRES_IMAGE="postgres:18" if docker ps --all --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then if docker ps --format '{{.Names}}' | grep -qw "${CONTAINER_NAME}"; then diff --git a/scripts/run/restore-postgres.sh b/scripts/run/restore-postgres.sh index bd2b50e0b4..0a82410608 100755 --- a/scripts/run/restore-postgres.sh +++ b/scripts/run/restore-postgres.sh @@ -2,7 +2,7 @@ set -euo pipefail CONTAINER_NAME="rivet-engine-postgres" -POSTGRES_IMAGE="postgres:17" +POSTGRES_IMAGE="postgres:18" if [ $# -ne 1 ]; then echo "Usage: $0 " diff --git a/self-host/compose/dev-host/docker-compose.yml b/self-host/compose/dev-host/docker-compose.yml index 3b54702d63..96db5a57d0 100644 --- a/self-host/compose/dev-host/docker-compose.yml +++ b/self-host/compose/dev-host/docker-compose.yml @@ -72,7 +72,7 @@ services: network_mode: host postgres: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/self-host/compose/dev-multidc-multinode/docker-compose.yml b/self-host/compose/dev-multidc-multinode/docker-compose.yml index 75c500850b..7e421144c8 100644 --- a/self-host/compose/dev-multidc-multinode/docker-compose.yml +++ b/self-host/compose/dev-multidc-multinode/docker-compose.yml @@ -85,7 +85,7 @@ services: condition: service_healthy postgres-dc-a: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -355,7 +355,7 @@ services: - rivet-network-dc-a postgres-dc-b: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -617,7 +617,7 @@ services: - rivet-network-dc-b postgres-dc-c: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/self-host/compose/dev-multidc/docker-compose.yml b/self-host/compose/dev-multidc/docker-compose.yml index f20db41ff3..79cb619e0a 100644 --- a/self-host/compose/dev-multidc/docker-compose.yml +++ b/self-host/compose/dev-multidc/docker-compose.yml @@ -85,7 +85,7 @@ services: condition: service_healthy postgres-dc-a: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -235,7 +235,7 @@ services: - rivet-network-dc-a postgres-dc-b: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -377,7 +377,7 @@ services: - rivet-network-dc-b postgres-dc-c: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/self-host/compose/dev-multinode/docker-compose.yml b/self-host/compose/dev-multinode/docker-compose.yml index 381ece396d..a432743057 100644 --- a/self-host/compose/dev-multinode/docker-compose.yml +++ b/self-host/compose/dev-multinode/docker-compose.yml @@ -81,7 +81,7 @@ services: condition: service_healthy postgres: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/self-host/compose/dev/docker-compose.yml b/self-host/compose/dev/docker-compose.yml index 6c5dcc0ef2..91f82dce45 100644 --- a/self-host/compose/dev/docker-compose.yml +++ b/self-host/compose/dev/docker-compose.yml @@ -81,7 +81,7 @@ services: condition: service_healthy postgres: restart: unless-stopped - image: postgres:17-alpine + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/self-host/compose/template/src/docker-compose.ts b/self-host/compose/template/src/docker-compose.ts index ca2f6b1fc8..6db4a8ab9a 100644 --- a/self-host/compose/template/src/docker-compose.ts +++ b/self-host/compose/template/src/docker-compose.ts @@ -171,7 +171,7 @@ export function generateDockerCompose(context: TemplateContext) { ); services[postgresServiceName] = { restart: "unless-stopped", - image: "postgres:17-alpine", + image: "postgres:18-alpine", environment: [ "POSTGRES_USER=postgres", "POSTGRES_PASSWORD=postgres", diff --git a/self-host/k8s/engine/12-postgres-statefulset.yaml b/self-host/k8s/engine/12-postgres-statefulset.yaml index e1e14a4b75..c035d26611 100644 --- a/self-host/k8s/engine/12-postgres-statefulset.yaml +++ b/self-host/k8s/engine/12-postgres-statefulset.yaml @@ -20,7 +20,7 @@ spec: spec: containers: - name: postgres - image: postgres:17 + image: postgres:18 args: - postgres - -c diff --git a/website/src/content/docs/self-hosting/docker-compose.mdx b/website/src/content/docs/self-hosting/docker-compose.mdx index eae884ca99..daf5ab0096 100644 --- a/website/src/content/docs/self-hosting/docker-compose.mdx +++ b/website/src/content/docs/self-hosting/docker-compose.mdx @@ -192,7 +192,7 @@ PostgreSQL is the recommended backend for multi-node self-hosted deployments. It ```yaml services: postgres: - image: postgres:15 + image: postgres:18 environment: POSTGRES_DB: rivet POSTGRES_USER: rivet diff --git a/website/src/content/docs/self-hosting/docker-container.mdx b/website/src/content/docs/self-hosting/docker-container.mdx index e6d4366d68..46d544ddd7 100644 --- a/website/src/content/docs/self-hosting/docker-container.mdx +++ b/website/src/content/docs/self-hosting/docker-container.mdx @@ -161,7 +161,7 @@ docker run -d \ -e POSTGRES_USER=rivet \ -e POSTGRES_PASSWORD=rivet_password \ -v postgres-data:/var/lib/postgresql/data \ - postgres:15 + postgres:18 # Run Rivet Engine docker run -d \ From 2534d5c84a46ec35446378cdb9f6a26e764070d6 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 12:00:32 -0700 Subject: [PATCH 09/19] [SLOP(claude-opus-4-8-high)] feat(ups): table-backed postgres transport with coalesced doorbell --- .../src/driver/postgres/doorbell.rs | 133 ++++ .../src/driver/postgres/mod.rs | 692 +++++++++--------- 2 files changed, 477 insertions(+), 348 deletions(-) create mode 100644 engine/packages/universalpubsub/src/driver/postgres/doorbell.rs diff --git a/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs b/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs new file mode 100644 index 0000000000..f08ff7bbf3 --- /dev/null +++ b/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs @@ -0,0 +1,133 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; + +use deadpool_postgres::Pool; +use tokio::sync::Notify; +use tokio::time::Instant; + +/// Number of doorbell shards. A subject maps to a shard via `hash(subject_hash) % K`. +/// Subscribers LISTEN their subject's shard channel; publishers wake the local +/// doorbell task which NOTIFYs the shard. +pub const DOORBELL_SHARD_COUNT: usize = 32; + +/// Debounce window. Caps each (process, shard) NOTIFY rate at one per window, which +/// bounds how many backends are woken per shard over time. +const DOORBELL_WINDOW: Duration = Duration::from_millis(5); + +/// Returns the NOTIFY channel name for a doorbell shard. +pub fn shard_channel(shard: usize) -> String { + format!("ups_db_{shard}") +} + +/// Returns the doorbell shard for a subject hash. +pub fn shard_for(subject_hash: &str) -> usize { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + subject_hash.hash(&mut hasher); + (hasher.finish() as usize) % DOORBELL_SHARD_COUNT +} + +/// Coalesced, payload-free NOTIFY doorbell. +/// +/// Publishers call [`Doorbell::mark_dirty`] after committing a row. A single +/// per-process task drains dirty shards and emits at most one NOTIFY per shard per +/// debounce window using leading-edge fire plus a trailing-edge flush. The doorbell +/// is a latency optimization only. Correctness comes from the table plus the +/// subscriber poll backstop, so a dropped or failed NOTIFY only adds latency. +pub struct Doorbell { + dirty: [AtomicBool; DOORBELL_SHARD_COUNT], + notify: Notify, + pool: Arc, +} + +impl Doorbell { + pub fn new(pool: Arc) -> Arc { + let doorbell = Arc::new(Self { + dirty: std::array::from_fn(|_| AtomicBool::new(false)), + notify: Notify::new(), + pool, + }); + + let task_doorbell = doorbell.clone(); + tokio::spawn(async move { task_doorbell.run().await }); + + doorbell + } + + /// Marks a shard dirty and wakes the doorbell task. Never blocks. + pub fn mark_dirty(&self, shard: usize) { + self.dirty[shard].store(true, Ordering::Release); + self.notify.notify_one(); + } + + async fn run(self: Arc) { + // Per-shard timestamp of the last NOTIFY emitted by this process. + let mut last_notify: [Option; DOORBELL_SHARD_COUNT] = [None; DOORBELL_SHARD_COUNT]; + // Per-shard deadline for a pending trailing-edge NOTIFY, if any. + let mut trailing: [Option; DOORBELL_SHARD_COUNT] = [None; DOORBELL_SHARD_COUNT]; + + loop { + // Arm on the next pending trailing deadline so the trailing edge fires + // even with no further publishes. Wait on the notify permit otherwise. + let next_deadline = trailing.iter().filter_map(|x| *x).min(); + match next_deadline { + Some(deadline) => { + tokio::select! { + _ = self.notify.notified() => {} + _ = tokio::time::sleep_until(deadline) => {} + } + } + None => { + self.notify.notified().await; + } + } + + let now = Instant::now(); + for shard in 0..DOORBELL_SHARD_COUNT { + let is_dirty = self.dirty[shard].swap(false, Ordering::AcqRel); + if is_dirty { + match last_notify[shard] { + Some(last) if now.duration_since(last) < DOORBELL_WINDOW => { + // Within the window. Defer to a trailing-edge NOTIFY at + // window end so at most one NOTIFY fires per shard per W. + if trailing[shard].is_none() { + trailing[shard] = Some(last + DOORBELL_WINDOW); + } + } + _ => { + // Leading edge. Fire immediately for low idle latency. + self.notify_shard(shard).await; + last_notify[shard] = Some(now); + trailing[shard] = None; + } + } + } + + // Flush a trailing-edge NOTIFY whose window has elapsed. + if let Some(deadline) = trailing[shard] { + if now >= deadline { + self.notify_shard(shard).await; + last_notify[shard] = Some(now); + trailing[shard] = None; + } + } + } + } + } + + async fn notify_shard(&self, shard: usize) { + let channel = shard_channel(shard); + match self.pool.get().await { + Ok(conn) => { + // Payload-free doorbell. The payload lives in the table. + if let Err(err) = conn.execute("SELECT pg_notify($1, '')", &[&channel]).await { + tracing::warn!(?err, %channel, "failed to emit doorbell notify"); + } + } + Err(err) => { + tracing::warn!(?err, %channel, "failed to get connection for doorbell notify"); + } + } + } +} diff --git a/engine/packages/universalpubsub/src/driver/postgres/mod.rs b/engine/packages/universalpubsub/src/driver/postgres/mod.rs index 52bc219b2a..d361eb1149 100644 --- a/engine/packages/universalpubsub/src/driver/postgres/mod.rs +++ b/engine/packages/universalpubsub/src/driver/postgres/mod.rs @@ -1,12 +1,11 @@ -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result}; use async_trait::async_trait; -use base64::Engine; -use base64::engine::general_purpose::STANDARD_NO_PAD as BASE64; use deadpool_postgres::{Config, ManagerConfig, Pool, PoolConfig, RecyclingMethod, Runtime}; use futures_util::future::poll_fn; use rivet_postgres_util::build_tls_config; use rivet_util::throttle::Backoff; use scc::HashMap; +use std::collections::VecDeque; use std::hash::{DefaultHasher, Hash, Hasher}; use std::path::PathBuf; use std::sync::Arc; @@ -21,37 +20,37 @@ use crate::driver::{PubSubDriver, SubscriberDriver, SubscriberDriverHandle}; use crate::metrics; use crate::pubsub::DriverOutput; -#[derive(Clone)] -struct Subscription { - // Channel to send messages to this subscription - tx: broadcast::Sender>, -} +mod doorbell; -impl Subscription { - fn new(tx: broadcast::Sender>) -> Self { - Self { tx } - } -} +use doorbell::{Doorbell, shard_channel, shard_for}; -/// > In the default configuration it must be shorter than 8000 bytes -/// -/// https://www.postgresql.org/docs/17/sql-notify.html -const MAX_NOTIFY_LENGTH: usize = 8000; +/// The transport is the table, not the NOTIFY payload, so there is no per-message +/// size cap from the 8000-byte NOTIFY limit. Match the NATS ceiling so chunking +/// behaves identically across drivers. +pub const POSTGRES_MAX_MESSAGE_SIZE: usize = 1024 * 1024; -/// Base64 encoding ratio -const BYTES_PER_BLOCK: usize = 3; -const CHARS_PER_BLOCK: usize = 4; +/// Poll backstop interval. Every subscriber reads its table on this interval +/// regardless of doorbell wakeups. This is the correctness floor that makes delivery +/// independent of any NOTIFY arriving. +const POLL_INTERVAL: Duration = Duration::from_secs(1); -/// Calculate max message size if encoded as base64 -/// -/// We need to remove BYTES_PER_BLOCK since there might be a tail on the base64-encoded data that -/// would bump it over the limit. -pub const POSTGRES_MAX_MESSAGE_SIZE: usize = - (MAX_NOTIFY_LENGTH * BYTES_PER_BLOCK) / CHARS_PER_BLOCK - BYTES_PER_BLOCK; +/// Idle-in-transaction timeout applied to the LISTEN connection. A wedged listener +/// holding a transaction open would otherwise fill the shared notify queue and fail +/// NOTIFY cluster-wide. Bounding it keeps a stuck listener degrading to added latency +/// rather than a cluster outage. +const LISTEN_IDLE_IN_TRANSACTION_TIMEOUT_MS: i64 = 30_000; const QUEUE_SUB_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(10); /// How long a queue subscriber's heartbeat must be within to be considered active. const QUEUE_SUB_TTL_SECS: i64 = 30; + +/// How often to GC expired broadcast messages. +const MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(5); +/// Max age before a broadcast message row is garbage collected. Must exceed the poll +/// interval plus the reconnect gap. A subscriber that falls behind this misses +/// messages, matching NATS-core at-most-once semantics for slow consumers. +const MESSAGE_MAX_AGE_SECS: i64 = 10; + /// How often to GC orphaned queue messages. const QUEUE_MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(300); /// Max age before an unconsumed queue message is garbage collected. @@ -61,9 +60,11 @@ const QUEUE_MESSAGE_MAX_AGE_SECS: i64 = 3600; pub struct PostgresDriver { pool: Arc, client: Arc>>, - subscriptions: Arc>, - /// Wakeup channels for queue subscriptions, keyed by queue channel name. - queue_subscriptions: Arc>, + /// Wakeup channels keyed by doorbell shard channel name. Shared by broadcast and + /// queue subscribers whose subjects map to the same shard. Carries empty wakeups + /// only; payload lives in the table. + shard_subscriptions: Arc>>, + doorbell: Arc, client_ready: tokio::sync::watch::Receiver, } @@ -103,8 +104,9 @@ impl PostgresDriver { .context("failed to create postgres pool")?; tracing::debug!("postgres pool created successfully"); - let subscriptions: Arc> = Arc::new(HashMap::new()); - let queue_subscriptions: Arc> = Arc::new(HashMap::new()); + let pool = Arc::new(pool); + let shard_subscriptions: Arc>> = + Arc::new(HashMap::new()); let client: Arc>> = Arc::new(Mutex::new(None)); // Create channel for client ready notifications @@ -113,8 +115,7 @@ impl PostgresDriver { // Spawn connection lifecycle task tokio::spawn(Self::spawn_connection_lifecycle( conn_str.clone(), - subscriptions.clone(), - queue_subscriptions.clone(), + shard_subscriptions.clone(), client.clone(), ready_tx, ssl_root_cert_path.clone(), @@ -122,26 +123,41 @@ impl PostgresDriver { ssl_client_key_path.clone(), )); + let doorbell = Doorbell::new(pool.clone()); + let driver = Self { - pool: Arc::new(pool), + pool, client, - subscriptions, - queue_subscriptions, + shard_subscriptions, + doorbell, client_ready, }; // Wait for initial connection to be established driver.wait_for_client().await?; - // Create queue tables eagerly so they exist before any publish or subscribe + // Create tables eagerly so they exist before any publish or subscribe. { let conn = driver .pool .get() .await - .context("failed to get connection for queue table creation")?; + .context("failed to get connection for table creation")?; conn.batch_execute( - "CREATE TABLE IF NOT EXISTS ups_queue_subs ( \ + // Broadcast transport table. UNLOGGED gives at-most-once across a + // crash, matching NATS-core semantics, and avoids WAL fsync on every + // publish. The real subject is stored so receivers can verify it and + // reject DefaultHasher subject-hash collisions. + "CREATE UNLOGGED TABLE IF NOT EXISTS ups_messages ( \ + id BIGSERIAL PRIMARY KEY, \ + subject_hash TEXT NOT NULL, \ + subject TEXT NOT NULL, \ + payload BYTEA NOT NULL, \ + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ + ); \ + CREATE INDEX IF NOT EXISTS ups_messages_subject_id \ + ON ups_messages (subject_hash, id); \ + CREATE TABLE IF NOT EXISTS ups_queue_subs ( \ id TEXT PRIMARY KEY, \ subject_hash TEXT NOT NULL, \ queue_hash TEXT NOT NULL, \ @@ -149,7 +165,7 @@ impl PostgresDriver { ); \ CREATE INDEX IF NOT EXISTS ups_queue_subs_subject_queue \ ON ups_queue_subs (subject_hash, queue_hash); \ - CREATE TABLE IF NOT EXISTS ups_queue_messages ( \ + CREATE UNLOGGED TABLE IF NOT EXISTS ups_queue_messages ( \ id BIGSERIAL PRIMARY KEY, \ subject_hash TEXT NOT NULL, \ queue_hash TEXT NOT NULL, \ @@ -160,10 +176,33 @@ impl PostgresDriver { ON ups_queue_messages (subject_hash, queue_hash, id);", ) .await - .context("failed to create queue tables")?; - tracing::debug!("queue tables ready"); + .context("failed to create tables")?; + tracing::debug!("tables ready"); } + // Spawn GC task for expired broadcast messages + let message_gc_driver = driver.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(MESSAGE_GC_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + interval.tick().await; + if let Ok(conn) = message_gc_driver.pool.get().await { + let result = conn + .execute( + "DELETE FROM ups_messages \ + WHERE created_at < NOW() - ($1::bigint * INTERVAL '1 second')", + &[&MESSAGE_MAX_AGE_SECS], + ) + .await; + if let Err(e) = result { + tracing::warn!(?e, "failed to gc broadcast messages"); + } + } + } + }); + // Spawn GC task for orphaned queue messages let gc_driver = driver.clone(); tokio::spawn(async move { @@ -193,8 +232,7 @@ impl PostgresDriver { /// Manages the connection lifecycle with automatic reconnection async fn spawn_connection_lifecycle( conn_str: String, - subscriptions: Arc>, - queue_subscriptions: Arc>, + shard_subscriptions: Arc>>, client: Arc>>, ready_tx: tokio::sync::watch::Sender, ssl_root_cert_path: Option, @@ -227,41 +265,42 @@ impl PostgresDriver { // Spawn the polling task immediately // This must be done before any operations on the client - let subscriptions_clone = subscriptions.clone(); - let queue_subscriptions_clone = queue_subscriptions.clone(); + let shard_subscriptions_clone = shard_subscriptions.clone(); let poll_handle = tokio::spawn(async move { - Self::poll_connection(conn, subscriptions_clone, queue_subscriptions_clone) - .await; + Self::poll_connection(conn, shard_subscriptions_clone).await; }); - // Get regular channels to re-subscribe to + // Bound a stuck listener so it cannot wedge the shared notify queue. + if let Result::Err(e) = new_client + .execute( + &format!( + "SET idle_in_transaction_session_timeout = '{}'", + LISTEN_IDLE_IN_TRANSACTION_TIMEOUT_MS + ), + &[], + ) + .await + { + tracing::warn!(?e, "failed to set idle_in_transaction_session_timeout"); + } + + // Get shard channels to re-subscribe to let mut channels = Vec::new(); - subscriptions + shard_subscriptions .iter_async(|k, _| { channels.push(k.clone()); true }) .await; - // Get queue wakeup channels to re-subscribe to - let mut queue_channels = Vec::new(); - queue_subscriptions - .iter_async(|k, _| { - queue_channels.push(k.clone()); - true - }) - .await; - - let needs_resubscribe = !channels.is_empty() || !queue_channels.is_empty(); - if needs_resubscribe { + if !channels.is_empty() { tracing::debug!( - regular_channels = channels.len(), - queue_channels = queue_channels.len(), - "re-subscribing to channels after reconnection" + channels = channels.len(), + "re-subscribing to doorbell shards after reconnection" ); } - for channel in channels.iter().chain(queue_channels.iter()) { + for channel in channels.iter() { tracing::debug!(?channel, "re-subscribing to channel"); if let Result::Err(e) = new_client .execute(&format!("LISTEN \"{}\"", channel), &[]) @@ -298,30 +337,20 @@ impl PostgresDriver { /// Polls the connection for notifications until it closes or errors async fn poll_connection( mut conn: tokio_postgres::Connection, - subscriptions: Arc>, - queue_subscriptions: Arc>, + shard_subscriptions: Arc>>, ) where T: tokio_postgres::tls::TlsStream + Unpin, { loop { match poll_fn(|cx| conn.poll_message(cx)).await { Some(std::result::Result::Ok(AsyncMessage::Notification(note))) => { - tracing::trace!(channel = %note.channel(), "received notification"); - if let Some(sub) = subscriptions.get_async(note.channel()).await { - let bytes = match BASE64.decode(note.payload()) { - std::result::Result::Ok(b) => b, - std::result::Result::Err(err) => { - tracing::error!(?err, "failed decoding base64"); - continue; - } - }; - tracing::trace!(channel = %note.channel(), bytes_len = bytes.len(), "sending to broadcast channel"); - let _ = sub.tx.send(bytes); - } else if let Some(sub) = queue_subscriptions.get_async(note.channel()).await { - // Queue notifications are wakeup signals only; payload lives in the table - let _ = sub.tx.send(Vec::new()); + tracing::trace!(channel = %note.channel(), "received doorbell wakeup"); + // Doorbell notifications are payload-free wakeup signals only. + // Subscribers read their payload from the table. + if let Some(sub) = shard_subscriptions.get_async(note.channel()).await { + let _ = sub.send(()); } else { - tracing::warn!(channel = %note.channel(), "received notification for unknown channel"); + tracing::trace!(channel = %note.channel(), "wakeup for unknown shard"); } } Some(std::result::Result::Ok(_)) => { @@ -361,8 +390,9 @@ impl PostgresDriver { } fn hash_subject(&self, subject: &str) -> String { - // Postgres channel names have a 64 character limit - // Hash the subject to ensure it fits + // Postgres channel names have a 64 character limit, but this hash is also the + // table index key. Collisions are possible and resolved by verifying the real + // subject stored alongside each row. let mut hasher = DefaultHasher::new(); subject.hash(&mut hasher); format!("ups_{:x}", hasher.finish()) @@ -374,57 +404,68 @@ impl PostgresDriver { format!("{:x}", hasher.finish()) } - /// Returns the NOTIFY channel name for a (subject, queue) pair. - fn queue_channel(&self, subject_hash: &str, queue_hash: &str) -> String { - // Max length: "ups_q_" (6) + 16 + "_" (1) + 16 = 39 chars, well within 64 - format!("ups_q_{}_{}", subject_hash, queue_hash) - } - - /// Inserts messages into the queue table and notifies active queue subscribers. - async fn publish_to_queues(&self, subject: &str, payload: &[u8]) -> Result<()> { - let subject_hash = self.hash_subject(subject); - + /// Returns the current max broadcast message id, used as a subscriber's starting + /// cursor so it only sees future messages (NATS at-most-once, no replay). + async fn current_max_id(&self) -> Result { let conn = self .pool .get() .await - .context("failed to get connection for queue publish")?; - - // Find active queue groups for this subject - let rows = conn - .query( - "SELECT DISTINCT queue_hash FROM ups_queue_subs \ - WHERE subject_hash = $1 \ - AND heartbeat_at > NOW() - ($2::bigint * INTERVAL '1 second')", - &[&subject_hash, &QUEUE_SUB_TTL_SECS], - ) + .context("failed to get connection for cursor init")?; + let row = conn + .query_one("SELECT COALESCE(MAX(id), 0) FROM ups_messages", &[]) .await - .context("failed to query active queue subs")?; + .context("failed to read current max id")?; + Ok(row.get(0)) + } - for row in rows { - let queue_hash: String = row.get(0); - let channel = self.queue_channel(&subject_hash, &queue_hash); + /// Ensures this process is LISTENing on the given doorbell shard and returns a + /// wakeup receiver plus a drop guard that UNLISTENs once no receivers remain. + async fn ensure_shard_listen( + &self, + shard: usize, + ) -> (broadcast::Receiver<()>, tokio_util::sync::DropGuard) { + let channel = shard_channel(shard); - conn.execute( - "INSERT INTO ups_queue_messages (subject_hash, queue_hash, payload) \ - VALUES ($1, $2, $3)", - &[&subject_hash, &queue_hash, &payload], - ) - .await - .context("failed to insert queue message")?; + match self.shard_subscriptions.entry_async(channel.clone()).await { + scc::hash_map::Entry::Occupied(existing) => { + let rx = existing.subscribe(); + let drop_guard = + self.spawn_shard_cleanup_task(channel.clone(), existing.get().clone()); + (rx, drop_guard) + } + scc::hash_map::Entry::Vacant(e) => { + let (tx, rx) = broadcast::channel(1024); + e.insert_entry(tx.clone()); + metrics::POSTGRES_SUBSCRIPTION_COUNT.set(self.shard_subscriptions.len() as i64); - conn.execute(&format!("NOTIFY \"{}\"", channel), &[]) - .await - .context("failed to notify queue channel")?; - } + if let Some(client) = &*self.client.lock().await { + match client + .execute(&format!("LISTEN \"{channel}\""), &[]) + .instrument(tracing::trace_span!("pg_listen")) + .await + { + Result::Ok(_) => { + tracing::debug!(%channel, "successfully subscribed to shard"); + } + Result::Err(e) => { + tracing::warn!(?e, %channel, "failed to LISTEN, will retry on reconnection"); + } + } + } else { + tracing::debug!(%channel, "client not connected, will LISTEN on reconnection"); + } - Ok(()) + let drop_guard = self.spawn_shard_cleanup_task(channel.clone(), tx.clone()); + (rx, drop_guard) + } + } } - fn spawn_subscription_cleanup_task( + fn spawn_shard_cleanup_task( &self, - subject_hash: String, - tx: broadcast::Sender>, + channel: String, + tx: broadcast::Sender<()>, ) -> tokio_util::sync::DropGuard { let driver = self.clone(); let token = tokio_util::sync::CancellationToken::new(); @@ -434,47 +475,72 @@ impl PostgresDriver { token.cancelled().await; if tx.receiver_count() == 0 { if let Some(client) = &*driver.client.lock().await { - let sql = format!("UNLISTEN \"{}\"", subject_hash); + let sql = format!("UNLISTEN \"{}\"", channel); if let Err(err) = client.execute(sql.as_str(), &[]).await { - tracing::warn!(?err, %subject_hash, "failed to UNLISTEN channel"); + tracing::warn!(?err, %channel, "failed to UNLISTEN channel"); } else { - tracing::trace!(%subject_hash, "unlistened channel"); + tracing::trace!(%channel, "unlistened channel"); } } - driver.subscriptions.remove_async(&subject_hash).await; - metrics::POSTGRES_SUBSCRIPTION_COUNT.set(driver.subscriptions.len() as i64); + driver.shard_subscriptions.remove_async(&channel).await; + metrics::POSTGRES_SUBSCRIPTION_COUNT.set(driver.shard_subscriptions.len() as i64); } }); drop_guard } - fn spawn_queue_subscription_cleanup_task( + /// Inserts the broadcast row and any active queue-group rows in one transaction. + async fn try_publish_to_db( &self, - channel: String, - tx: broadcast::Sender>, - ) -> tokio_util::sync::DropGuard { - let driver = self.clone(); - let token = tokio_util::sync::CancellationToken::new(); - let drop_guard = token.clone().drop_guard(); + subject: &str, + subject_hash: &str, + payload: &[u8], + ) -> Result<()> { + let mut conn = self + .pool + .get() + .await + .context("failed to get connection for publish")?; + let tx = conn + .transaction() + .await + .context("failed to begin publish transaction")?; - tokio::spawn(async move { - token.cancelled().await; - if tx.receiver_count() == 0 { - if let Some(client) = &*driver.client.lock().await { - let sql = format!("UNLISTEN \"{}\"", channel); + // Broadcast row. + tx.execute( + "INSERT INTO ups_messages (subject_hash, subject, payload) VALUES ($1, $2, $3)", + &[&subject_hash, &subject, &payload], + ) + .await + .context("failed to insert broadcast message")?; - if let Err(err) = client.execute(sql.as_str(), &[]).await { - tracing::warn!(?err, %channel, "failed to UNLISTEN queue channel"); - } else { - tracing::trace!(%channel, "unlistened queue channel"); - } - } - driver.queue_subscriptions.remove_async(&channel).await; - } - }); + // Queue rows for every active queue group on this subject. Batched into the + // same transaction so a crash never strands a row mid-publish. + let rows = tx + .query( + "SELECT DISTINCT queue_hash FROM ups_queue_subs \ + WHERE subject_hash = $1 \ + AND heartbeat_at > NOW() - ($2::bigint * INTERVAL '1 second')", + &[&subject_hash, &QUEUE_SUB_TTL_SECS], + ) + .await + .context("failed to query active queue subs")?; - drop_guard + for row in rows { + let queue_hash: String = row.get(0); + tx.execute( + "INSERT INTO ups_queue_messages (subject_hash, queue_hash, payload) \ + VALUES ($1, $2, $3)", + &[&subject_hash, &queue_hash, &payload], + ) + .await + .context("failed to insert queue message")?; + } + + tx.commit().await.context("failed to commit publish")?; + + Ok(()) } } @@ -485,66 +551,23 @@ impl PubSubDriver for PostgresDriver { subject: &str, _reply_id: Option, ) -> Result { - // TODO: To match NATS implementation, LISTEN must be pipelined (i.e. wait for the command - // to reach the server, but not wait for it to respond). However, this has to ensure that - // NOTIFY & LISTEN are called on the same connection (not diff connections in a pool) or - // else there will be race conditions where messages might be published before - // subscriptions are registered. - // - // tokio-postgres currently does not expose the API for pipelining, so we are SOL. - // - // We might be able to use a background tokio task in combination with flush if we use the - // same Postgres connection, but unsure if that will create a bottleneck. - - let hashed = self.hash_subject(subject); - - // Check if we already have a subscription for this channel - let (rx, drop_guard) = match self.subscriptions.entry_async(hashed.clone()).await { - scc::hash_map::Entry::Occupied(existing_sub) => { - // Reuse the existing broadcast channel - let rx = existing_sub.tx.subscribe(); - let drop_guard = - self.spawn_subscription_cleanup_task(hashed.clone(), existing_sub.tx.clone()); - (rx, drop_guard) - } - scc::hash_map::Entry::Vacant(e) => { - // Create a new broadcast channel for this subject - let (tx, rx) = tokio::sync::broadcast::channel(1024); - let subscription = Subscription::new(tx.clone()); - - // Register subscription - e.insert_entry(subscription.clone()); - metrics::POSTGRES_SUBSCRIPTION_COUNT.set(self.subscriptions.len() as i64); - - // Execute LISTEN command on the async client (for receiving notifications) - // This only needs to be done once per channel - // Try to LISTEN if client is available, but don't fail if disconnected - // The reconnection logic will handle re-subscribing - if let Some(client) = &*self.client.lock().await { - match client - .execute(&format!("LISTEN \"{hashed}\""), &[]) - .instrument(tracing::trace_span!("pg_listen")) - .await - { - Result::Ok(_) => { - tracing::debug!(%hashed, "successfully subscribed to channel"); - } - Result::Err(e) => { - tracing::warn!(?e, %hashed, "failed to LISTEN, will retry on reconnection"); - } - } - } else { - tracing::debug!(%hashed, "client not connected, will LISTEN on reconnection"); - } + let subject_hash = self.hash_subject(subject); + let shard = shard_for(&subject_hash); - let drop_guard = self.spawn_subscription_cleanup_task(hashed.clone(), tx.clone()); - (rx, drop_guard) - } - }; + // Capture the cursor before LISTENing. Any message inserted after this point + // has a higher id and is delivered either by the doorbell wakeup or the poll + // backstop, so there is no subscribe/publish race. + let cursor = self.current_max_id().await?; + + let (rx, drop_guard) = self.ensure_shard_listen(shard).await; Ok(Box::new(PostgresSubscriber { subject: subject.to_string(), - rx: Some(rx), + subject_hash, + pool: self.pool.clone(), + cursor, + buffer: VecDeque::new(), + rx, _drop_guard: drop_guard, })) } @@ -552,7 +575,7 @@ impl PubSubDriver for PostgresDriver { async fn queue_subscribe(&self, subject: &str, queue: &str) -> Result { let subject_hash = self.hash_subject(subject); let queue_hash = self.hash_queue(queue); - let channel = self.queue_channel(&subject_hash, &queue_hash); + let shard = shard_for(&subject_hash); // Register this subscriber in the database so publishers know the queue exists let sub_id = Uuid::new_v4().to_string(); @@ -570,44 +593,7 @@ impl PubSubDriver for PostgresDriver { .context("failed to register queue subscriber")?; } - // Set up a shared LISTEN/broadcast channel for the wakeup signal - let (rx, drop_guard) = match self.queue_subscriptions.entry_async(channel.clone()).await { - scc::hash_map::Entry::Occupied(existing_sub) => { - let rx = existing_sub.tx.subscribe(); - let drop_guard = self.spawn_queue_subscription_cleanup_task( - channel.clone(), - existing_sub.tx.clone(), - ); - (rx, drop_guard) - } - scc::hash_map::Entry::Vacant(e) => { - let (tx, rx) = tokio::sync::broadcast::channel(1024); - let subscription = Subscription::new(tx.clone()); - - e.insert_entry(subscription.clone()); - - if let Some(client) = &*self.client.lock().await { - match client - .execute(&format!("LISTEN \"{}\"", channel), &[]) - .instrument(tracing::trace_span!("pg_listen_queue")) - .await - { - Result::Ok(_) => { - tracing::debug!(%channel, "successfully subscribed to queue channel"); - } - Result::Err(e) => { - tracing::warn!(?e, %channel, "failed to LISTEN queue channel, will retry on reconnection"); - } - } - } else { - tracing::debug!(%channel, "client not connected, will LISTEN queue channel on reconnection"); - } - - let drop_guard = - self.spawn_queue_subscription_cleanup_task(channel.clone(), tx.clone()); - (rx, drop_guard) - } - }; + let (rx, drop_guard) = self.ensure_shard_listen(shard).await; // Spawn heartbeat task to keep the registration alive let pool = self.pool.clone(); @@ -644,7 +630,7 @@ impl PubSubDriver for PostgresDriver { queue_hash, sub_id, pool: self.pool.clone(), - rx: Some(rx), + rx, _drop_guard: drop_guard, _heartbeat_token: heartbeat_token, })) @@ -656,78 +642,33 @@ impl PubSubDriver for PostgresDriver { payload: &[u8], _reply_subject: Option<&str>, ) -> Result<()> { - // TODO: See `subscribe` about pipelining - - // Encode payload to base64 and send NOTIFY - let encoded = BASE64.encode(payload); - let hashed = self.hash_subject(subject); - - tracing::trace!("attempting to get connection for publish"); - - // Wait for listen connection to be ready first if this channel has subscribers - // This ensures that if we're reconnecting, the LISTEN is re-registered before NOTIFY - if self.subscriptions.contains_async(&hashed).await { - self.wait_for_client().await?; - } + let subject_hash = self.hash_subject(subject); + let shard = shard_for(&subject_hash); - // Retry getting a connection from the pool with backoff in case the connection is - // currently disconnected + // Persist the message, retrying on transient connection errors. The row is + // committed before the doorbell rings so any wakeup observes it. let mut backoff = Backoff::default(); - let mut last_error; - loop { - match self.pool.get().await { - Result::Ok(conn) => { - // Test the connection with a simple query before using it - match conn.execute("SELECT 1", &[]).await { - Result::Ok(_) => { - // Connection is good; run NOTIFY and queue publish in parallel. - // publish_to_queues acquires its own pool connection so both - // can proceed concurrently. - let notify_sql = format!("NOTIFY \"{hashed}\", '{encoded}'"); - let (notify_result, queue_result) = tokio::join!( - conn.execute(notify_sql.as_str(), &[]) - .instrument(tracing::trace_span!("pg_notify")), - self.publish_to_queues(subject, payload), - ); - match notify_result { - Result::Ok(_) => { - if let Err(e) = queue_result { - tracing::warn!(?e, %subject, "failed to publish to queue subscribers"); - } - return Ok(()); - } - Result::Err(e) => { - tracing::debug!( - ?e, - "NOTIFY failed, retrying with new connection" - ); - last_error = Some(e.into()); - } - } - } - Result::Err(e) => { - tracing::debug!( - ?e, - "connection test failed, retrying with new connection" - ); - last_error = Some(e.into()); - } - } - } + match self + .try_publish_to_db(subject, &subject_hash, payload) + .await + { + Result::Ok(()) => break, Result::Err(e) => { - tracing::debug!(?e, "failed to get connection from pool, retrying"); - last_error = Some(e.into()); + if !backoff.tick().await { + tracing::warn!(?e, %subject, "failed to publish, cannot retry again"); + return Err(e); + } + tracing::debug!(?e, "publish failed, retrying"); } } - - // Check if we should continue retrying - if !backoff.tick().await { - return Err( - last_error.unwrap_or_else(|| anyhow!("failed to publish after retries")) - ); - } } + + // Ring the doorbell. Best-effort: the subscriber poll backstop covers a + // dropped or coalesced wakeup, so publish never blocks on NOTIFY. + self.doorbell.mark_dirty(shard); + + Ok(()) } async fn flush(&self) -> Result<()> { @@ -741,26 +682,80 @@ impl PubSubDriver for PostgresDriver { pub struct PostgresSubscriber { subject: String, - rx: Option>>, + subject_hash: String, + pool: Arc, + cursor: i64, + buffer: VecDeque>, + rx: broadcast::Receiver<()>, _drop_guard: tokio_util::sync::DropGuard, } +impl PostgresSubscriber { + /// Reads new rows past the cursor into the buffer, advancing the cursor. Rows + /// whose stored subject does not match are skipped (DefaultHasher collisions) but + /// still advance the cursor. + async fn fetch(&mut self) -> Result<()> { + let conn = self + .pool + .get() + .await + .context("failed to get connection for poll")?; + let rows = conn + .query( + "SELECT id, subject, payload FROM ups_messages \ + WHERE subject_hash = $1 AND id > $2 ORDER BY id", + &[&self.subject_hash, &self.cursor], + ) + .await + .context("failed to poll broadcast messages")?; + + for row in rows { + let id: i64 = row.get(0); + let subject: String = row.get(1); + let payload: Vec = row.get(2); + self.cursor = id; + if subject == self.subject { + self.buffer.push_back(payload); + } + } + + Ok(()) + } +} + #[async_trait] impl SubscriberDriver for PostgresSubscriber { async fn next(&mut self) -> Result { - let rx = match self.rx.as_mut() { - Some(rx) => rx, - None => return Ok(DriverOutput::Unsubscribed), - }; - match rx.recv().await { - std::result::Result::Ok(payload) => Ok(DriverOutput::Message { - subject: self.subject.clone(), - payload, - }), - Err(tokio::sync::broadcast::error::RecvError::Closed) => Ok(DriverOutput::Unsubscribed), - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { - // Try again - self.next().await + loop { + if let Some(payload) = self.buffer.pop_front() { + return Ok(DriverOutput::Message { + subject: self.subject.clone(), + payload, + }); + } + + if let Err(e) = self.fetch().await { + // Transient DB errors must not kill the subscriber; the next poll + // tick retries. + tracing::warn!(?e, subject = %self.subject, "failed to poll, will retry"); + } + + if !self.buffer.is_empty() { + continue; + } + + // Wait for a doorbell wakeup or the poll backstop, whichever is first. + tokio::select! { + res = self.rx.recv() => { + match res { + std::result::Result::Ok(()) => {} + Err(broadcast::error::RecvError::Lagged(_)) => {} + Err(broadcast::error::RecvError::Closed) => { + return Ok(DriverOutput::Unsubscribed); + } + } + } + _ = tokio::time::sleep(POLL_INTERVAL) => {} } } } @@ -772,7 +767,7 @@ pub struct PostgresQueueSubscriber { queue_hash: String, sub_id: String, pool: Arc, - rx: Option>>, + rx: broadcast::Receiver<()>, _drop_guard: tokio_util::sync::DropGuard, _heartbeat_token: tokio_util::sync::CancellationToken, } @@ -811,31 +806,32 @@ impl PostgresQueueSubscriber { impl SubscriberDriver for PostgresQueueSubscriber { async fn next(&mut self) -> Result { loop { - // Drain any messages that arrived before or between notifications. - // Do this before borrowing rx so claim_message can borrow self freely. - if let Some(payload) = self.claim_message().await? { - return Ok(DriverOutput::Message { - subject: self.subject.clone(), - payload, - }); - } - - // Wait for a wakeup notification, then loop back to claim. - let rx = match self.rx.as_mut() { - Some(rx) => rx, - None => return Ok(DriverOutput::Unsubscribed), - }; - match rx.recv().await { - std::result::Result::Ok(_) => { - // Wakeup received; loop back to claim + // Drain any messages that arrived before or between wakeups. + match self.claim_message().await { + Result::Ok(Some(payload)) => { + return Ok(DriverOutput::Message { + subject: self.subject.clone(), + payload, + }); } - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - return Ok(DriverOutput::Unsubscribed); + Result::Ok(None) => {} + Result::Err(e) => { + tracing::warn!(?e, subject = %self.subject, "failed to claim, will retry"); } - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { - // Notifications were dropped while lagged; loop back to claim in case - // messages are waiting + } + + // Wait for a doorbell wakeup or the poll backstop, then loop back to claim. + tokio::select! { + res = self.rx.recv() => { + match res { + std::result::Result::Ok(()) => {} + Err(broadcast::error::RecvError::Lagged(_)) => {} + Err(broadcast::error::RecvError::Closed) => { + return Ok(DriverOutput::Unsubscribed); + } + } } + _ = tokio::time::sleep(POLL_INTERVAL) => {} } } } From 826a9de5b47efffd378d45d5187894a9a97fc334 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 12:17:20 -0700 Subject: [PATCH 10/19] [slopfix] test(universaldb): postgres leader failover + abort resolver task on driver drop --- engine/artifacts/config-schema.json | 32 -- .../packages/config/src/config/api_public.rs | 25 -- engine/packages/config/src/config/mod.rs | 11 - engine/packages/engine/src/main.rs | 2 + engine/packages/metrics-server/src/server.rs | 2 +- .../pegboard-envoy/src/ws_to_tunnel_task.rs | 9 +- .../pegboard-gateway/src/shared_state.rs | 2 +- .../packages/pegboard-gateway2/src/metrics.rs | 6 - .../pegboard-gateway2/src/shared_state.rs | 20 +- engine/packages/perf/src/lib.rs | 2 +- engine/packages/service-manager/src/lib.rs | 2 - engine/packages/test-deps/src/datacenter.rs | 1 - engine/packages/universaldb/Cargo.toml | 1 + .../src/driver/postgres/database.rs | 7 +- .../src/driver/postgres/resolver/mod.rs | 7 +- engine/packages/universaldb/tests/failover.rs | 219 ++++++++++++ .../src/driver/postgres/mod.rs | 319 ++++++++++++++---- .../universalpubsub/tests/reconnect.rs | 35 +- scripts/run/engine-postgres.sh | 21 +- 19 files changed, 535 insertions(+), 188 deletions(-) delete mode 100644 engine/packages/config/src/config/api_public.rs create mode 100644 engine/packages/universaldb/tests/failover.rs diff --git a/engine/artifacts/config-schema.json b/engine/artifacts/config-schema.json index 998771acc1..af2af30077 100644 --- a/engine/artifacts/config-schema.json +++ b/engine/artifacts/config-schema.json @@ -40,17 +40,6 @@ } ] }, - "api_public": { - "default": null, - "anyOf": [ - { - "$ref": "#/definitions/ApiPublic" - }, - { - "type": "null" - } - ] - }, "auth": { "default": null, "anyOf": [ @@ -214,27 +203,6 @@ }, "additionalProperties": false }, - "ApiPublic": { - "description": "Configuration for the public API service.", - "type": "object", - "properties": { - "respect_forwarded_for": { - "description": "Flag to respect the X-Forwarded-For header for client IP addresses.\n\nWill be ignored in favor of CF-Connecting-IP if DNS provider is configured as Cloudflare.", - "type": [ - "boolean", - "null" - ] - }, - "verbose_errors": { - "description": "Flag to enable verbose error reporting.", - "type": [ - "boolean", - "null" - ] - } - }, - "additionalProperties": false - }, "Auth": { "type": "object", "required": [ diff --git a/engine/packages/config/src/config/api_public.rs b/engine/packages/config/src/config/api_public.rs deleted file mode 100644 index 53cb280a37..0000000000 --- a/engine/packages/config/src/config/api_public.rs +++ /dev/null @@ -1,25 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Configuration for the public API service. -#[derive(Debug, Serialize, Deserialize, Clone, Default, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct ApiPublic { - /// Flag to enable verbose error reporting. - pub verbose_errors: Option, - /// Flag to respect the X-Forwarded-For header for client IP addresses. - /// - /// Will be ignored in favor of CF-Connecting-IP if DNS provider is - /// configured as Cloudflare. - pub respect_forwarded_for: Option, -} - -impl ApiPublic { - pub fn verbose_errors(&self) -> bool { - self.verbose_errors.unwrap_or(true) - } - - pub fn respect_forwarded_for(&self) -> bool { - self.respect_forwarded_for.unwrap_or(false) - } -} diff --git a/engine/packages/config/src/config/mod.rs b/engine/packages/config/src/config/mod.rs index 3ee2aace1d..caa8bf196e 100644 --- a/engine/packages/config/src/config/mod.rs +++ b/engine/packages/config/src/config/mod.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; use std::sync::LazyLock; pub mod api_peer; -pub mod api_public; pub mod auth; pub mod cache; pub mod clickhouse; @@ -21,7 +20,6 @@ pub mod telemetry; pub mod topology; pub use api_peer::*; -pub use api_public::*; pub use auth::*; pub use cache::*; pub use clickhouse::*; @@ -74,9 +72,6 @@ pub struct Root { #[serde(default)] pub guard: Option, - #[serde(default)] - pub api_public: Option, - #[serde(default)] pub api_peer: Option, @@ -122,7 +117,6 @@ impl Default for Root { Root { auth: None, guard: None, - api_public: None, api_peer: None, pegboard: None, logs: None, @@ -146,11 +140,6 @@ impl Root { self.guard.as_ref().unwrap_or(&DEFAULT) } - pub fn api_public(&self) -> &ApiPublic { - static DEFAULT: LazyLock = LazyLock::new(ApiPublic::default); - self.api_public.as_ref().unwrap_or(&DEFAULT) - } - pub fn api_peer(&self) -> &ApiPeer { static DEFAULT: LazyLock = LazyLock::new(ApiPeer::default); self.api_peer.as_ref().unwrap_or(&DEFAULT) diff --git a/engine/packages/engine/src/main.rs b/engine/packages/engine/src/main.rs index d0f38a6efb..88e74b0f1b 100644 --- a/engine/packages/engine/src/main.rs +++ b/engine/packages/engine/src/main.rs @@ -36,6 +36,8 @@ fn main() -> Result<()> { } async fn main_inner() -> Result<()> { + tracing::info!(version=%build_meta::VERSION, git_sha=%build_meta::GIT_SHA, built_at=%build_meta::BUILD_TIMESTAMP, "starting rivet"); + let cli = Cli::parse(); // Load config diff --git a/engine/packages/metrics-server/src/server.rs b/engine/packages/metrics-server/src/server.rs index 48b974c537..b2512b3e31 100644 --- a/engine/packages/metrics-server/src/server.rs +++ b/engine/packages/metrics-server/src/server.rs @@ -28,7 +28,7 @@ pub async fn run_standalone(config: rivet_config::Config) -> Result<()> { Ok::<_, hyper::Error>(service_fn(serve_req)) })); - tracing::info!(?host, ?port, "started metrics server"); + tracing::debug!(?host, ?port, "started metrics server"); server.await?; Ok(()) diff --git a/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs b/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs index fcbe313453..922225c282 100644 --- a/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs +++ b/engine/packages/pegboard-envoy/src/ws_to_tunnel_task.rs @@ -474,9 +474,14 @@ pub async fn task_inner( // backpressure to the runner rather than dropping protocol messages. let mut rate_limit = rivet_util::throttle::RateLimiter::new( rivet_util::throttle::RateLimitMethod::LeakyBucket { - requests: ctx.config().pegboard().envoy_websocket_rate_limit_requests(), + requests: ctx + .config() + .pegboard() + .envoy_websocket_rate_limit_requests(), drip_rate: Duration::from_micros( - ctx.config().pegboard().envoy_websocket_rate_limit_drip_rate_us(), + ctx.config() + .pegboard() + .envoy_websocket_rate_limit_drip_rate_us(), ), }, ); diff --git a/engine/packages/pegboard-gateway/src/shared_state.rs b/engine/packages/pegboard-gateway/src/shared_state.rs index 580da8eabc..742b146921 100644 --- a/engine/packages/pegboard-gateway/src/shared_state.rs +++ b/engine/packages/pegboard-gateway/src/shared_state.rs @@ -142,7 +142,7 @@ pub struct SharedState(Arc); impl SharedState { pub fn new(config: &rivet_config::Config, ups: PubSub) -> Self { let gateway_id = protocol::util::generate_gateway_id(); - tracing::info!(gateway_id = %protocol::util::id_to_string(&gateway_id), "setting up shared state for gateway"); + tracing::debug!(gateway_id = %protocol::util::id_to_string(&gateway_id), "setting up shared state for gateway"); let receiver_subject = GatewayReceiverSubject::new(gateway_id); let pegboard_config = config.pegboard(); diff --git a/engine/packages/pegboard-gateway2/src/metrics.rs b/engine/packages/pegboard-gateway2/src/metrics.rs index 244df7f882..f087d2ac3b 100644 --- a/engine/packages/pegboard-gateway2/src/metrics.rs +++ b/engine/packages/pegboard-gateway2/src/metrics.rs @@ -53,12 +53,6 @@ lazy_static::lazy_static! { &["namespace_id", "pool_name", "protocol", "reason"], *REGISTRY ).unwrap(); - pub static ref SHUTDOWN_IN_FLIGHT_ABORTED_TOTAL: IntCounter = - register_int_counter_with_registry!( - "gateway2_shutdown_in_flight_aborted_total", - "In-flight gateway requests abandoned on pod shutdown without sending close.", - *REGISTRY - ).unwrap(); pub static ref MSG_SENT_TOTAL: IntCounterVec = register_int_counter_vec_with_registry!( "gateway2_msg_sent_total", "Count of total of tunnel messages sent.", diff --git a/engine/packages/pegboard-gateway2/src/shared_state.rs b/engine/packages/pegboard-gateway2/src/shared_state.rs index b3de6e5887..1d1b7a95cd 100644 --- a/engine/packages/pegboard-gateway2/src/shared_state.rs +++ b/engine/packages/pegboard-gateway2/src/shared_state.rs @@ -163,7 +163,7 @@ impl SharedState { init_slow_ping_threshold_from_env(); let gateway_id = protocol::util::generate_gateway_id(); - tracing::info!(gateway_id = %display_id(&gateway_id), "setting up shared state for gateway"); + tracing::debug!(gateway_id = %display_id(&gateway_id), "setting up shared state for gateway"); let receiver_subject = GatewayReceiverSubject::new(gateway_id); let pegboard_config = config.pegboard(); @@ -194,27 +194,9 @@ impl SharedState { let self_clone = self.clone(); tokio::spawn(async move { self_clone.gc().await }); - let self_clone = self.clone(); - tokio::spawn(async move { self_clone.shutdown_watcher().await }); - Ok(()) } - #[tracing::instrument(skip_all)] - async fn shutdown_watcher(&self) { - let mut term_signal = __rivet_runtime::TermSignal::get(); - term_signal.recv().await; - - let in_flight_aborted = self.in_flight_requests.len(); - if in_flight_aborted > 0 { - metrics::SHUTDOWN_IN_FLIGHT_ABORTED_TOTAL.inc_by(in_flight_aborted as u64); - } - tracing::info!( - in_flight_aborted, - "gateway shutdown in-flight requests abandoned without close" - ); - } - #[tracing::instrument(skip_all)] async fn receiver(&self) { // Automatically resubscribe if unsubscribed diff --git a/engine/packages/perf/src/lib.rs b/engine/packages/perf/src/lib.rs index 7642c406ad..f7ebb0dc71 100644 --- a/engine/packages/perf/src/lib.rs +++ b/engine/packages/perf/src/lib.rs @@ -95,7 +95,7 @@ impl Drop for PerfMeasure { let elapsed = self.start.elapsed(); let _guard = self.span.enter(); - tracing::warn!( + tracing::debug!( name = self.name, elapsed_ms = PerfMeasure::__elapsed_ms(elapsed), "PerfMeasure dropped without finish() - measurement discarded", diff --git a/engine/packages/service-manager/src/lib.rs b/engine/packages/service-manager/src/lib.rs index 6c9f198d78..a60854f3e0 100644 --- a/engine/packages/service-manager/src/lib.rs +++ b/engine/packages/service-manager/src/lib.rs @@ -160,8 +160,6 @@ pub async fn start( let shutting_down = Arc::new(AtomicBool::new(false)); for service in services { - tracing::debug!(name=%service.name, kind=?service.kind, "server starting service"); - match service.kind.behavior() { ServiceBehavior::Service => { let config = config.clone(); diff --git a/engine/packages/test-deps/src/datacenter.rs b/engine/packages/test-deps/src/datacenter.rs index 33843062df..d7fdfcf040 100644 --- a/engine/packages/test-deps/src/datacenter.rs +++ b/engine/packages/test-deps/src/datacenter.rs @@ -73,7 +73,6 @@ pub async fn setup_single_datacenter( let mut root = rivet_config::config::Root::default(); root.database = Some(db_config); root.pubsub = Some(pubsub_config); - root.api_public = Some(Default::default()); root.api_peer = Some(rivet_config::config::ApiPeer { port: Some(api_peer_port), ..Default::default() diff --git a/engine/packages/universaldb/Cargo.toml b/engine/packages/universaldb/Cargo.toml index b2f6b5f045..57dba4ab33 100644 --- a/engine/packages/universaldb/Cargo.toml +++ b/engine/packages/universaldb/Cargo.toml @@ -39,4 +39,5 @@ rivet-config.workspace = true rivet-env.workspace = true rivet-pools.workspace = true rivet-test-deps-docker.workspace = true +tokio-postgres.workspace = true tracing-subscriber.workspace = true diff --git a/engine/packages/universaldb/src/driver/postgres/database.rs b/engine/packages/universaldb/src/driver/postgres/database.rs index 6e24cf647d..6cded0f320 100644 --- a/engine/packages/universaldb/src/driver/postgres/database.rs +++ b/engine/packages/universaldb/src/driver/postgres/database.rs @@ -59,6 +59,7 @@ impl PostgresConfig { pub struct PostgresDatabaseDriver { shared: Arc, max_retries: AtomicI32, + resolver_handle: JoinHandle<()>, gc_handle: JoinHandle<()>, } @@ -112,13 +113,14 @@ impl PostgresDatabaseDriver { let shared = PostgresShared::new(pool, node_id, listener); // Every node runs the resolver; only the elected leader drains the commit queue. - resolver::spawn(shared.clone()); + let resolver_handle = resolver::spawn(shared.clone()); let gc_handle = Self::spawn_gc(shared.clone()); Ok(PostgresDatabaseDriver { shared, max_retries: AtomicI32::new(100), + resolver_handle, gc_handle, }) } @@ -292,6 +294,9 @@ impl DatabaseDriver for PostgresDatabaseDriver { impl Drop for PostgresDatabaseDriver { fn drop(&mut self) { + // Abort the resolver so a dropped node stops renewing its lease; the lease then expires and + // another node can take over. Without this a dropped leader would renew its lease forever. + self.resolver_handle.abort(); self.gc_handle.abort(); } } diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs index 79f1f1f48b..3dfac50fce 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -33,9 +33,10 @@ enum DrainOutcome { } /// Spawn the per-process resolver task. Every node runs this; only the elected leader drains the -/// commit queue. -pub fn spawn(shared: Arc) { - tokio::spawn(run(shared)); +/// commit queue. The returned handle is aborted when the owning driver drops, which stops lease +/// renewal so the lease expires and another node can take over (node-death / failover path). +pub fn spawn(shared: Arc) -> tokio::task::JoinHandle<()> { + tokio::spawn(run(shared)) } async fn run(shared: Arc) { diff --git a/engine/packages/universaldb/tests/failover.rs b/engine/packages/universaldb/tests/failover.rs new file mode 100644 index 0000000000..7d122ca781 --- /dev/null +++ b/engine/packages/universaldb/tests/failover.rs @@ -0,0 +1,219 @@ +use std::{sync::Arc, time::Duration}; + +use rivet_test_deps_docker::TestDatabase; +use tokio_postgres::NoTls; +use universaldb::{Database, utils::IsolationLevel::*}; +use uuid::Uuid; + +const ALPHA_KEY: &[u8] = b"failover/alpha"; +const BETA_KEY: &[u8] = b"failover/beta"; + +/// Build a fresh Postgres-backed `Database`. Each call spins up an independent driver (its own pool, +/// node id, listener, and resolver), so two of them against one Postgres model two engine nodes. +async fn make_db(connection_string: &str) -> Database { + let driver = universaldb::driver::PostgresDatabaseDriver::new_with_config( + universaldb::driver::postgres::PostgresConfig::new(connection_string.to_string()), + ) + .await + .unwrap(); + Database::new(Arc::new(driver)) +} + +/// Raw verification connection used to inspect leader/lease/version state out of band. +async fn connect_raw(connection_string: &str) -> tokio_postgres::Client { + let (client, connection) = tokio_postgres::connect(connection_string, NoTls) + .await + .unwrap(); + tokio::spawn(async move { + let _ = connection.await; + }); + client +} + +struct LeaseRow { + epoch: i64, + leader_addr: String, + durable_version: i64, +} + +async fn read_lease(client: &tokio_postgres::Client) -> Option { + let row = client + .query_opt( + "SELECT epoch, leader_addr, durable_version FROM udb_lease WHERE id = 1", + &[], + ) + .await + .unwrap()?; + Some(LeaseRow { + epoch: row.get(0), + leader_addr: row.get(1), + durable_version: row.get(2), + }) +} + +/// High-water of the LOGGED version sequence. A freshly elected leader must continue from at least +/// this value, never regress below it. +async fn read_seq_high(client: &tokio_postgres::Client) -> i64 { + client + .query_one("SELECT last_value FROM udb_version_seq", &[]) + .await + .unwrap() + .get(0) +} + +/// Poll `udb_lease` until `pred` holds or the deadline passes. +async fn wait_for_lease bool>( + client: &tokio_postgres::Client, + timeout: Duration, + pred: F, +) -> LeaseRow { + let deadline = tokio::time::Instant::now() + timeout; + loop { + if let Some(lease) = read_lease(client).await { + if pred(&lease) { + return lease; + } + } + if tokio::time::Instant::now() >= deadline { + panic!("timed out waiting for lease condition"); + } + tokio::time::sleep(Duration::from_millis(200)).await; + } +} + +async fn write_key(db: &Database, key: &'static [u8], value: &'static [u8]) { + db.txn("test_failover", move |tx| async move { + tx.set(key, value); + Ok(()) + }) + .await + .unwrap(); +} + +async fn read_key(db: &Database, key: &'static [u8]) -> Option> { + db.txn("test_failover", move |tx| async move { + let val = tx.get(key, Serializable).await?; + Ok(val) + }) + .await + .unwrap() + .map(|slice| slice.to_vec()) +} + +/// Exercises leader failover: two nodes share one Postgres, the elected leader is killed, the +/// survivor must take over the lease (new epoch), continue the crash-safe version sequence without +/// regression, preserve the dead leader's committed data, and resume accepting commits. +#[tokio::test] +async fn test_postgres_leader_failover() { + let _ = tracing_subscriber::fmt() + .with_env_filter("info") + .with_test_writer() + .try_init(); + + let (db_config, docker_config) = TestDatabase::Postgres + .config(Uuid::new_v4(), 1) + .await + .unwrap(); + let mut docker_config = docker_config.unwrap(); + docker_config.start().await.unwrap(); + + tokio::time::sleep(Duration::from_secs(4)).await; + + let rivet_config::config::Database::Postgres(postgres_config) = db_config else { + unreachable!(); + }; + let connection_string = postgres_config.url.read().clone(); + + let raw = connect_raw(&connection_string).await; + + // Node 1 comes up first and deterministically wins the first election (epoch 1). + let db1 = make_db(&connection_string).await; + let lease1 = wait_for_lease(&raw, Duration::from_secs(15), |l| l.epoch == 1).await; + let leader1_addr = lease1.leader_addr.clone(); + + // Node 2 joins while node 1 holds a valid lease, so it loses the election and runs as a + // follower. + let db2 = make_db(&connection_string).await; + + // Leader (node 1) commits data. The version sequence and watermark advance. + write_key(&db1, ALPHA_KEY, b"1").await; + + // The follower (node 2) reads through its own snapshot and sees the leader's committed write, + // proving cross-node reads work before any failover. + assert_eq!( + read_key(&db2, ALPHA_KEY).await, + Some(b"1".to_vec()), + "follower must see the leader's committed write" + ); + + let lease_before = read_lease(&raw).await.unwrap(); + let seq_before = read_seq_high(&raw).await; + assert!( + lease_before.durable_version >= 1, + "durable_version must have advanced after the first commit" + ); + + // Kill node 1. Dropping the driver aborts its resolver, so it stops renewing the lease. + drop(db1); + + // Node 2 must take over once node 1's lease expires (TTL is 10s). The epoch is bumped and the + // leader address changes to node 2. + let lease_after = wait_for_lease(&raw, Duration::from_secs(40), |l| { + l.epoch > lease_before.epoch + }) + .await; + assert!( + lease_after.epoch > lease_before.epoch, + "new leader must bump the epoch (was {}, now {})", + lease_before.epoch, + lease_after.epoch + ); + assert_ne!( + lease_after.leader_addr, leader1_addr, + "the surviving node must become the new leader" + ); + + // The crash-safe LOGGED sequence continues from the prior high-water; it never regresses. + let seq_after_takeover = read_seq_high(&raw).await; + assert!( + seq_after_takeover >= seq_before, + "version sequence regressed across failover ({} -> {})", + seq_before, + seq_after_takeover + ); + assert!( + lease_after.durable_version >= lease_before.durable_version, + "durable_version regressed across failover ({} -> {})", + lease_before.durable_version, + lease_after.durable_version + ); + + // The data the dead leader committed survives the failover. + assert_eq!( + read_key(&db2, ALPHA_KEY).await, + Some(b"1".to_vec()), + "committed data must survive leader failover" + ); + + // The new leader resumes accepting commits. + write_key(&db2, BETA_KEY, b"2").await; + assert_eq!( + read_key(&db2, BETA_KEY).await, + Some(b"2".to_vec()), + "new leader must accept and durably apply commits" + ); + + // The new commit advanced the version sequence and watermark past the pre-failover floor, + // confirming the new leader sequences from a strictly higher version. + let lease_final = read_lease(&raw).await.unwrap(); + assert!( + read_seq_high(&raw).await > seq_before, + "a post-failover commit must advance the version sequence" + ); + assert!( + lease_final.durable_version > lease_before.durable_version, + "a post-failover commit must advance the durable watermark" + ); + + drop(db2); +} diff --git a/engine/packages/universalpubsub/src/driver/postgres/mod.rs b/engine/packages/universalpubsub/src/driver/postgres/mod.rs index d361eb1149..2a492fbccf 100644 --- a/engine/packages/universalpubsub/src/driver/postgres/mod.rs +++ b/engine/packages/universalpubsub/src/driver/postgres/mod.rs @@ -40,9 +40,12 @@ const POLL_INTERVAL: Duration = Duration::from_secs(1); /// rather than a cluster outage. const LISTEN_IDLE_IN_TRANSACTION_TIMEOUT_MS: i64 = 30_000; -const QUEUE_SUB_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(10); -/// How long a queue subscriber's heartbeat must be within to be considered active. -const QUEUE_SUB_TTL_SECS: i64 = 30; +/// How often this process refreshes its node liveness heartbeat. One heartbeat per +/// process keeps all of its subscriber registrations alive at once. +const NODE_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(10); +/// How recent a node's heartbeat must be for its subscribers to count as live +/// responders. +const NODE_TTL_SECS: i64 = 30; /// How often to GC expired broadcast messages. const MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(5); @@ -51,19 +54,34 @@ const MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(5); /// messages, matching NATS-core at-most-once semantics for slow consumers. const MESSAGE_MAX_AGE_SECS: i64 = 10; +/// How often to GC dead nodes and the subscriber rows orphaned by them. +const REGISTRY_GC_INTERVAL: Duration = Duration::from_secs(30); + /// How often to GC orphaned queue messages. const QUEUE_MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(300); /// Max age before an unconsumed queue message is garbage collected. const QUEUE_MESSAGE_MAX_AGE_SECS: i64 = 3600; +/// Per-shard signal carried over a subscriber's in-process wakeup channel. +#[derive(Clone)] +enum ShardSignal { + /// A doorbell NOTIFY landed for this shard. Poll the table. + Wakeup, + /// A local request found no responders for the given reply subject. The matching + /// reply subscriber surfaces a no-responders result. + NoResponders { subject: String }, +} + #[derive(Clone)] pub struct PostgresDriver { pool: Arc, client: Arc>>, + /// Identifies this process in the subscriber registry. A single heartbeat keeps + /// all of this node's registrations live. + node_id: String, /// Wakeup channels keyed by doorbell shard channel name. Shared by broadcast and - /// queue subscribers whose subjects map to the same shard. Carries empty wakeups - /// only; payload lives in the table. - shard_subscriptions: Arc>>, + /// queue subscribers whose subjects map to the same shard. + shard_subscriptions: Arc>>, doorbell: Arc, client_ready: tokio::sync::watch::Receiver, } @@ -105,9 +123,10 @@ impl PostgresDriver { tracing::debug!("postgres pool created successfully"); let pool = Arc::new(pool); - let shard_subscriptions: Arc>> = + let shard_subscriptions: Arc>> = Arc::new(HashMap::new()); let client: Arc>> = Arc::new(Mutex::new(None)); + let node_id = Uuid::new_v4().to_string(); // Create channel for client ready notifications let (ready_tx, client_ready) = tokio::sync::watch::channel(false); @@ -128,6 +147,7 @@ impl PostgresDriver { let driver = Self { pool, client, + node_id, shard_subscriptions, doorbell, client_ready, @@ -138,6 +158,7 @@ impl PostgresDriver { // Create tables eagerly so they exist before any publish or subscribe. { + tracing::debug!("configuring postgres udb tables"); let conn = driver .pool .get() @@ -157,11 +178,23 @@ impl PostgresDriver { ); \ CREATE INDEX IF NOT EXISTS ups_messages_subject_id \ ON ups_messages (subject_hash, id); \ + CREATE TABLE IF NOT EXISTS ups_nodes ( \ + node_id TEXT PRIMARY KEY, \ + heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ + ); \ + CREATE TABLE IF NOT EXISTS ups_subs ( \ + id TEXT PRIMARY KEY, \ + node_id TEXT NOT NULL, \ + subject_hash TEXT NOT NULL, \ + subject TEXT NOT NULL \ + ); \ + CREATE INDEX IF NOT EXISTS ups_subs_subject \ + ON ups_subs (subject_hash); \ CREATE TABLE IF NOT EXISTS ups_queue_subs ( \ id TEXT PRIMARY KEY, \ + node_id TEXT NOT NULL, \ subject_hash TEXT NOT NULL, \ - queue_hash TEXT NOT NULL, \ - heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ + queue_hash TEXT NOT NULL \ ); \ CREATE INDEX IF NOT EXISTS ups_queue_subs_subject_queue \ ON ups_queue_subs (subject_hash, queue_hash); \ @@ -177,9 +210,24 @@ impl PostgresDriver { ) .await .context("failed to create tables")?; - tracing::debug!("tables ready"); + tracing::debug!("postgres udb tables ready"); } + // Register this node and start its liveness heartbeat. + driver.heartbeat_node().await?; + let heartbeat_driver = driver.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(NODE_HEARTBEAT_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + interval.tick().await; + if let Err(e) = heartbeat_driver.heartbeat_node().await { + tracing::warn!(?e, "failed to heartbeat node"); + } + } + }); + // Spawn GC task for expired broadcast messages let message_gc_driver = driver.clone(); tokio::spawn(async move { @@ -203,6 +251,49 @@ impl PostgresDriver { } }); + // Spawn GC task for dead nodes and orphaned subscriber rows. + let registry_gc_driver = driver.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(REGISTRY_GC_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + + loop { + interval.tick().await; + if let Ok(conn) = registry_gc_driver.pool.get().await { + if let Err(e) = conn + .execute( + "DELETE FROM ups_nodes \ + WHERE heartbeat_at < NOW() - ($1::bigint * INTERVAL '1 second')", + &[&NODE_TTL_SECS], + ) + .await + { + tracing::warn!(?e, "failed to gc dead nodes"); + } + if let Err(e) = conn + .execute( + "DELETE FROM ups_subs \ + WHERE node_id NOT IN (SELECT node_id FROM ups_nodes)", + &[], + ) + .await + { + tracing::warn!(?e, "failed to gc orphaned subs"); + } + if let Err(e) = conn + .execute( + "DELETE FROM ups_queue_subs \ + WHERE node_id NOT IN (SELECT node_id FROM ups_nodes)", + &[], + ) + .await + { + tracing::warn!(?e, "failed to gc orphaned queue subs"); + } + } + } + }); + // Spawn GC task for orphaned queue messages let gc_driver = driver.clone(); tokio::spawn(async move { @@ -232,7 +323,7 @@ impl PostgresDriver { /// Manages the connection lifecycle with automatic reconnection async fn spawn_connection_lifecycle( conn_str: String, - shard_subscriptions: Arc>>, + shard_subscriptions: Arc>>, client: Arc>>, ready_tx: tokio::sync::watch::Sender, ssl_root_cert_path: Option, @@ -337,7 +428,7 @@ impl PostgresDriver { /// Polls the connection for notifications until it closes or errors async fn poll_connection( mut conn: tokio_postgres::Connection, - shard_subscriptions: Arc>>, + shard_subscriptions: Arc>>, ) where T: tokio_postgres::tls::TlsStream + Unpin, { @@ -348,7 +439,7 @@ impl PostgresDriver { // Doorbell notifications are payload-free wakeup signals only. // Subscribers read their payload from the table. if let Some(sub) = shard_subscriptions.get_async(note.channel()).await { - let _ = sub.send(()); + let _ = sub.send(ShardSignal::Wakeup); } else { tracing::trace!(channel = %note.channel(), "wakeup for unknown shard"); } @@ -404,6 +495,24 @@ impl PostgresDriver { format!("{:x}", hasher.finish()) } + /// Upserts this node's liveness heartbeat. Re-inserts the row if a GC pass removed + /// it after a transient stall. + async fn heartbeat_node(&self) -> Result<()> { + let conn = self + .pool + .get() + .await + .context("failed to get connection for node heartbeat")?; + conn.execute( + "INSERT INTO ups_nodes (node_id, heartbeat_at) VALUES ($1, NOW()) \ + ON CONFLICT (node_id) DO UPDATE SET heartbeat_at = NOW()", + &[&self.node_id], + ) + .await + .context("failed to upsert node heartbeat")?; + Ok(()) + } + /// Returns the current max broadcast message id, used as a subscriber's starting /// cursor so it only sees future messages (NATS at-most-once, no replay). async fn current_max_id(&self) -> Result { @@ -424,7 +533,10 @@ impl PostgresDriver { async fn ensure_shard_listen( &self, shard: usize, - ) -> (broadcast::Receiver<()>, tokio_util::sync::DropGuard) { + ) -> ( + broadcast::Receiver, + tokio_util::sync::DropGuard, + ) { let channel = shard_channel(shard); match self.shard_subscriptions.entry_async(channel.clone()).await { @@ -465,7 +577,7 @@ impl PostgresDriver { fn spawn_shard_cleanup_task( &self, channel: String, - tx: broadcast::Sender<()>, + tx: broadcast::Sender, ) -> tokio_util::sync::DropGuard { let driver = self.clone(); let token = tokio_util::sync::CancellationToken::new(); @@ -515,14 +627,15 @@ impl PostgresDriver { .await .context("failed to insert broadcast message")?; - // Queue rows for every active queue group on this subject. Batched into the - // same transaction so a crash never strands a row mid-publish. + // Queue rows for every live queue group on this subject. Batched into the same + // transaction so a crash never strands a row mid-publish. let rows = tx .query( - "SELECT DISTINCT queue_hash FROM ups_queue_subs \ - WHERE subject_hash = $1 \ - AND heartbeat_at > NOW() - ($2::bigint * INTERVAL '1 second')", - &[&subject_hash, &QUEUE_SUB_TTL_SECS], + "SELECT DISTINCT s.queue_hash FROM ups_queue_subs s \ + JOIN ups_nodes n ON s.node_id = n.node_id \ + WHERE s.subject_hash = $1 \ + AND n.heartbeat_at > NOW() - ($2::bigint * INTERVAL '1 second')", + &[&subject_hash, &NODE_TTL_SECS], ) .await .context("failed to query active queue subs")?; @@ -542,6 +655,50 @@ impl PostgresDriver { Ok(()) } + + /// Returns whether any live subscriber (broadcast or queue) exists for the subject + /// anywhere in the fleet. Used to decide whether a request surfaces a no-responders + /// result instead of waiting out its timeout. + async fn has_responders(&self, subject_hash: &str, subject: &str) -> Result { + let conn = self + .pool + .get() + .await + .context("failed to get connection for responder check")?; + let row = conn + .query_one( + "SELECT \ + EXISTS( \ + SELECT 1 FROM ups_subs s \ + JOIN ups_nodes n ON s.node_id = n.node_id \ + WHERE s.subject_hash = $1 AND s.subject = $2 \ + AND n.heartbeat_at > NOW() - ($3::bigint * INTERVAL '1 second') \ + ) \ + OR EXISTS( \ + SELECT 1 FROM ups_queue_subs s \ + JOIN ups_nodes n ON s.node_id = n.node_id \ + WHERE s.subject_hash = $1 \ + AND n.heartbeat_at > NOW() - ($3::bigint * INTERVAL '1 second') \ + )", + &[&subject_hash, &subject, &NODE_TTL_SECS], + ) + .await + .context("failed to check responders")?; + Ok(row.get(0)) + } + + /// Delivers a no-responders result to the local reply subscriber. The requester is + /// always in this process, so the signal is routed in-memory over the reply + /// subject's shard channel rather than the table. + async fn signal_no_responders(&self, reply_subject: &str) { + let reply_hash = self.hash_subject(reply_subject); + let channel = shard_channel(shard_for(&reply_hash)); + if let Some(tx) = self.shard_subscriptions.get_async(&channel).await { + let _ = tx.send(ShardSignal::NoResponders { + subject: reply_subject.to_string(), + }); + } + } } #[async_trait] @@ -549,7 +706,7 @@ impl PubSubDriver for PostgresDriver { async fn subscribe( &self, subject: &str, - _reply_id: Option, + reply_id: Option, ) -> Result { let subject_hash = self.hash_subject(subject); let shard = shard_for(&subject_hash); @@ -561,6 +718,28 @@ impl PubSubDriver for PostgresDriver { let (rx, drop_guard) = self.ensure_shard_listen(shard).await; + // Register in the responder registry so requests to this subject can detect + // responders. Reply inboxes are never request targets, so they skip the + // registry to keep request latency off this path. + let sub_id = if reply_id.is_none() { + let sub_id = Uuid::new_v4().to_string(); + let conn = self + .pool + .get() + .await + .context("failed to get connection for subscribe")?; + conn.execute( + "INSERT INTO ups_subs (id, node_id, subject_hash, subject) \ + VALUES ($1, $2, $3, $4)", + &[&sub_id, &self.node_id, &subject_hash, &subject], + ) + .await + .context("failed to register subscriber")?; + Some(sub_id) + } else { + None + }; + Ok(Box::new(PostgresSubscriber { subject: subject.to_string(), subject_hash, @@ -568,6 +747,7 @@ impl PubSubDriver for PostgresDriver { cursor, buffer: VecDeque::new(), rx, + sub_id, _drop_guard: drop_guard, })) } @@ -586,8 +766,9 @@ impl PubSubDriver for PostgresDriver { .await .context("failed to get connection for queue subscribe")?; conn.execute( - "INSERT INTO ups_queue_subs (id, subject_hash, queue_hash) VALUES ($1, $2, $3)", - &[&sub_id, &subject_hash, &queue_hash], + "INSERT INTO ups_queue_subs (id, node_id, subject_hash, queue_hash) \ + VALUES ($1, $2, $3, $4)", + &[&sub_id, &self.node_id, &subject_hash, &queue_hash], ) .await .context("failed to register queue subscriber")?; @@ -595,35 +776,6 @@ impl PubSubDriver for PostgresDriver { let (rx, drop_guard) = self.ensure_shard_listen(shard).await; - // Spawn heartbeat task to keep the registration alive - let pool = self.pool.clone(); - let sub_id_for_heartbeat = sub_id.clone(); - let heartbeat_token = tokio_util::sync::CancellationToken::new(); - let heartbeat_token_child = heartbeat_token.clone(); - tokio::spawn(async move { - let mut interval = tokio::time::interval(QUEUE_SUB_HEARTBEAT_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - tokio::select! { - _ = heartbeat_token_child.cancelled() => break, - _ = interval.tick() => { - if let Ok(conn) = pool.get().await { - if let Err(e) = conn - .execute( - "UPDATE ups_queue_subs SET heartbeat_at = NOW() WHERE id = $1", - &[&sub_id_for_heartbeat], - ) - .await - { - tracing::warn!(?e, id = %sub_id_for_heartbeat, "failed to heartbeat queue sub"); - } - } - } - } - } - }); - Ok(Box::new(PostgresQueueSubscriber { subject: subject.to_string(), subject_hash, @@ -632,7 +784,6 @@ impl PubSubDriver for PostgresDriver { pool: self.pool.clone(), rx, _drop_guard: drop_guard, - _heartbeat_token: heartbeat_token, })) } @@ -640,11 +791,29 @@ impl PubSubDriver for PostgresDriver { &self, subject: &str, payload: &[u8], - _reply_subject: Option<&str>, + reply_subject: Option<&str>, ) -> Result<()> { let subject_hash = self.hash_subject(subject); let shard = shard_for(&subject_hash); + // Request semantics: if a reply is expected and no responder exists anywhere, + // surface a no-responders result immediately instead of persisting a message + // nobody will read. + if let Some(reply_subject) = reply_subject { + match self.has_responders(&subject_hash, subject).await { + Result::Ok(false) => { + self.signal_no_responders(reply_subject).await; + return Ok(()); + } + Result::Ok(true) => {} + Result::Err(e) => { + // On a failed check, fall through to a normal publish rather than + // risk a false no-responders result. + tracing::warn!(?e, %subject, "responder check failed, publishing anyway"); + } + } + } + // Persist the message, retrying on transient connection errors. The row is // committed before the doorbell rings so any wakeup observes it. let mut backoff = Backoff::default(); @@ -686,7 +855,9 @@ pub struct PostgresSubscriber { pool: Arc, cursor: i64, buffer: VecDeque>, - rx: broadcast::Receiver<()>, + rx: broadcast::Receiver, + /// Responder-registry row id, present for non-inbox subscriptions. Deleted on drop. + sub_id: Option, _drop_guard: tokio_util::sync::DropGuard, } @@ -744,11 +915,17 @@ impl SubscriberDriver for PostgresSubscriber { continue; } - // Wait for a doorbell wakeup or the poll backstop, whichever is first. + // Wait for a doorbell wakeup, a no-responders signal, or the poll backstop. tokio::select! { res = self.rx.recv() => { match res { - std::result::Result::Ok(()) => {} + std::result::Result::Ok(ShardSignal::Wakeup) => {} + std::result::Result::Ok(ShardSignal::NoResponders { subject }) + if subject == self.subject => + { + return Ok(DriverOutput::NoResponders); + } + std::result::Result::Ok(ShardSignal::NoResponders { .. }) => {} Err(broadcast::error::RecvError::Lagged(_)) => {} Err(broadcast::error::RecvError::Closed) => { return Ok(DriverOutput::Unsubscribed); @@ -761,15 +938,33 @@ impl SubscriberDriver for PostgresSubscriber { } } +impl Drop for PostgresSubscriber { + fn drop(&mut self) { + let Some(sub_id) = self.sub_id.take() else { + return; + }; + let pool = self.pool.clone(); + tokio::spawn(async move { + if let Ok(conn) = pool.get().await { + if let Err(e) = conn + .execute("DELETE FROM ups_subs WHERE id = $1", &[&sub_id]) + .await + { + tracing::warn!(?e, %sub_id, "failed to deregister subscriber"); + } + } + }); + } +} + pub struct PostgresQueueSubscriber { subject: String, subject_hash: String, queue_hash: String, sub_id: String, pool: Arc, - rx: broadcast::Receiver<()>, + rx: broadcast::Receiver, _drop_guard: tokio_util::sync::DropGuard, - _heartbeat_token: tokio_util::sync::CancellationToken, } impl PostgresQueueSubscriber { @@ -820,11 +1015,11 @@ impl SubscriberDriver for PostgresQueueSubscriber { } } - // Wait for a doorbell wakeup or the poll backstop, then loop back to claim. + // Wait for any shard signal or the poll backstop, then loop back to claim. tokio::select! { res = self.rx.recv() => { match res { - std::result::Result::Ok(()) => {} + std::result::Result::Ok(_) => {} Err(broadcast::error::RecvError::Lagged(_)) => {} Err(broadcast::error::RecvError::Closed) => { return Ok(DriverOutput::Unsubscribed); diff --git a/engine/packages/universalpubsub/tests/reconnect.rs b/engine/packages/universalpubsub/tests/reconnect.rs index 35ace375d3..8c15f6ab54 100644 --- a/engine/packages/universalpubsub/tests/reconnect.rs +++ b/engine/packages/universalpubsub/tests/reconnect.rs @@ -43,7 +43,7 @@ async fn test_nats_driver_with_memory_reconnect() { .unwrap(); let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), true); - test_all_inner(&pubsub, &docker).await; + test_all_inner(&pubsub, &docker, true).await; } #[tokio::test] @@ -77,7 +77,7 @@ async fn test_nats_driver_without_memory_reconnect() { .unwrap(); let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - test_all_inner(&pubsub, &docker).await; + test_all_inner(&pubsub, &docker, true).await; } #[tokio::test] @@ -95,13 +95,12 @@ async fn test_postgres_driver_with_memory_reconnect() { }; let url = pg.url.read().clone(); - let driver = - universalpubsub::driver::postgres::PostgresDriver::connect(url, true, None, None, None) - .await - .unwrap(); + let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) + .await + .unwrap(); let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), true); - test_all_inner(&pubsub, &docker).await; + test_all_inner(&pubsub, &docker, false).await; } #[tokio::test] @@ -119,19 +118,27 @@ async fn test_postgres_driver_without_memory_reconnect() { }; let url = pg.url.read().clone(); - let driver = - universalpubsub::driver::postgres::PostgresDriver::connect(url, false, None, None, None) - .await - .unwrap(); + let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) + .await + .unwrap(); let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - test_all_inner(&pubsub, &docker).await; + test_all_inner(&pubsub, &docker, false).await; } -async fn test_all_inner(pubsub: &PubSub, docker: &rivet_test_deps_docker::DockerRunConfig) { +async fn test_all_inner( + pubsub: &PubSub, + docker: &rivet_test_deps_docker::DockerRunConfig, + supports_subscribe_while_stopped: bool, +) { test_reconnect_inner(&pubsub, &docker).await; test_publish_while_stopped(&pubsub, &docker).await; - test_subscribe_while_stopped(&pubsub, &docker).await; + // The table-backed Postgres driver must read its cursor and register in the + // responder table when subscribing, so it cannot subscribe while the backend is + // fully down. NATS buffers the subscribe and reconnects, so it can. + if supports_subscribe_while_stopped { + test_subscribe_while_stopped(&pubsub, &docker).await; + } } async fn test_reconnect_inner(pubsub: &PubSub, docker: &rivet_test_deps_docker::DockerRunConfig) { diff --git a/scripts/run/engine-postgres.sh b/scripts/run/engine-postgres.sh index 966c421bec..70689517bd 100755 --- a/scripts/run/engine-postgres.sh +++ b/scripts/run/engine-postgres.sh @@ -4,19 +4,26 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -if ! command -v nc >/dev/null 2>&1; then - echo "error: required command 'nc' not found." - exit 1 -fi +POSTGRES_IMAGE="postgres:18" + +# pg_isready reports ready only once the server is actually accepting connections. +# The Postgres entrypoint binds the port during its bootstrap phase and then +# restarts, so a plain port check (nc -z) passes too early and the engine hits +# "connection reset" / "early eof" on first connect. Run pg_isready from a throwaway +# container on the host network so no client binary needs to be installed locally. +postgres_ready() { + docker run --rm --network host "${POSTGRES_IMAGE}" \ + pg_isready -h localhost -p 5432 -U postgres -d postgres >/dev/null 2>&1 +} -if ! nc -z localhost 5432 >/dev/null 2>&1; then - echo "Postgres is not reachable at localhost:5432." +if ! postgres_ready; then + echo "Postgres is not accepting connections." echo "Starting postgres container..." "${SCRIPT_DIR}/postgres.sh" echo "Waiting for postgres to be ready..." for i in {1..30}; do - if nc -z localhost 5432 >/dev/null 2>&1; then + if postgres_ready; then echo "Postgres is ready!" break fi From 833bfeb718b468e794b8083773393a20f19a957a Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 13:07:04 -0700 Subject: [PATCH 11/19] [SLOP(claude-opus-4-8-high)] feat(universaldb): graceful postgres leader handoff on shutdown --- engine/packages/service-manager/src/lib.rs | 5 +- engine/packages/universaldb/src/database.rs | 5 ++ engine/packages/universaldb/src/driver/mod.rs | 7 ++ .../src/driver/postgres/database.rs | 11 +++ .../src/driver/postgres/resolver/lease.rs | 24 +++++++ .../src/driver/postgres/resolver/mod.rs | 65 ++++++++++++++++- .../universaldb/src/driver/postgres/shared.rs | 4 ++ engine/packages/universaldb/tests/failover.rs | 70 +++++++++++++++++++ 8 files changed, 187 insertions(+), 4 deletions(-) diff --git a/engine/packages/service-manager/src/lib.rs b/engine/packages/service-manager/src/lib.rs index a60854f3e0..0b84fbae82 100644 --- a/engine/packages/service-manager/src/lib.rs +++ b/engine/packages/service-manager/src/lib.rs @@ -393,7 +393,7 @@ pub async fn start( if abort { // Give time for services to handle final abort tokio::time::sleep(Duration::from_millis(50)).await; - rivet_runtime::shutdown().await; // TODO: Fix `JoinHandle polled after completion` error + rivet_runtime::shutdown().await; break; } @@ -401,6 +401,9 @@ pub async fn start( } } + // Shut down udb + pools.udb()?.shutdown().await; + // Stops term signal handler bg task rivet_runtime::TermSignal::stop(); diff --git a/engine/packages/universaldb/src/database.rs b/engine/packages/universaldb/src/database.rs index c2f95983a4..a5c65dd0f5 100644 --- a/engine/packages/universaldb/src/database.rs +++ b/engine/packages/universaldb/src/database.rs @@ -106,4 +106,9 @@ impl Database { pub fn checkpoint(&self, path: &Path) -> Result<()> { self.driver.checkpoint(path) } + + /// Gracefully release process-wide driver resources before shutdown. + pub async fn shutdown(&self) { + self.driver.shutdown().await; + } } diff --git a/engine/packages/universaldb/src/driver/mod.rs b/engine/packages/universaldb/src/driver/mod.rs index 01f7c22b2c..7b19e90fef 100644 --- a/engine/packages/universaldb/src/driver/mod.rs +++ b/engine/packages/universaldb/src/driver/mod.rs @@ -34,6 +34,13 @@ pub trait DatabaseDriver: Send + Sync { fn checkpoint(&self, _path: &Path) -> Result<()> { bail!("checkpoint not supported by this database driver") } + + /// Gracefully release any process-wide resources before shutdown. The Postgres driver hands off + /// its leader lease here so a standby node takes over immediately instead of waiting out the + /// lease TTL. Default is a no-op. + fn shutdown<'a>(&'a self) -> BoxFut<'a, ()> { + Box::pin(async {}) + } } pub trait TransactionDriver: Send + Sync { diff --git a/engine/packages/universaldb/src/driver/postgres/database.rs b/engine/packages/universaldb/src/driver/postgres/database.rs index 6cded0f320..3ff891011f 100644 --- a/engine/packages/universaldb/src/driver/postgres/database.rs +++ b/engine/packages/universaldb/src/driver/postgres/database.rs @@ -290,6 +290,17 @@ impl DatabaseDriver for PostgresDatabaseDriver { self.max_retries.store(limit, Ordering::SeqCst); Ok(()) } + + fn shutdown<'a>(&'a self) -> BoxFut<'a, ()> { + Box::pin(async move { + // Stop renewing the lease before releasing it so a racing renew cannot re-extend it. + self.resolver_handle.abort(); + self.gc_handle.abort(); + + // Hand off leadership immediately if we hold it, instead of waiting out the lease TTL. + resolver::handoff(&self.shared).await; + }) + } } impl Drop for PostgresDatabaseDriver { diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs b/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs index a17b6c7759..93f7f7884e 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/lease.rs @@ -62,6 +62,30 @@ pub async fn renew(pool: &Pool, node_id: &str, epoch: i64) -> Result { Ok(updated == 1) } +/// Gracefully release the lease so a standby node can take over immediately instead of waiting out +/// the TTL. Expires the lease in place, fenced on this node's address so it never clobbers a +/// successor that already took over. Returns `true` if our lease was released (i.e. we were the +/// leader); `false` is the normal no-op when this node is a follower. Renewal must already be +/// stopped before calling this, otherwise a racing renew could re-extend the lease. +pub async fn release(pool: &Pool, node_id: &str) -> Result { + let conn = pool + .get() + .await + .context("failed to get connection for lease release")?; + + let updated = conn + .execute( + "UPDATE udb_lease + SET expires_at = now() + WHERE id = $1 AND leader_addr = $2", + &[&LEASE_ID, &node_id], + ) + .await + .context("failed to release lease")?; + + Ok(updated == 1) +} + /// Read the current durable version (`udb_lease.durable_version`). Used by a freshly elected leader /// to learn the watermark floor it must continue from. pub async fn current_durable_version(pool: &Pool) -> Result { diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs index 3dfac50fce..f9b6c45461 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -7,10 +7,13 @@ use std::{ }; use anyhow::{Context, Result}; +use tokio::sync::broadcast; use crate::{conflict_tracker::TransactionConflictTracker, transaction::TXN_TIMEOUT}; -use super::shared::{LEASE_ID, LeaseInfo, PostgresShared, WATERMARK_CHANNEL, commit_channel}; +use super::shared::{ + ELECTION_CHANNEL, LEASE_ID, LeaseInfo, PostgresShared, WATERMARK_CHANNEL, commit_channel, +}; /// Max commits resolved+applied per batch (group commit). Amortizes the resolver, Postgres /// round-trips, and fsync across the batch. @@ -40,6 +43,10 @@ pub fn spawn(shared: Arc) -> tokio::task::JoinHandle<()> { } async fn run(shared: Arc) { + // A departing leader NOTIFYs this channel after releasing its lease so we elect immediately + // rather than waiting out the full `ELECTION_RETRY` tick. + let mut election_rx = shared.listener.listen(ELECTION_CHANNEL).await; + loop { match lease::try_acquire(&shared.pool, &shared.node_id).await { Ok(Some(acquired)) => { @@ -50,16 +57,68 @@ async fn run(shared: Arc) { tracing::info!(epoch = acquired.epoch, "stepped down from udb leader"); } Ok(None) => { - tokio::time::sleep(ELECTION_RETRY).await; + wait_for_election_retry(&shared, &mut election_rx).await; } Err(err) => { tracing::warn!(?err, "failed udb lease acquire attempt"); - tokio::time::sleep(ELECTION_RETRY).await; + wait_for_election_retry(&shared, &mut election_rx).await; + } + } + } +} + +/// Wait before retrying the election: either the `ELECTION_RETRY` backstop elapses, or a departing +/// leader wakes us via `ELECTION_CHANNEL` so handoff is near-instant. +async fn wait_for_election_retry( + shared: &Arc, + election_rx: &mut broadcast::Receiver, +) { + tokio::select! { + _ = tokio::time::sleep(ELECTION_RETRY) => {} + res = election_rx.recv() => { + if matches!(res, Err(broadcast::error::RecvError::Closed)) { + // The listener recreates the channel on reconnect; re-subscribe. + *election_rx = shared.listener.listen(ELECTION_CHANNEL).await; } } } } +/// Best-effort graceful leadership handoff invoked on shutdown. If this node currently holds the +/// lease, expire it and wake a standby so it takes over immediately instead of waiting out the TTL. +/// Safe to call on a follower: the fenced release matches no row and nothing is notified. The +/// caller must already have stopped lease renewal before calling this. +pub async fn handoff(shared: &Arc) { + match lease::release(&shared.pool, &shared.node_id).await { + Ok(true) => { + tracing::info!(node_id = %shared.node_id, "released udb leader lease for graceful handoff"); + notify_election(shared).await; + } + Ok(false) => {} + Err(err) => { + tracing::warn!(?err, "failed to release udb lease on shutdown"); + } + } +} + +/// Wake standby candidates so the next election fires immediately after a graceful release. +async fn notify_election(shared: &Arc) { + let conn = match shared.pool.get().await { + Ok(conn) => conn, + Err(err) => { + tracing::debug!(?err, "failed to get connection for election notify"); + return; + } + }; + + if let Err(err) = conn + .execute("SELECT pg_notify($1, '')", &[&ELECTION_CHANNEL]) + .await + { + tracing::debug!(?err, "failed to notify election channel"); + } +} + /// Leader main loop: hold the lease, drain the commit queue on wake or poll, and renew the lease. async fn lead(shared: &Arc, epoch: i64) -> Result<()> { // Publish our own lease into the cache immediately so our local commits route to us. diff --git a/engine/packages/universaldb/src/driver/postgres/shared.rs b/engine/packages/universaldb/src/driver/postgres/shared.rs index 0ab2963158..5b14c4df08 100644 --- a/engine/packages/universaldb/src/driver/postgres/shared.rs +++ b/engine/packages/universaldb/src/driver/postgres/shared.rs @@ -32,6 +32,10 @@ pub fn reply_channel(node_id: &str) -> String { /// Channel the leader NOTIFYs on every watermark advance; all nodes LISTEN. pub const WATERMARK_CHANNEL: &str = "udb_watermark"; +/// Channel a departing leader NOTIFYs after releasing its lease so a standby candidate elects +/// immediately instead of waiting out `ELECTION_RETRY`. All non-leader candidates LISTEN. +pub const ELECTION_CHANNEL: &str = "udb_election"; + /// Cached view of the current leader lease, as seen by a follower. #[derive(Clone, Debug)] pub struct LeaseInfo { diff --git a/engine/packages/universaldb/tests/failover.rs b/engine/packages/universaldb/tests/failover.rs index 7d122ca781..5fb394453c 100644 --- a/engine/packages/universaldb/tests/failover.rs +++ b/engine/packages/universaldb/tests/failover.rs @@ -217,3 +217,73 @@ async fn test_postgres_leader_failover() { drop(db2); } + +/// Exercises graceful leader handoff: a leader that is shut down cleanly (SIGTERM path) releases its +/// lease immediately instead of letting it expire, so a standby takes over well within the lease TTL +/// rather than after it. This is what turns a rolling deploy from a ~TTL commit stall into a +/// near-instant handoff. +#[tokio::test] +async fn test_postgres_graceful_handoff() { + let _ = tracing_subscriber::fmt() + .with_env_filter("info") + .with_test_writer() + .try_init(); + + let (db_config, docker_config) = TestDatabase::Postgres + .config(Uuid::new_v4(), 1) + .await + .unwrap(); + let mut docker_config = docker_config.unwrap(); + docker_config.start().await.unwrap(); + + tokio::time::sleep(Duration::from_secs(4)).await; + + let rivet_config::config::Database::Postgres(postgres_config) = db_config else { + unreachable!(); + }; + let connection_string = postgres_config.url.read().clone(); + + let raw = connect_raw(&connection_string).await; + + // Node 1 wins the first election; node 2 joins as a follower. + let db1 = make_db(&connection_string).await; + let lease1 = wait_for_lease(&raw, Duration::from_secs(15), |l| l.epoch == 1).await; + let leader1_addr = lease1.leader_addr.clone(); + let db2 = make_db(&connection_string).await; + + write_key(&db1, ALPHA_KEY, b"1").await; + let lease_before = read_lease(&raw).await.unwrap(); + + // Gracefully shut down the leader. Unlike a hard drop, this releases the lease in place and + // wakes the standby, so takeover must complete in well under the 10s TTL. + let handoff_start = tokio::time::Instant::now(); + db1.shutdown().await; + + // The lease TTL is 10s; a graceful handoff must take over well under that. The 5s deadline here + // is itself below the TTL, so reaching this line already proves the lease was not waited out. + let lease_after = wait_for_lease(&raw, Duration::from_secs(5), |l| { + l.epoch > lease_before.epoch + }) + .await; + let handoff_elapsed = handoff_start.elapsed(); + assert!( + handoff_elapsed < Duration::from_secs(8), + "graceful handoff must beat the lease TTL (took {handoff_elapsed:?})" + ); + assert_ne!( + lease_after.leader_addr, leader1_addr, + "the standby must become the new leader after a graceful handoff" + ); + + // The new leader serves the old leader's data and accepts fresh commits. + assert_eq!( + read_key(&db2, ALPHA_KEY).await, + Some(b"1".to_vec()), + "committed data must survive graceful handoff" + ); + write_key(&db2, BETA_KEY, b"2").await; + assert_eq!(read_key(&db2, BETA_KEY).await, Some(b"2".to_vec())); + + drop(db1); + drop(db2); +} From dce315dde40e16ff745d891900046c74485be437 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 13:36:10 -0700 Subject: [PATCH 12/19] [slopfix] fix(self-host): repair dev compose generation, kitchen-sink serverful runner, and postgres/udb bootstrap --- engine/packages/metrics/src/registry.rs | 4 +- examples/kitchen-sink/Dockerfile | 7 +- .../compose/prod-file-system/.gitattributes | 1 - self-host/compose/prod-file-system/README.md | 76 ------ .../prod-file-system/docker-compose.yml | 41 ---- .../rivet-engine/config.jsonc | 2 - .../compose/template/src/docker-compose.ts | 75 +++++- .../src/services/edge/rivet-engine.ts | 16 +- .../template/src/services/edge/runner.ts | 5 +- .../{compose => }/dev-host/.gitattributes | 0 self-host/{compose => }/dev-host/README.md | 0 .../dev-host/clickhouse/client-config.xml | 0 .../dev-host/clickhouse/config.xml | 0 .../clickhouse/init/01-create-otel-table.sql | 0 .../dev-host/clickhouse/users.xml | 0 .../{compose => }/dev-host/docker-compose.yml | 44 +++- .../dev-host/grafana/dashboards/api.json | 0 .../dev-host/grafana/dashboards/cache.json | 0 .../dev-host/grafana/dashboards/epoxy.json | 0 .../dev-host/grafana/dashboards/futures.json | 0 .../dev-host/grafana/dashboards/gasoline.json | 0 .../dev-host/grafana/dashboards/guard.json | 0 .../grafana/dashboards/operation.json | 0 .../dev-host/grafana/dashboards/pegboard.json | 0 .../dev-host/grafana/dashboards/tokio.json | 0 .../dev-host/grafana/dashboards/traces.json | 0 .../dev-host/grafana/grafana.ini | 0 .../provisioning/dashboards/dashboards.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../dev-host/otel-collector/config.yaml | 0 .../dev-host/postgres/init-db.sh | 0 .../dev-host/prometheus/prometheus.yml | 0 .../dev-host/rivet-engine/config.jsonc | 12 +- .../dev-host/vector-client/vector.yaml | 0 .../dev-host/vector-server/vector.yaml | 0 .../dev-multidc-multinode/.gitattributes | 0 .../dev-multidc-multinode/README.md | 0 .../core/clickhouse/client-config.xml | 0 .../core/clickhouse/config.xml | 0 .../clickhouse/init/01-create-otel-table.sql | 0 .../core/clickhouse/users.xml | 0 .../core/grafana/dashboards/api.json | 0 .../core/grafana/dashboards/cache.json | 0 .../core/grafana/dashboards/epoxy.json | 0 .../core/grafana/dashboards/futures.json | 0 .../core/grafana/dashboards/gasoline.json | 0 .../core/grafana/dashboards/guard.json | 0 .../core/grafana/dashboards/operation.json | 0 .../core/grafana/dashboards/pegboard.json | 0 .../core/grafana/dashboards/tokio.json | 0 .../core/grafana/dashboards/traces.json | 0 .../core/grafana/grafana.ini | 0 .../provisioning/dashboards/dashboards.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../core/prometheus/prometheus.yml | 0 .../dc-a/otel-collector/config.yaml | 0 .../datacenters/dc-a/postgres/init-db.sh | 0 .../dc-a/rivet-engine/0}/config.jsonc | 12 +- .../dc-a/rivet-engine/1}/config.jsonc | 12 +- .../dc-a/rivet-engine/2}/config.jsonc | 12 +- .../dc-a/vector-client/vector.yaml | 0 .../dc-a/vector-server/vector.yaml | 0 .../dc-b/otel-collector/config.yaml | 0 .../datacenters/dc-b/postgres/init-db.sh | 0 .../dc-b/rivet-engine/0}/config.jsonc | 12 +- .../dc-b/rivet-engine/1}/config.jsonc | 12 +- .../dc-b/rivet-engine/2/config.jsonc | 12 +- .../dc-b/vector-client/vector.yaml | 0 .../dc-b/vector-server/vector.yaml | 0 .../dc-c/otel-collector/config.yaml | 0 .../datacenters/dc-c/postgres/init-db.sh | 0 .../dc-c/rivet-engine/0/config.jsonc | 12 +- .../dc-c/rivet-engine/1/config.jsonc | 12 +- .../dc-c/rivet-engine/2/config.jsonc | 12 +- .../dc-c/vector-client/vector.yaml | 0 .../dc-c/vector-server/vector.yaml | 0 .../dev-multidc-multinode/docker-compose.yml | 216 ++++++++++++------ .../{compose => }/dev-multidc/.gitattributes | 0 self-host/{compose => }/dev-multidc/README.md | 0 .../core/clickhouse/client-config.xml | 0 .../dev-multidc/core/clickhouse/config.xml | 0 .../clickhouse/init/01-create-otel-table.sql | 0 .../dev-multidc/core/clickhouse/users.xml | 0 .../core/grafana/dashboards/api.json | 0 .../core/grafana/dashboards/cache.json | 0 .../core/grafana/dashboards/epoxy.json | 0 .../core/grafana/dashboards/futures.json | 0 .../core/grafana/dashboards/gasoline.json | 0 .../core/grafana/dashboards/guard.json | 0 .../core/grafana/dashboards/operation.json | 0 .../core/grafana/dashboards/pegboard.json | 0 .../core/grafana/dashboards/tokio.json | 0 .../core/grafana/dashboards/traces.json | 0 .../dev-multidc/core/grafana/grafana.ini | 0 .../provisioning/dashboards/dashboards.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../core/prometheus/prometheus.yml | 0 .../dc-a/otel-collector/config.yaml | 0 .../datacenters/dc-a/postgres/init-db.sh | 0 .../dc-a/rivet-engine/config.jsonc | 12 +- .../dc-a/vector-client/vector.yaml | 0 .../dc-a/vector-server/vector.yaml | 0 .../dc-b/otel-collector/config.yaml | 0 .../datacenters/dc-b/postgres/init-db.sh | 0 .../dc-b/rivet-engine/config.jsonc | 12 +- .../dc-b/vector-client/vector.yaml | 0 .../dc-b/vector-server/vector.yaml | 0 .../dc-c/otel-collector/config.yaml | 0 .../datacenters/dc-c/postgres/init-db.sh | 0 .../dc-c/rivet-engine/config.jsonc | 12 +- .../dc-c/vector-client/vector.yaml | 0 .../dc-c/vector-server/vector.yaml | 0 .../dev-multidc/docker-compose.yml | 102 ++++++--- .../dev-multinode/.gitattributes | 0 .../{compose => }/dev-multinode/README.md | 0 .../clickhouse/client-config.xml | 0 .../dev-multinode/clickhouse/config.xml | 0 .../clickhouse/init/01-create-otel-table.sql | 0 .../dev-multinode/clickhouse/users.xml | 0 .../dev-multinode/docker-compose.yml | 85 +++++-- .../dev-multinode/grafana/dashboards/api.json | 0 .../grafana/dashboards/cache.json | 0 .../grafana/dashboards/epoxy.json | 0 .../grafana/dashboards/futures.json | 0 .../grafana/dashboards/gasoline.json | 0 .../grafana/dashboards/guard.json | 0 .../grafana/dashboards/operation.json | 0 .../grafana/dashboards/pegboard.json | 0 .../grafana/dashboards/tokio.json | 0 .../grafana/dashboards/traces.json | 0 .../dev-multinode/grafana/grafana.ini | 0 .../provisioning/dashboards/dashboards.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../dev-multinode/otel-collector/config.yaml | 0 .../dev-multinode/postgres/init-db.sh | 0 .../dev-multinode/prometheus/prometheus.yml | 0 .../rivet-engine/0}/config.jsonc | 12 +- .../rivet-engine/1}/config.jsonc | 12 +- .../rivet-engine/2}/config.jsonc | 12 +- .../dev-multinode/vector-client/vector.yaml | 0 .../dev-multinode/vector-server/vector.yaml | 0 self-host/{compose => }/dev/.gitattributes | 0 self-host/{compose => }/dev/README.md | 0 .../dev/clickhouse/client-config.xml | 0 .../{compose => }/dev/clickhouse/config.xml | 0 .../clickhouse/init/01-create-otel-table.sql | 0 .../{compose => }/dev/clickhouse/users.xml | 0 .../{compose => }/dev/docker-compose.yml | 47 +++- .../dev/grafana/dashboards/api.json | 0 .../dev/grafana/dashboards/cache.json | 0 .../dev/grafana/dashboards/epoxy.json | 0 .../dev/grafana/dashboards/futures.json | 0 .../dev/grafana/dashboards/gasoline.json | 0 .../dev/grafana/dashboards/guard.json | 0 .../dev/grafana/dashboards/operation.json | 0 .../dev/grafana/dashboards/pegboard.json | 0 .../dev/grafana/dashboards/tokio.json | 0 .../dev/grafana/dashboards/traces.json | 0 .../{compose => }/dev/grafana/grafana.ini | 0 .../provisioning/dashboards/dashboards.yaml | 0 .../provisioning/datasources/datasources.yaml | 0 .../dev/otel-collector/config.yaml | 0 .../{compose => }/dev/postgres/init-db.sh | 0 .../dev/prometheus/prometheus.yml | 0 .../dev/rivet-engine/config.jsonc | 12 +- .../dev/vector-client/vector.yaml | 0 .../dev/vector-server/vector.yaml | 0 167 files changed, 468 insertions(+), 457 deletions(-) delete mode 100644 self-host/compose/prod-file-system/.gitattributes delete mode 100644 self-host/compose/prod-file-system/README.md delete mode 100644 self-host/compose/prod-file-system/docker-compose.yml delete mode 100644 self-host/compose/prod-file-system/rivet-engine/config.jsonc rename self-host/{compose => }/dev-host/.gitattributes (100%) rename self-host/{compose => }/dev-host/README.md (100%) rename self-host/{compose => }/dev-host/clickhouse/client-config.xml (100%) rename self-host/{compose => }/dev-host/clickhouse/config.xml (100%) rename self-host/{compose => }/dev-host/clickhouse/init/01-create-otel-table.sql (100%) rename self-host/{compose => }/dev-host/clickhouse/users.xml (100%) rename self-host/{compose => }/dev-host/docker-compose.yml (83%) rename self-host/{compose => }/dev-host/grafana/dashboards/api.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/cache.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/epoxy.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/futures.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/gasoline.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/guard.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/operation.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/pegboard.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/tokio.json (100%) rename self-host/{compose => }/dev-host/grafana/dashboards/traces.json (100%) rename self-host/{compose => }/dev-host/grafana/grafana.ini (100%) rename self-host/{compose => }/dev-host/grafana/provisioning/dashboards/dashboards.yaml (100%) rename self-host/{compose => }/dev-host/grafana/provisioning/datasources/datasources.yaml (100%) rename self-host/{compose => }/dev-host/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-host/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-host/prometheus/prometheus.yml (100%) rename self-host/{compose => }/dev-host/rivet-engine/config.jsonc (79%) rename self-host/{compose => }/dev-host/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-host/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/.gitattributes (100%) rename self-host/{compose => }/dev-multidc-multinode/README.md (100%) rename self-host/{compose => }/dev-multidc-multinode/core/clickhouse/client-config.xml (100%) rename self-host/{compose => }/dev-multidc-multinode/core/clickhouse/config.xml (100%) rename self-host/{compose => }/dev-multidc-multinode/core/clickhouse/init/01-create-otel-table.sql (100%) rename self-host/{compose => }/dev-multidc-multinode/core/clickhouse/users.xml (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/api.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/cache.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/epoxy.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/futures.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/gasoline.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/guard.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/operation.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/pegboard.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/tokio.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/dashboards/traces.json (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/grafana.ini (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/provisioning/dashboards/dashboards.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/core/prometheus/prometheus.yml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-a/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-a/postgres/init-db.sh (100%) rename self-host/{compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2 => dev-multidc-multinode/datacenters/dc-a/rivet-engine/0}/config.jsonc (88%) rename self-host/{compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0 => dev-multidc-multinode/datacenters/dc-a/rivet-engine/1}/config.jsonc (88%) rename self-host/{compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1 => dev-multidc-multinode/datacenters/dc-a/rivet-engine/2}/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-a/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-a/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-b/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-b/postgres/init-db.sh (100%) rename self-host/{compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1 => dev-multidc-multinode/datacenters/dc-b/rivet-engine/0}/config.jsonc (88%) rename self-host/{compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0 => dev-multidc-multinode/datacenters/dc-b/rivet-engine/1}/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-b/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-b/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc (88%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/datacenters/dc-c/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc-multinode/docker-compose.yml (84%) rename self-host/{compose => }/dev-multidc/.gitattributes (100%) rename self-host/{compose => }/dev-multidc/README.md (100%) rename self-host/{compose => }/dev-multidc/core/clickhouse/client-config.xml (100%) rename self-host/{compose => }/dev-multidc/core/clickhouse/config.xml (100%) rename self-host/{compose => }/dev-multidc/core/clickhouse/init/01-create-otel-table.sql (100%) rename self-host/{compose => }/dev-multidc/core/clickhouse/users.xml (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/api.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/cache.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/epoxy.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/futures.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/gasoline.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/guard.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/operation.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/pegboard.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/tokio.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/dashboards/traces.json (100%) rename self-host/{compose => }/dev-multidc/core/grafana/grafana.ini (100%) rename self-host/{compose => }/dev-multidc/core/grafana/provisioning/dashboards/dashboards.yaml (100%) rename self-host/{compose => }/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml (100%) rename self-host/{compose => }/dev-multidc/core/prometheus/prometheus.yml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-a/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-a/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc (88%) rename self-host/{compose => }/dev-multidc/datacenters/dc-a/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-a/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-b/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-b/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc (88%) rename self-host/{compose => }/dev-multidc/datacenters/dc-b/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-b/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-c/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-c/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc (88%) rename self-host/{compose => }/dev-multidc/datacenters/dc-c/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/datacenters/dc-c/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev-multidc/docker-compose.yml (87%) rename self-host/{compose => }/dev-multinode/.gitattributes (100%) rename self-host/{compose => }/dev-multinode/README.md (100%) rename self-host/{compose => }/dev-multinode/clickhouse/client-config.xml (100%) rename self-host/{compose => }/dev-multinode/clickhouse/config.xml (100%) rename self-host/{compose => }/dev-multinode/clickhouse/init/01-create-otel-table.sql (100%) rename self-host/{compose => }/dev-multinode/clickhouse/users.xml (100%) rename self-host/{compose => }/dev-multinode/docker-compose.yml (82%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/api.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/cache.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/epoxy.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/futures.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/gasoline.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/guard.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/operation.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/pegboard.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/tokio.json (100%) rename self-host/{compose => }/dev-multinode/grafana/dashboards/traces.json (100%) rename self-host/{compose => }/dev-multinode/grafana/grafana.ini (100%) rename self-host/{compose => }/dev-multinode/grafana/provisioning/dashboards/dashboards.yaml (100%) rename self-host/{compose => }/dev-multinode/grafana/provisioning/datasources/datasources.yaml (100%) rename self-host/{compose => }/dev-multinode/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev-multinode/postgres/init-db.sh (100%) rename self-host/{compose => }/dev-multinode/prometheus/prometheus.yml (100%) rename self-host/{compose/dev-multinode/rivet-engine/2 => dev-multinode/rivet-engine/0}/config.jsonc (79%) rename self-host/{compose/dev-multinode/rivet-engine/0 => dev-multinode/rivet-engine/1}/config.jsonc (79%) rename self-host/{compose/dev-multinode/rivet-engine/1 => dev-multinode/rivet-engine/2}/config.jsonc (79%) rename self-host/{compose => }/dev-multinode/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev-multinode/vector-server/vector.yaml (100%) rename self-host/{compose => }/dev/.gitattributes (100%) rename self-host/{compose => }/dev/README.md (100%) rename self-host/{compose => }/dev/clickhouse/client-config.xml (100%) rename self-host/{compose => }/dev/clickhouse/config.xml (100%) rename self-host/{compose => }/dev/clickhouse/init/01-create-otel-table.sql (100%) rename self-host/{compose => }/dev/clickhouse/users.xml (100%) rename self-host/{compose => }/dev/docker-compose.yml (84%) rename self-host/{compose => }/dev/grafana/dashboards/api.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/cache.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/epoxy.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/futures.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/gasoline.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/guard.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/operation.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/pegboard.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/tokio.json (100%) rename self-host/{compose => }/dev/grafana/dashboards/traces.json (100%) rename self-host/{compose => }/dev/grafana/grafana.ini (100%) rename self-host/{compose => }/dev/grafana/provisioning/dashboards/dashboards.yaml (100%) rename self-host/{compose => }/dev/grafana/provisioning/datasources/datasources.yaml (100%) rename self-host/{compose => }/dev/otel-collector/config.yaml (100%) rename self-host/{compose => }/dev/postgres/init-db.sh (100%) rename self-host/{compose => }/dev/prometheus/prometheus.yml (100%) rename self-host/{compose => }/dev/rivet-engine/config.jsonc (79%) rename self-host/{compose => }/dev/vector-client/vector.yaml (100%) rename self-host/{compose => }/dev/vector-server/vector.yaml (100%) diff --git a/engine/packages/metrics/src/registry.rs b/engine/packages/metrics/src/registry.rs index 880cc495ae..c6bd268591 100644 --- a/engine/packages/metrics/src/registry.rs +++ b/engine/packages/metrics/src/registry.rs @@ -1,5 +1,7 @@ use prometheus::*; lazy_static::lazy_static! { - pub static ref REGISTRY: Registry = Registry::new_custom(None, Some(labels! { })).unwrap(); + pub static ref REGISTRY: Registry = Registry::new_custom( + Some("rivet".to_string()), + Some(labels! { })).unwrap(); } diff --git a/examples/kitchen-sink/Dockerfile b/examples/kitchen-sink/Dockerfile index e1a6f2a3d8..21377a6c9d 100644 --- a/examples/kitchen-sink/Dockerfile +++ b/examples/kitchen-sink/Dockerfile @@ -1,4 +1,9 @@ -FROM node:22-slim +# Base image is overridable so local builds can match the host glibc. The cloud +# build keeps the node:22-slim default and supplies a glibc-compatible napi +# binary; a modern dev host (newer glibc) should override this with a newer base +# such as node:22-trixie-slim so the host-built napi binary loads. +ARG NODE_IMAGE=node:22-slim +FROM ${NODE_IMAGE} RUN corepack enable && corepack prepare pnpm@10.13.1 --activate WORKDIR /app ENV NODE_OPTIONS=--max-old-space-size=7168 diff --git a/self-host/compose/prod-file-system/.gitattributes b/self-host/compose/prod-file-system/.gitattributes deleted file mode 100644 index 447edeb5c2..0000000000 --- a/self-host/compose/prod-file-system/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -. linguist-generated=true diff --git a/self-host/compose/prod-file-system/README.md b/self-host/compose/prod-file-system/README.md deleted file mode 100644 index 4b02525eeb..0000000000 --- a/self-host/compose/prod-file-system/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# dev - Auto-generated Docker Compose Template - -> ! **Auto-generated**: This directory and its contents are automatically generated by `docker/template/`. Do not edit these files directly as your changes will be overwritten. - -## Overview - -This Docker Compose configuration provides a complete development environment for Rivet with the following services: - -- **Rivet Engine**: Main orchestration service -- **Rivet Shell**: Interactive shell for debugging -- **Runner**: Executes user code -- **ClickHouse**: Analytics and time-series database -- **NATS**: Message broker -- **PostgreSQL**: Relational database -- **Vector Server**: Log aggregation and processing -- **OpenTelemetry Collector**: Observability data collection - -## Port Configuration - -| Service | Port(s) | Description | -|---------|---------|-------------| -| Rivet Engine | 6420 | Public endpoint | -| Runner | 5050 | Code execution service | -| NATS | 4222 | Message broker | -| PostgreSQL | 5432 | Database | -| ClickHouse HTTP | 9300 | Database HTTP interface | -| ClickHouse Native | 9301 | Database native protocol | -| OpenTelemetry gRPC | 4317 | OTLP gRPC endpoint | -| OpenTelemetry HTTP | 4318 | OTLP HTTP endpoint | - -## Template Configuration - -**Template Name**: `dev` -**Base Port**: `6420` -**Network Mode**: `bridge` - -### Datacenters -- **1**: 1 engine(s), 1 runner(s) - -## Usage - -1. Start all services: - ```bash - docker-compose up -d - ``` - -2. Check service health: - ```bash - docker-compose ps - ``` - -3. View logs: - ```bash - docker-compose logs -f [service-name] - ``` - -4. Stop all services: - ```bash - docker-compose down - ``` - -## Generated Files - -This template generates the following files and directories: -- `docker-compose.yml` - Main Docker Compose configuration -- `core/` - Core services shared across datacenters: - - `clickhouse/` - ClickHouse configuration and initialization - - `vector-server/` - Vector aggregator configuration - - `otel-collector-server/` - OpenTelemetry Collector server configuration -- `datacenters/` - Datacenter-specific configurations: - - `1/` - Configuration for datacenter 1: - - `postgres/` - PostgreSQL setup scripts - - `rivet-engine/` - Rivet Engine configuration - - `vector-client/` - Vector client configuration - - `otel-collector-client/` - OpenTelemetry Collector client configuration -- `README.md` - This file diff --git a/self-host/compose/prod-file-system/docker-compose.yml b/self-host/compose/prod-file-system/docker-compose.yml deleted file mode 100644 index 2e4a315c33..0000000000 --- a/self-host/compose/prod-file-system/docker-compose.yml +++ /dev/null @@ -1,41 +0,0 @@ -services: - rivet-engine: - build: - context: ../../.. - dockerfile: docker/engine/Dockerfile - target: engine-full - restart: unless-stopped - command: /usr/bin/rivet-engine start - environment: - - RIVET__FILE_SYSTEM__PATH=/var/lib/rivet-engine - volumes: - - ./rivet-engine/config.jsonc:/etc/rivet/config.jsonc:ro - - rivet-engine-data:/var/lib/rivet-engine - ports: - - '6420:6420' - healthcheck: - test: - - CMD - - curl - - '-f' - - http://127.0.0.1:6421/health - interval: 2s - timeout: 10s - retries: 10 - start_period: 30s - runner: - build: - context: ../.. - dockerfile: docker/runner/Dockerfile - platform: linux/amd64 - restart: unless-stopped - environment: - - RIVET_ENDPOINT=http://rivet-engine:6420 - stop_grace_period: 4s - ports: - - '5050:5050' - depends_on: - rivet-engine: - condition: service_healthy -volumes: - rivet-engine-data: diff --git a/self-host/compose/prod-file-system/rivet-engine/config.jsonc b/self-host/compose/prod-file-system/rivet-engine/config.jsonc deleted file mode 100644 index 2c63c08510..0000000000 --- a/self-host/compose/prod-file-system/rivet-engine/config.jsonc +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/self-host/compose/template/src/docker-compose.ts b/self-host/compose/template/src/docker-compose.ts index 6db4a8ab9a..4e3781aab2 100644 --- a/self-host/compose/template/src/docker-compose.ts +++ b/self-host/compose/template/src/docker-compose.ts @@ -1,6 +1,8 @@ import * as yaml from "js-yaml"; import { CORE_NETWORK_NAME, type TemplateContext } from "./context"; +const RUNNER_CONFIG_INIT_SERVICE = "runner-config-init"; + export function generateDockerCompose(context: TemplateContext) { const config = context.config; @@ -172,6 +174,10 @@ export function generateDockerCompose(context: TemplateContext) { services[postgresServiceName] = { restart: "unless-stopped", image: "postgres:18-alpine", + // Each engine opens a UDB connection pool (up to 64 connections) plus a + // dedicated LISTEN connection and pubsub, so a multi-engine datacenter + // needs far more than the default max_connections of 100. + command: ["postgres", "-c", "max_connections=500"], environment: [ "POSTGRES_USER=postgres", "POSTGRES_PASSWORD=postgres", @@ -179,7 +185,7 @@ export function generateDockerCompose(context: TemplateContext) { ], volumes: [ `./${context.getDatacenterServicePath("postgres", datacenter.name)}/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh`, - `${postgresVolumeName}:/var/lib/postgresql/data`, + `${postgresVolumeName}:/var/lib/postgresql`, ], ports: isPrimary ? [`5432:5432`] : undefined, healthcheck: { @@ -194,7 +200,7 @@ export function generateDockerCompose(context: TemplateContext) { services[shellServiceName] = { build: { - context: "../../..", + context: "../..", dockerfile: "docker/engine/Dockerfile", target: "engine-full", args: { @@ -275,7 +281,7 @@ export function generateDockerCompose(context: TemplateContext) { services[serviceName] = { build: { - context: "../../..", + context: "../..", dockerfile: "docker/engine/Dockerfile", target: "engine-full", args: { @@ -335,30 +341,79 @@ export function generateDockerCompose(context: TemplateContext) { services[serviceName] = { build: { - context: "../../..", - dockerfile: "engine/sdks/rust/test-envoy/Dockerfile", + context: "../..", + dockerfile: "examples/kitchen-sink/Dockerfile", + // The runner copies the host-built napi binary, so the base + // image must have a glibc at least as new as the build host. + args: { + NODE_IMAGE: "node:22-trixie-slim", + }, }, platform: "linux/amd64", restart: "unless-stopped", environment: [ + `RIVET_KITCHEN_SINK_MODE=serverful`, `RIVET_ENDPOINT=http://${context.getServiceHost("rivet-engine", datacenter.name, 0)}:6420`, - `INTERNAL_SERVER_PORT=5050`, - `RIVET_POOL_NAME=test-envoy`, - `AUTOSTART_ENVOY=1`, - `AUTOCONFIGURE_SERVERLESS=0` + `RIVET_TOKEN=dev`, + `RIVET_NAMESPACE=default`, + `RIVET_POOL=default`, + `PORT=8080`, ], stop_grace_period: "4s", - ports: isPrimary && i === 0 ? [`5050:5050`] : undefined, + ports: isPrimary && i === 0 ? [`5050:8080`] : undefined, depends_on: { [engineServiceName]: { condition: "service_healthy", }, + [RUNNER_CONFIG_INIT_SERVICE]: { + condition: "service_completed_successfully", + }, }, networks: [dcNetworkName], }; } }); + // Serverful runners are rejected with `no_runner_config` until a runner + // config exists for their pool, so a one-shot init container upserts a + // `normal` runner config for the `default` pool across every datacenter once + // the leader engine is healthy. The runners wait for this to finish. + const primaryDc = config.datacenters[0]; + const primaryEngineHost = context.getServiceHost( + "rivet-engine", + primaryDc.name, + 0, + ); + const primaryEngineService = context.getServiceName( + "rivet-engine", + primaryDc.name, + 0, + ); + const runnerConfigBody = JSON.stringify({ + datacenters: Object.fromEntries( + config.datacenters.map((dc) => [dc.name, { normal: {} }]), + ), + }); + const runnerConfigScript = [ + `until curl -fsS -X PUT`, + `"http://${primaryEngineHost}:6420/runner-configs/default?namespace=default"`, + `-H "Authorization: Bearer dev"`, + `-H "Content-Type: application/json"`, + `-d '${runnerConfigBody}';`, + `do echo "waiting for engine to accept runner config"; sleep 2; done;`, + `echo "runner config upserted"`, + ].join(" "); + services[RUNNER_CONFIG_INIT_SERVICE] = { + image: "curlimages/curl:latest", + restart: "no", + depends_on: { + [primaryEngineService]: { condition: "service_healthy" }, + }, + entrypoint: ["sh", "-c"], + command: [runnerConfigScript], + networks: [context.getDatacenterNetworkName(primaryDc.name)], + }; + const dockerComposeConfig = { services, networks, diff --git a/self-host/compose/template/src/services/edge/rivet-engine.ts b/self-host/compose/template/src/services/edge/rivet-engine.ts index 25df2ce5ca..52d33dfc1b 100644 --- a/self-host/compose/template/src/services/edge/rivet-engine.ts +++ b/self-host/compose/template/src/services/edge/rivet-engine.ts @@ -35,32 +35,24 @@ export function generateDatacenterRivetEngine( datacenters, }; - // Config structure matching Rust schema in packages/common/config/src/config/mod.rs + // Config structure matching Rust schema in engine/packages/config/src/config/mod.rs. + // Values that match the engine's defaults are omitted. const config = { auth: { admin_token: "dev", }, - guard: { - port: GUARD_PORT, - // https is optional and not configured for local development - }, api_peer: { host: "0.0.0.0", - port: API_PEER_PORT, }, topology, postgres: { url: `postgresql://postgres:postgres@${context.getServiceHost("postgres", datacenter.name)}:5432/rivet_engine`, }, - cache: { - driver: "in_memory", - }, clickhouse: { - http_url: `http://${clickhouseHost}:9300`, // TODO: - native_url: `http://${clickhouseHost}:9301`, // TODO: + http_url: `http://${clickhouseHost}:9300`, + native_url: `http://${clickhouseHost}:9301`, username: "system", password: "default", - secure: false, }, }; diff --git a/self-host/compose/template/src/services/edge/runner.ts b/self-host/compose/template/src/services/edge/runner.ts index c04f936dd3..9a111e637c 100644 --- a/self-host/compose/template/src/services/edge/runner.ts +++ b/self-host/compose/template/src/services/edge/runner.ts @@ -1,6 +1,7 @@ import type { TemplateContext } from "../../context"; export function generateRunner(context: TemplateContext) { - // The test runner service now uses the Rust test-envoy binary. - // The docker-compose template points at the Rust Dockerfile directly. + // The runner service runs the kitchen-sink example in serverful mode, + // connecting to the engine as a long-lived runner. The docker-compose + // template builds examples/kitchen-sink/Dockerfile directly. } diff --git a/self-host/compose/dev-host/.gitattributes b/self-host/dev-host/.gitattributes similarity index 100% rename from self-host/compose/dev-host/.gitattributes rename to self-host/dev-host/.gitattributes diff --git a/self-host/compose/dev-host/README.md b/self-host/dev-host/README.md similarity index 100% rename from self-host/compose/dev-host/README.md rename to self-host/dev-host/README.md diff --git a/self-host/compose/dev-host/clickhouse/client-config.xml b/self-host/dev-host/clickhouse/client-config.xml similarity index 100% rename from self-host/compose/dev-host/clickhouse/client-config.xml rename to self-host/dev-host/clickhouse/client-config.xml diff --git a/self-host/compose/dev-host/clickhouse/config.xml b/self-host/dev-host/clickhouse/config.xml similarity index 100% rename from self-host/compose/dev-host/clickhouse/config.xml rename to self-host/dev-host/clickhouse/config.xml diff --git a/self-host/compose/dev-host/clickhouse/init/01-create-otel-table.sql b/self-host/dev-host/clickhouse/init/01-create-otel-table.sql similarity index 100% rename from self-host/compose/dev-host/clickhouse/init/01-create-otel-table.sql rename to self-host/dev-host/clickhouse/init/01-create-otel-table.sql diff --git a/self-host/compose/dev-host/clickhouse/users.xml b/self-host/dev-host/clickhouse/users.xml similarity index 100% rename from self-host/compose/dev-host/clickhouse/users.xml rename to self-host/dev-host/clickhouse/users.xml diff --git a/self-host/compose/dev-host/docker-compose.yml b/self-host/dev-host/docker-compose.yml similarity index 83% rename from self-host/compose/dev-host/docker-compose.yml rename to self-host/dev-host/docker-compose.yml index 96db5a57d0..94619e9324 100644 --- a/self-host/compose/dev-host/docker-compose.yml +++ b/self-host/dev-host/docker-compose.yml @@ -73,13 +73,17 @@ services: postgres: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres volumes: - ./postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql healthcheck: test: - CMD-SHELL @@ -90,7 +94,7 @@ services: network_mode: host rivet-shell: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -144,7 +148,7 @@ services: network_mode: host rivet-engine: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -179,20 +183,42 @@ services: network_mode: host runner: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://127.0.0.1:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine: condition: service_healthy + runner-config-init: + condition: service_completed_successfully + network_mode: host + runner-config-init: + image: curlimages/curl:latest + restart: 'no' + depends_on: + rivet-engine: + condition: service_healthy + entrypoint: + - sh + - '-c' + command: + - >- + until curl -fsS -X PUT + "http://127.0.0.1:6420/runner-configs/default?namespace=default" -H + "Authorization: Bearer dev" -H "Content-Type: application/json" -d + '{"datacenters":{"default":{"normal":{}}}}'; do echo "waiting for engine + to accept runner config"; sleep 2; done; echo "runner config upserted" network_mode: host networks: rivet-core-network: diff --git a/self-host/compose/dev-host/grafana/dashboards/api.json b/self-host/dev-host/grafana/dashboards/api.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/api.json rename to self-host/dev-host/grafana/dashboards/api.json diff --git a/self-host/compose/dev-host/grafana/dashboards/cache.json b/self-host/dev-host/grafana/dashboards/cache.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/cache.json rename to self-host/dev-host/grafana/dashboards/cache.json diff --git a/self-host/compose/dev-host/grafana/dashboards/epoxy.json b/self-host/dev-host/grafana/dashboards/epoxy.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/epoxy.json rename to self-host/dev-host/grafana/dashboards/epoxy.json diff --git a/self-host/compose/dev-host/grafana/dashboards/futures.json b/self-host/dev-host/grafana/dashboards/futures.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/futures.json rename to self-host/dev-host/grafana/dashboards/futures.json diff --git a/self-host/compose/dev-host/grafana/dashboards/gasoline.json b/self-host/dev-host/grafana/dashboards/gasoline.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/gasoline.json rename to self-host/dev-host/grafana/dashboards/gasoline.json diff --git a/self-host/compose/dev-host/grafana/dashboards/guard.json b/self-host/dev-host/grafana/dashboards/guard.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/guard.json rename to self-host/dev-host/grafana/dashboards/guard.json diff --git a/self-host/compose/dev-host/grafana/dashboards/operation.json b/self-host/dev-host/grafana/dashboards/operation.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/operation.json rename to self-host/dev-host/grafana/dashboards/operation.json diff --git a/self-host/compose/dev-host/grafana/dashboards/pegboard.json b/self-host/dev-host/grafana/dashboards/pegboard.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/pegboard.json rename to self-host/dev-host/grafana/dashboards/pegboard.json diff --git a/self-host/compose/dev-host/grafana/dashboards/tokio.json b/self-host/dev-host/grafana/dashboards/tokio.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/tokio.json rename to self-host/dev-host/grafana/dashboards/tokio.json diff --git a/self-host/compose/dev-host/grafana/dashboards/traces.json b/self-host/dev-host/grafana/dashboards/traces.json similarity index 100% rename from self-host/compose/dev-host/grafana/dashboards/traces.json rename to self-host/dev-host/grafana/dashboards/traces.json diff --git a/self-host/compose/dev-host/grafana/grafana.ini b/self-host/dev-host/grafana/grafana.ini similarity index 100% rename from self-host/compose/dev-host/grafana/grafana.ini rename to self-host/dev-host/grafana/grafana.ini diff --git a/self-host/compose/dev-host/grafana/provisioning/dashboards/dashboards.yaml b/self-host/dev-host/grafana/provisioning/dashboards/dashboards.yaml similarity index 100% rename from self-host/compose/dev-host/grafana/provisioning/dashboards/dashboards.yaml rename to self-host/dev-host/grafana/provisioning/dashboards/dashboards.yaml diff --git a/self-host/compose/dev-host/grafana/provisioning/datasources/datasources.yaml b/self-host/dev-host/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from self-host/compose/dev-host/grafana/provisioning/datasources/datasources.yaml rename to self-host/dev-host/grafana/provisioning/datasources/datasources.yaml diff --git a/self-host/compose/dev-host/otel-collector/config.yaml b/self-host/dev-host/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-host/otel-collector/config.yaml rename to self-host/dev-host/otel-collector/config.yaml diff --git a/self-host/compose/dev-host/postgres/init-db.sh b/self-host/dev-host/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-host/postgres/init-db.sh rename to self-host/dev-host/postgres/init-db.sh diff --git a/self-host/compose/dev-host/prometheus/prometheus.yml b/self-host/dev-host/prometheus/prometheus.yml similarity index 100% rename from self-host/compose/dev-host/prometheus/prometheus.yml rename to self-host/dev-host/prometheus/prometheus.yml diff --git a/self-host/compose/dev-host/rivet-engine/config.jsonc b/self-host/dev-host/rivet-engine/config.jsonc similarity index 79% rename from self-host/compose/dev-host/rivet-engine/config.jsonc rename to self-host/dev-host/rivet-engine/config.jsonc index d5a3095269..ee79af406a 100644 --- a/self-host/compose/dev-host/rivet-engine/config.jsonc +++ b/self-host/dev-host/rivet-engine/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -28,14 +24,10 @@ "postgres": { "url": "postgresql://postgres:postgres@127.0.0.1:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://127.0.0.1:9300", "native_url": "http://127.0.0.1:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-host/vector-client/vector.yaml b/self-host/dev-host/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-host/vector-client/vector.yaml rename to self-host/dev-host/vector-client/vector.yaml diff --git a/self-host/compose/dev-host/vector-server/vector.yaml b/self-host/dev-host/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-host/vector-server/vector.yaml rename to self-host/dev-host/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/.gitattributes b/self-host/dev-multidc-multinode/.gitattributes similarity index 100% rename from self-host/compose/dev-multidc-multinode/.gitattributes rename to self-host/dev-multidc-multinode/.gitattributes diff --git a/self-host/compose/dev-multidc-multinode/README.md b/self-host/dev-multidc-multinode/README.md similarity index 100% rename from self-host/compose/dev-multidc-multinode/README.md rename to self-host/dev-multidc-multinode/README.md diff --git a/self-host/compose/dev-multidc-multinode/core/clickhouse/client-config.xml b/self-host/dev-multidc-multinode/core/clickhouse/client-config.xml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/clickhouse/client-config.xml rename to self-host/dev-multidc-multinode/core/clickhouse/client-config.xml diff --git a/self-host/compose/dev-multidc-multinode/core/clickhouse/config.xml b/self-host/dev-multidc-multinode/core/clickhouse/config.xml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/clickhouse/config.xml rename to self-host/dev-multidc-multinode/core/clickhouse/config.xml diff --git a/self-host/compose/dev-multidc-multinode/core/clickhouse/init/01-create-otel-table.sql b/self-host/dev-multidc-multinode/core/clickhouse/init/01-create-otel-table.sql similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/clickhouse/init/01-create-otel-table.sql rename to self-host/dev-multidc-multinode/core/clickhouse/init/01-create-otel-table.sql diff --git a/self-host/compose/dev-multidc-multinode/core/clickhouse/users.xml b/self-host/dev-multidc-multinode/core/clickhouse/users.xml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/clickhouse/users.xml rename to self-host/dev-multidc-multinode/core/clickhouse/users.xml diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/api.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/api.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/api.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/api.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/cache.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/cache.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/cache.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/cache.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/epoxy.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/epoxy.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/epoxy.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/epoxy.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/futures.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/futures.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/futures.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/futures.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/gasoline.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/gasoline.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/gasoline.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/gasoline.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/guard.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/guard.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/guard.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/guard.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/operation.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/operation.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/operation.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/operation.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/pegboard.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/pegboard.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/pegboard.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/pegboard.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/tokio.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/tokio.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/tokio.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/tokio.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/dashboards/traces.json b/self-host/dev-multidc-multinode/core/grafana/dashboards/traces.json similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/dashboards/traces.json rename to self-host/dev-multidc-multinode/core/grafana/dashboards/traces.json diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/grafana.ini b/self-host/dev-multidc-multinode/core/grafana/grafana.ini similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/grafana.ini rename to self-host/dev-multidc-multinode/core/grafana/grafana.ini diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/provisioning/dashboards/dashboards.yaml b/self-host/dev-multidc-multinode/core/grafana/provisioning/dashboards/dashboards.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/provisioning/dashboards/dashboards.yaml rename to self-host/dev-multidc-multinode/core/grafana/provisioning/dashboards/dashboards.yaml diff --git a/self-host/compose/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml b/self-host/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml rename to self-host/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml diff --git a/self-host/compose/dev-multidc-multinode/core/prometheus/prometheus.yml b/self-host/dev-multidc-multinode/core/prometheus/prometheus.yml similarity index 100% rename from self-host/compose/dev-multidc-multinode/core/prometheus/prometheus.yml rename to self-host/dev-multidc-multinode/core/prometheus/prometheus.yml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/otel-collector/config.yaml b/self-host/dev-multidc-multinode/datacenters/dc-a/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/otel-collector/config.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-a/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/postgres/init-db.sh b/self-host/dev-multidc-multinode/datacenters/dc-a/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/postgres/init-db.sh rename to self-host/dev-multidc-multinode/datacenters/dc-a/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc index ab9cfa618a..0532bce5c3 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-a:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc index ab9cfa618a..0532bce5c3 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-a:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc index ab9cfa618a..0532bce5c3 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-a:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/vector-client/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-a/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/vector-client/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-a/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-a/vector-server/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-a/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-a/vector-server/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-a/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/otel-collector/config.yaml b/self-host/dev-multidc-multinode/datacenters/dc-b/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/otel-collector/config.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-b/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/postgres/init-db.sh b/self-host/dev-multidc-multinode/datacenters/dc-b/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/postgres/init-db.sh rename to self-host/dev-multidc-multinode/datacenters/dc-b/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc index 2a34bc83ff..e75a22f77c 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 2, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-b:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc index 2a34bc83ff..e75a22f77c 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 2, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-b:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc index 2a34bc83ff..e75a22f77c 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 2, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-b:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/vector-client/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-b/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/vector-client/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-b/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-b/vector-server/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-b/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-b/vector-server/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-b/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/otel-collector/config.yaml b/self-host/dev-multidc-multinode/datacenters/dc-c/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/otel-collector/config.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-c/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/postgres/init-db.sh b/self-host/dev-multidc-multinode/datacenters/dc-c/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/postgres/init-db.sh rename to self-host/dev-multidc-multinode/datacenters/dc-c/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc index 970454f7f6..cb4c587a19 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 3, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-c:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc index 970454f7f6..cb4c587a19 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 3, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-c:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc rename to self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc index 970454f7f6..cb4c587a19 100644 --- a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 3, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-c:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/vector-client/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-c/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/vector-client/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-c/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/datacenters/dc-c/vector-server/vector.yaml b/self-host/dev-multidc-multinode/datacenters/dc-c/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc-multinode/datacenters/dc-c/vector-server/vector.yaml rename to self-host/dev-multidc-multinode/datacenters/dc-c/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc-multinode/docker-compose.yml b/self-host/dev-multidc-multinode/docker-compose.yml similarity index 84% rename from self-host/compose/dev-multidc-multinode/docker-compose.yml rename to self-host/dev-multidc-multinode/docker-compose.yml index 7e421144c8..e4ccd4fc70 100644 --- a/self-host/compose/dev-multidc-multinode/docker-compose.yml +++ b/self-host/dev-multidc-multinode/docker-compose.yml @@ -86,6 +86,10 @@ services: postgres-dc-a: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -93,7 +97,7 @@ services: volumes: - >- ./datacenters/dc-a/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-a:/var/lib/postgresql/data + - postgres-data-dc-a:/var/lib/postgresql ports: - '5432:5432' healthcheck: @@ -107,7 +111,7 @@ services: - rivet-network-dc-a rivet-shell-dc-a: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -174,7 +178,7 @@ services: - '4317:4317' rivet-engine-dc-a-0: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -217,7 +221,7 @@ services: start_period: 30s rivet-engine-dc-a-1: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -258,7 +262,7 @@ services: start_period: 30s rivet-engine-dc-a-2: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -299,63 +303,82 @@ services: start_period: 30s runner-dc-a-0: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-a-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s ports: - - '5050:5050' + - '5050:8080' depends_on: rivet-engine-dc-a-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-a runner-dc-a-1: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-a-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-a-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-a runner-dc-a-2: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-a-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-a-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-a postgres-dc-b: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -363,7 +386,7 @@ services: volumes: - >- ./datacenters/dc-b/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-b:/var/lib/postgresql/data + - postgres-data-dc-b:/var/lib/postgresql healthcheck: test: - CMD-SHELL @@ -375,7 +398,7 @@ services: - rivet-network-dc-b rivet-shell-dc-b: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -440,7 +463,7 @@ services: - rivet-network-dc-b-to-core rivet-engine-dc-b-0: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -481,7 +504,7 @@ services: start_period: 30s rivet-engine-dc-b-1: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -522,7 +545,7 @@ services: start_period: 30s rivet-engine-dc-b-2: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -563,61 +586,80 @@ services: start_period: 30s runner-dc-b-0: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-b-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-b-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-b runner-dc-b-1: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-b-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-b-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-b runner-dc-b-2: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-b-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-b-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-b postgres-dc-c: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -625,7 +667,7 @@ services: volumes: - >- ./datacenters/dc-c/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-c:/var/lib/postgresql/data + - postgres-data-dc-c:/var/lib/postgresql healthcheck: test: - CMD-SHELL @@ -637,7 +679,7 @@ services: - rivet-network-dc-c rivet-shell-dc-c: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -702,7 +744,7 @@ services: - rivet-network-dc-c-to-core rivet-engine-dc-c-0: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -743,7 +785,7 @@ services: start_period: 30s rivet-engine-dc-c-1: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -784,7 +826,7 @@ services: start_period: 30s rivet-engine-dc-c-2: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -825,58 +867,92 @@ services: start_period: 30s runner-dc-c-0: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-c-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-c-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-c runner-dc-c-1: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-c-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-c-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-c runner-dc-c-2: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-c-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-c-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-c + runner-config-init: + image: curlimages/curl:latest + restart: 'no' + depends_on: + rivet-engine-dc-a-0: + condition: service_healthy + entrypoint: + - sh + - '-c' + command: + - >- + until curl -fsS -X PUT + "http://rivet-engine-dc-a-0:6420/runner-configs/default?namespace=default" + -H "Authorization: Bearer dev" -H "Content-Type: application/json" -d + '{"datacenters":{"dc-a":{"normal":{}},"dc-b":{"normal":{}},"dc-c":{"normal":{}}}}'; + do echo "waiting for engine to accept runner config"; sleep 2; done; + echo "runner config upserted" + networks: + - rivet-network-dc-a networks: rivet-core-network: driver: bridge diff --git a/self-host/compose/dev-multidc/.gitattributes b/self-host/dev-multidc/.gitattributes similarity index 100% rename from self-host/compose/dev-multidc/.gitattributes rename to self-host/dev-multidc/.gitattributes diff --git a/self-host/compose/dev-multidc/README.md b/self-host/dev-multidc/README.md similarity index 100% rename from self-host/compose/dev-multidc/README.md rename to self-host/dev-multidc/README.md diff --git a/self-host/compose/dev-multidc/core/clickhouse/client-config.xml b/self-host/dev-multidc/core/clickhouse/client-config.xml similarity index 100% rename from self-host/compose/dev-multidc/core/clickhouse/client-config.xml rename to self-host/dev-multidc/core/clickhouse/client-config.xml diff --git a/self-host/compose/dev-multidc/core/clickhouse/config.xml b/self-host/dev-multidc/core/clickhouse/config.xml similarity index 100% rename from self-host/compose/dev-multidc/core/clickhouse/config.xml rename to self-host/dev-multidc/core/clickhouse/config.xml diff --git a/self-host/compose/dev-multidc/core/clickhouse/init/01-create-otel-table.sql b/self-host/dev-multidc/core/clickhouse/init/01-create-otel-table.sql similarity index 100% rename from self-host/compose/dev-multidc/core/clickhouse/init/01-create-otel-table.sql rename to self-host/dev-multidc/core/clickhouse/init/01-create-otel-table.sql diff --git a/self-host/compose/dev-multidc/core/clickhouse/users.xml b/self-host/dev-multidc/core/clickhouse/users.xml similarity index 100% rename from self-host/compose/dev-multidc/core/clickhouse/users.xml rename to self-host/dev-multidc/core/clickhouse/users.xml diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/api.json b/self-host/dev-multidc/core/grafana/dashboards/api.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/api.json rename to self-host/dev-multidc/core/grafana/dashboards/api.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/cache.json b/self-host/dev-multidc/core/grafana/dashboards/cache.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/cache.json rename to self-host/dev-multidc/core/grafana/dashboards/cache.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/epoxy.json b/self-host/dev-multidc/core/grafana/dashboards/epoxy.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/epoxy.json rename to self-host/dev-multidc/core/grafana/dashboards/epoxy.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/futures.json b/self-host/dev-multidc/core/grafana/dashboards/futures.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/futures.json rename to self-host/dev-multidc/core/grafana/dashboards/futures.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/gasoline.json b/self-host/dev-multidc/core/grafana/dashboards/gasoline.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/gasoline.json rename to self-host/dev-multidc/core/grafana/dashboards/gasoline.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/guard.json b/self-host/dev-multidc/core/grafana/dashboards/guard.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/guard.json rename to self-host/dev-multidc/core/grafana/dashboards/guard.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/operation.json b/self-host/dev-multidc/core/grafana/dashboards/operation.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/operation.json rename to self-host/dev-multidc/core/grafana/dashboards/operation.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/pegboard.json b/self-host/dev-multidc/core/grafana/dashboards/pegboard.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/pegboard.json rename to self-host/dev-multidc/core/grafana/dashboards/pegboard.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/tokio.json b/self-host/dev-multidc/core/grafana/dashboards/tokio.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/tokio.json rename to self-host/dev-multidc/core/grafana/dashboards/tokio.json diff --git a/self-host/compose/dev-multidc/core/grafana/dashboards/traces.json b/self-host/dev-multidc/core/grafana/dashboards/traces.json similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/dashboards/traces.json rename to self-host/dev-multidc/core/grafana/dashboards/traces.json diff --git a/self-host/compose/dev-multidc/core/grafana/grafana.ini b/self-host/dev-multidc/core/grafana/grafana.ini similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/grafana.ini rename to self-host/dev-multidc/core/grafana/grafana.ini diff --git a/self-host/compose/dev-multidc/core/grafana/provisioning/dashboards/dashboards.yaml b/self-host/dev-multidc/core/grafana/provisioning/dashboards/dashboards.yaml similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/provisioning/dashboards/dashboards.yaml rename to self-host/dev-multidc/core/grafana/provisioning/dashboards/dashboards.yaml diff --git a/self-host/compose/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml b/self-host/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from self-host/compose/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml rename to self-host/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml diff --git a/self-host/compose/dev-multidc/core/prometheus/prometheus.yml b/self-host/dev-multidc/core/prometheus/prometheus.yml similarity index 100% rename from self-host/compose/dev-multidc/core/prometheus/prometheus.yml rename to self-host/dev-multidc/core/prometheus/prometheus.yml diff --git a/self-host/compose/dev-multidc/datacenters/dc-a/otel-collector/config.yaml b/self-host/dev-multidc/datacenters/dc-a/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-a/otel-collector/config.yaml rename to self-host/dev-multidc/datacenters/dc-a/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-a/postgres/init-db.sh b/self-host/dev-multidc/datacenters/dc-a/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-a/postgres/init-db.sh rename to self-host/dev-multidc/datacenters/dc-a/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc b/self-host/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc rename to self-host/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc index f989acaae9..ac3c4bc3c6 100644 --- a/self-host/compose/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc +++ b/self-host/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-a:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc/datacenters/dc-a/vector-client/vector.yaml b/self-host/dev-multidc/datacenters/dc-a/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-a/vector-client/vector.yaml rename to self-host/dev-multidc/datacenters/dc-a/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-a/vector-server/vector.yaml b/self-host/dev-multidc/datacenters/dc-a/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-a/vector-server/vector.yaml rename to self-host/dev-multidc/datacenters/dc-a/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-b/otel-collector/config.yaml b/self-host/dev-multidc/datacenters/dc-b/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-b/otel-collector/config.yaml rename to self-host/dev-multidc/datacenters/dc-b/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-b/postgres/init-db.sh b/self-host/dev-multidc/datacenters/dc-b/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-b/postgres/init-db.sh rename to self-host/dev-multidc/datacenters/dc-b/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc b/self-host/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc rename to self-host/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc index fa082ce527..8e5c6aaa45 100644 --- a/self-host/compose/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc +++ b/self-host/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 2, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-b:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc/datacenters/dc-b/vector-client/vector.yaml b/self-host/dev-multidc/datacenters/dc-b/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-b/vector-client/vector.yaml rename to self-host/dev-multidc/datacenters/dc-b/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-b/vector-server/vector.yaml b/self-host/dev-multidc/datacenters/dc-b/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-b/vector-server/vector.yaml rename to self-host/dev-multidc/datacenters/dc-b/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-c/otel-collector/config.yaml b/self-host/dev-multidc/datacenters/dc-c/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-c/otel-collector/config.yaml rename to self-host/dev-multidc/datacenters/dc-c/otel-collector/config.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-c/postgres/init-db.sh b/self-host/dev-multidc/datacenters/dc-c/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-c/postgres/init-db.sh rename to self-host/dev-multidc/datacenters/dc-c/postgres/init-db.sh diff --git a/self-host/compose/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc b/self-host/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc similarity index 88% rename from self-host/compose/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc rename to self-host/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc index f3c3c6ae38..6ba3be6d65 100644 --- a/self-host/compose/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc +++ b/self-host/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 3, @@ -50,14 +46,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres-dc-c:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multidc/datacenters/dc-c/vector-client/vector.yaml b/self-host/dev-multidc/datacenters/dc-c/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-c/vector-client/vector.yaml rename to self-host/dev-multidc/datacenters/dc-c/vector-client/vector.yaml diff --git a/self-host/compose/dev-multidc/datacenters/dc-c/vector-server/vector.yaml b/self-host/dev-multidc/datacenters/dc-c/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multidc/datacenters/dc-c/vector-server/vector.yaml rename to self-host/dev-multidc/datacenters/dc-c/vector-server/vector.yaml diff --git a/self-host/compose/dev-multidc/docker-compose.yml b/self-host/dev-multidc/docker-compose.yml similarity index 87% rename from self-host/compose/dev-multidc/docker-compose.yml rename to self-host/dev-multidc/docker-compose.yml index 79cb619e0a..437ad5bdee 100644 --- a/self-host/compose/dev-multidc/docker-compose.yml +++ b/self-host/dev-multidc/docker-compose.yml @@ -86,6 +86,10 @@ services: postgres-dc-a: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -93,7 +97,7 @@ services: volumes: - >- ./datacenters/dc-a/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-a:/var/lib/postgresql/data + - postgres-data-dc-a:/var/lib/postgresql ports: - '5432:5432' healthcheck: @@ -107,7 +111,7 @@ services: - rivet-network-dc-a rivet-shell-dc-a: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -173,7 +177,7 @@ services: - '4317:4317' rivet-engine-dc-a: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -215,27 +219,36 @@ services: start_period: 30s runner-dc-a: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-a:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s ports: - - '5050:5050' + - '5050:8080' depends_on: rivet-engine-dc-a: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-a postgres-dc-b: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -243,7 +256,7 @@ services: volumes: - >- ./datacenters/dc-b/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-b:/var/lib/postgresql/data + - postgres-data-dc-b:/var/lib/postgresql healthcheck: test: - CMD-SHELL @@ -255,7 +268,7 @@ services: - rivet-network-dc-b rivet-shell-dc-b: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -319,7 +332,7 @@ services: - rivet-network-dc-b-to-core rivet-engine-dc-b: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -359,25 +372,34 @@ services: start_period: 30s runner-dc-b: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-b:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-b: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-b postgres-dc-c: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres @@ -385,7 +407,7 @@ services: volumes: - >- ./datacenters/dc-c/postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data-dc-c:/var/lib/postgresql/data + - postgres-data-dc-c:/var/lib/postgresql healthcheck: test: - CMD-SHELL @@ -397,7 +419,7 @@ services: - rivet-network-dc-c rivet-shell-dc-c: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -461,7 +483,7 @@ services: - rivet-network-dc-c-to-core rivet-engine-dc-c: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -501,22 +523,46 @@ services: start_period: 30s runner-dc-c: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-dc-c:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-dc-c: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network-dc-c + runner-config-init: + image: curlimages/curl:latest + restart: 'no' + depends_on: + rivet-engine-dc-a: + condition: service_healthy + entrypoint: + - sh + - '-c' + command: + - >- + until curl -fsS -X PUT + "http://rivet-engine-dc-a:6420/runner-configs/default?namespace=default" + -H "Authorization: Bearer dev" -H "Content-Type: application/json" -d + '{"datacenters":{"dc-a":{"normal":{}},"dc-b":{"normal":{}},"dc-c":{"normal":{}}}}'; + do echo "waiting for engine to accept runner config"; sleep 2; done; + echo "runner config upserted" + networks: + - rivet-network-dc-a networks: rivet-core-network: driver: bridge diff --git a/self-host/compose/dev-multinode/.gitattributes b/self-host/dev-multinode/.gitattributes similarity index 100% rename from self-host/compose/dev-multinode/.gitattributes rename to self-host/dev-multinode/.gitattributes diff --git a/self-host/compose/dev-multinode/README.md b/self-host/dev-multinode/README.md similarity index 100% rename from self-host/compose/dev-multinode/README.md rename to self-host/dev-multinode/README.md diff --git a/self-host/compose/dev-multinode/clickhouse/client-config.xml b/self-host/dev-multinode/clickhouse/client-config.xml similarity index 100% rename from self-host/compose/dev-multinode/clickhouse/client-config.xml rename to self-host/dev-multinode/clickhouse/client-config.xml diff --git a/self-host/compose/dev-multinode/clickhouse/config.xml b/self-host/dev-multinode/clickhouse/config.xml similarity index 100% rename from self-host/compose/dev-multinode/clickhouse/config.xml rename to self-host/dev-multinode/clickhouse/config.xml diff --git a/self-host/compose/dev-multinode/clickhouse/init/01-create-otel-table.sql b/self-host/dev-multinode/clickhouse/init/01-create-otel-table.sql similarity index 100% rename from self-host/compose/dev-multinode/clickhouse/init/01-create-otel-table.sql rename to self-host/dev-multinode/clickhouse/init/01-create-otel-table.sql diff --git a/self-host/compose/dev-multinode/clickhouse/users.xml b/self-host/dev-multinode/clickhouse/users.xml similarity index 100% rename from self-host/compose/dev-multinode/clickhouse/users.xml rename to self-host/dev-multinode/clickhouse/users.xml diff --git a/self-host/compose/dev-multinode/docker-compose.yml b/self-host/dev-multinode/docker-compose.yml similarity index 82% rename from self-host/compose/dev-multinode/docker-compose.yml rename to self-host/dev-multinode/docker-compose.yml index a432743057..389510f7b3 100644 --- a/self-host/compose/dev-multinode/docker-compose.yml +++ b/self-host/dev-multinode/docker-compose.yml @@ -82,13 +82,17 @@ services: postgres: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres volumes: - ./postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql ports: - '5432:5432' healthcheck: @@ -102,7 +106,7 @@ services: - rivet-network rivet-shell: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -166,7 +170,7 @@ services: - '4317:4317' rivet-engine-0: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -206,7 +210,7 @@ services: start_period: 30s rivet-engine-1: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -244,7 +248,7 @@ services: start_period: 30s rivet-engine-2: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -282,58 +286,91 @@ services: start_period: 30s runner-0: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s ports: - - '5050:5050' + - '5050:8080' depends_on: rivet-engine-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network runner-1: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully networks: - rivet-network runner-2: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine-0:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s depends_on: rivet-engine-0: condition: service_healthy + runner-config-init: + condition: service_completed_successfully + networks: + - rivet-network + runner-config-init: + image: curlimages/curl:latest + restart: 'no' + depends_on: + rivet-engine-0: + condition: service_healthy + entrypoint: + - sh + - '-c' + command: + - >- + until curl -fsS -X PUT + "http://rivet-engine-0:6420/runner-configs/default?namespace=default" -H + "Authorization: Bearer dev" -H "Content-Type: application/json" -d + '{"datacenters":{"default":{"normal":{}}}}'; do echo "waiting for engine + to accept runner config"; sleep 2; done; echo "runner config upserted" networks: - rivet-network networks: diff --git a/self-host/compose/dev-multinode/grafana/dashboards/api.json b/self-host/dev-multinode/grafana/dashboards/api.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/api.json rename to self-host/dev-multinode/grafana/dashboards/api.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/cache.json b/self-host/dev-multinode/grafana/dashboards/cache.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/cache.json rename to self-host/dev-multinode/grafana/dashboards/cache.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/epoxy.json b/self-host/dev-multinode/grafana/dashboards/epoxy.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/epoxy.json rename to self-host/dev-multinode/grafana/dashboards/epoxy.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/futures.json b/self-host/dev-multinode/grafana/dashboards/futures.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/futures.json rename to self-host/dev-multinode/grafana/dashboards/futures.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/gasoline.json b/self-host/dev-multinode/grafana/dashboards/gasoline.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/gasoline.json rename to self-host/dev-multinode/grafana/dashboards/gasoline.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/guard.json b/self-host/dev-multinode/grafana/dashboards/guard.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/guard.json rename to self-host/dev-multinode/grafana/dashboards/guard.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/operation.json b/self-host/dev-multinode/grafana/dashboards/operation.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/operation.json rename to self-host/dev-multinode/grafana/dashboards/operation.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/pegboard.json b/self-host/dev-multinode/grafana/dashboards/pegboard.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/pegboard.json rename to self-host/dev-multinode/grafana/dashboards/pegboard.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/tokio.json b/self-host/dev-multinode/grafana/dashboards/tokio.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/tokio.json rename to self-host/dev-multinode/grafana/dashboards/tokio.json diff --git a/self-host/compose/dev-multinode/grafana/dashboards/traces.json b/self-host/dev-multinode/grafana/dashboards/traces.json similarity index 100% rename from self-host/compose/dev-multinode/grafana/dashboards/traces.json rename to self-host/dev-multinode/grafana/dashboards/traces.json diff --git a/self-host/compose/dev-multinode/grafana/grafana.ini b/self-host/dev-multinode/grafana/grafana.ini similarity index 100% rename from self-host/compose/dev-multinode/grafana/grafana.ini rename to self-host/dev-multinode/grafana/grafana.ini diff --git a/self-host/compose/dev-multinode/grafana/provisioning/dashboards/dashboards.yaml b/self-host/dev-multinode/grafana/provisioning/dashboards/dashboards.yaml similarity index 100% rename from self-host/compose/dev-multinode/grafana/provisioning/dashboards/dashboards.yaml rename to self-host/dev-multinode/grafana/provisioning/dashboards/dashboards.yaml diff --git a/self-host/compose/dev-multinode/grafana/provisioning/datasources/datasources.yaml b/self-host/dev-multinode/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from self-host/compose/dev-multinode/grafana/provisioning/datasources/datasources.yaml rename to self-host/dev-multinode/grafana/provisioning/datasources/datasources.yaml diff --git a/self-host/compose/dev-multinode/otel-collector/config.yaml b/self-host/dev-multinode/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev-multinode/otel-collector/config.yaml rename to self-host/dev-multinode/otel-collector/config.yaml diff --git a/self-host/compose/dev-multinode/postgres/init-db.sh b/self-host/dev-multinode/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev-multinode/postgres/init-db.sh rename to self-host/dev-multinode/postgres/init-db.sh diff --git a/self-host/compose/dev-multinode/prometheus/prometheus.yml b/self-host/dev-multinode/prometheus/prometheus.yml similarity index 100% rename from self-host/compose/dev-multinode/prometheus/prometheus.yml rename to self-host/dev-multinode/prometheus/prometheus.yml diff --git a/self-host/compose/dev-multinode/rivet-engine/2/config.jsonc b/self-host/dev-multinode/rivet-engine/0/config.jsonc similarity index 79% rename from self-host/compose/dev-multinode/rivet-engine/2/config.jsonc rename to self-host/dev-multinode/rivet-engine/0/config.jsonc index 655fc83ed7..b25680c399 100644 --- a/self-host/compose/dev-multinode/rivet-engine/2/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/0/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -28,14 +24,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multinode/rivet-engine/0/config.jsonc b/self-host/dev-multinode/rivet-engine/1/config.jsonc similarity index 79% rename from self-host/compose/dev-multinode/rivet-engine/0/config.jsonc rename to self-host/dev-multinode/rivet-engine/1/config.jsonc index 655fc83ed7..b25680c399 100644 --- a/self-host/compose/dev-multinode/rivet-engine/0/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/1/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -28,14 +24,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multinode/rivet-engine/1/config.jsonc b/self-host/dev-multinode/rivet-engine/2/config.jsonc similarity index 79% rename from self-host/compose/dev-multinode/rivet-engine/1/config.jsonc rename to self-host/dev-multinode/rivet-engine/2/config.jsonc index 655fc83ed7..b25680c399 100644 --- a/self-host/compose/dev-multinode/rivet-engine/1/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/2/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -28,14 +24,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev-multinode/vector-client/vector.yaml b/self-host/dev-multinode/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev-multinode/vector-client/vector.yaml rename to self-host/dev-multinode/vector-client/vector.yaml diff --git a/self-host/compose/dev-multinode/vector-server/vector.yaml b/self-host/dev-multinode/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev-multinode/vector-server/vector.yaml rename to self-host/dev-multinode/vector-server/vector.yaml diff --git a/self-host/compose/dev/.gitattributes b/self-host/dev/.gitattributes similarity index 100% rename from self-host/compose/dev/.gitattributes rename to self-host/dev/.gitattributes diff --git a/self-host/compose/dev/README.md b/self-host/dev/README.md similarity index 100% rename from self-host/compose/dev/README.md rename to self-host/dev/README.md diff --git a/self-host/compose/dev/clickhouse/client-config.xml b/self-host/dev/clickhouse/client-config.xml similarity index 100% rename from self-host/compose/dev/clickhouse/client-config.xml rename to self-host/dev/clickhouse/client-config.xml diff --git a/self-host/compose/dev/clickhouse/config.xml b/self-host/dev/clickhouse/config.xml similarity index 100% rename from self-host/compose/dev/clickhouse/config.xml rename to self-host/dev/clickhouse/config.xml diff --git a/self-host/compose/dev/clickhouse/init/01-create-otel-table.sql b/self-host/dev/clickhouse/init/01-create-otel-table.sql similarity index 100% rename from self-host/compose/dev/clickhouse/init/01-create-otel-table.sql rename to self-host/dev/clickhouse/init/01-create-otel-table.sql diff --git a/self-host/compose/dev/clickhouse/users.xml b/self-host/dev/clickhouse/users.xml similarity index 100% rename from self-host/compose/dev/clickhouse/users.xml rename to self-host/dev/clickhouse/users.xml diff --git a/self-host/compose/dev/docker-compose.yml b/self-host/dev/docker-compose.yml similarity index 84% rename from self-host/compose/dev/docker-compose.yml rename to self-host/dev/docker-compose.yml index 91f82dce45..7511952a55 100644 --- a/self-host/compose/dev/docker-compose.yml +++ b/self-host/dev/docker-compose.yml @@ -82,13 +82,17 @@ services: postgres: restart: unless-stopped image: postgres:18-alpine + command: + - postgres + - '-c' + - max_connections=500 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=postgres volumes: - ./postgres/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh - - postgres-data:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql ports: - '5432:5432' healthcheck: @@ -102,7 +106,7 @@ services: - rivet-network rivet-shell: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -166,7 +170,7 @@ services: - '4317:4317' rivet-engine: build: - context: ../../.. + context: ../.. dockerfile: docker/engine/Dockerfile target: engine-full args: @@ -206,22 +210,45 @@ services: start_period: 30s runner: build: - context: ../../.. - dockerfile: engine/sdks/rust/test-envoy/Dockerfile + context: ../.. + dockerfile: examples/kitchen-sink/Dockerfile + args: + NODE_IMAGE: node:22-trixie-slim platform: linux/amd64 restart: unless-stopped environment: + - RIVET_KITCHEN_SINK_MODE=serverful - RIVET_ENDPOINT=http://rivet-engine:6420 - - INTERNAL_SERVER_PORT=5050 - - RIVET_POOL_NAME=test-envoy - - AUTOSTART_ENVOY=1 - - AUTOCONFIGURE_SERVERLESS=0 + - RIVET_TOKEN=dev + - RIVET_NAMESPACE=default + - RIVET_POOL=default + - PORT=8080 stop_grace_period: 4s ports: - - '5050:5050' + - '5050:8080' + depends_on: + rivet-engine: + condition: service_healthy + runner-config-init: + condition: service_completed_successfully + networks: + - rivet-network + runner-config-init: + image: curlimages/curl:latest + restart: 'no' depends_on: rivet-engine: condition: service_healthy + entrypoint: + - sh + - '-c' + command: + - >- + until curl -fsS -X PUT + "http://rivet-engine:6420/runner-configs/default?namespace=default" -H + "Authorization: Bearer dev" -H "Content-Type: application/json" -d + '{"datacenters":{"default":{"normal":{}}}}'; do echo "waiting for engine + to accept runner config"; sleep 2; done; echo "runner config upserted" networks: - rivet-network networks: diff --git a/self-host/compose/dev/grafana/dashboards/api.json b/self-host/dev/grafana/dashboards/api.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/api.json rename to self-host/dev/grafana/dashboards/api.json diff --git a/self-host/compose/dev/grafana/dashboards/cache.json b/self-host/dev/grafana/dashboards/cache.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/cache.json rename to self-host/dev/grafana/dashboards/cache.json diff --git a/self-host/compose/dev/grafana/dashboards/epoxy.json b/self-host/dev/grafana/dashboards/epoxy.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/epoxy.json rename to self-host/dev/grafana/dashboards/epoxy.json diff --git a/self-host/compose/dev/grafana/dashboards/futures.json b/self-host/dev/grafana/dashboards/futures.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/futures.json rename to self-host/dev/grafana/dashboards/futures.json diff --git a/self-host/compose/dev/grafana/dashboards/gasoline.json b/self-host/dev/grafana/dashboards/gasoline.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/gasoline.json rename to self-host/dev/grafana/dashboards/gasoline.json diff --git a/self-host/compose/dev/grafana/dashboards/guard.json b/self-host/dev/grafana/dashboards/guard.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/guard.json rename to self-host/dev/grafana/dashboards/guard.json diff --git a/self-host/compose/dev/grafana/dashboards/operation.json b/self-host/dev/grafana/dashboards/operation.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/operation.json rename to self-host/dev/grafana/dashboards/operation.json diff --git a/self-host/compose/dev/grafana/dashboards/pegboard.json b/self-host/dev/grafana/dashboards/pegboard.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/pegboard.json rename to self-host/dev/grafana/dashboards/pegboard.json diff --git a/self-host/compose/dev/grafana/dashboards/tokio.json b/self-host/dev/grafana/dashboards/tokio.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/tokio.json rename to self-host/dev/grafana/dashboards/tokio.json diff --git a/self-host/compose/dev/grafana/dashboards/traces.json b/self-host/dev/grafana/dashboards/traces.json similarity index 100% rename from self-host/compose/dev/grafana/dashboards/traces.json rename to self-host/dev/grafana/dashboards/traces.json diff --git a/self-host/compose/dev/grafana/grafana.ini b/self-host/dev/grafana/grafana.ini similarity index 100% rename from self-host/compose/dev/grafana/grafana.ini rename to self-host/dev/grafana/grafana.ini diff --git a/self-host/compose/dev/grafana/provisioning/dashboards/dashboards.yaml b/self-host/dev/grafana/provisioning/dashboards/dashboards.yaml similarity index 100% rename from self-host/compose/dev/grafana/provisioning/dashboards/dashboards.yaml rename to self-host/dev/grafana/provisioning/dashboards/dashboards.yaml diff --git a/self-host/compose/dev/grafana/provisioning/datasources/datasources.yaml b/self-host/dev/grafana/provisioning/datasources/datasources.yaml similarity index 100% rename from self-host/compose/dev/grafana/provisioning/datasources/datasources.yaml rename to self-host/dev/grafana/provisioning/datasources/datasources.yaml diff --git a/self-host/compose/dev/otel-collector/config.yaml b/self-host/dev/otel-collector/config.yaml similarity index 100% rename from self-host/compose/dev/otel-collector/config.yaml rename to self-host/dev/otel-collector/config.yaml diff --git a/self-host/compose/dev/postgres/init-db.sh b/self-host/dev/postgres/init-db.sh similarity index 100% rename from self-host/compose/dev/postgres/init-db.sh rename to self-host/dev/postgres/init-db.sh diff --git a/self-host/compose/dev/prometheus/prometheus.yml b/self-host/dev/prometheus/prometheus.yml similarity index 100% rename from self-host/compose/dev/prometheus/prometheus.yml rename to self-host/dev/prometheus/prometheus.yml diff --git a/self-host/compose/dev/rivet-engine/config.jsonc b/self-host/dev/rivet-engine/config.jsonc similarity index 79% rename from self-host/compose/dev/rivet-engine/config.jsonc rename to self-host/dev/rivet-engine/config.jsonc index 74135c72fa..4c6312b6d3 100644 --- a/self-host/compose/dev/rivet-engine/config.jsonc +++ b/self-host/dev/rivet-engine/config.jsonc @@ -2,12 +2,8 @@ "auth": { "admin_token": "dev" }, - "guard": { - "port": 6420 - }, "api_peer": { - "host": "0.0.0.0", - "port": 6421 + "host": "0.0.0.0" }, "topology": { "datacenter_label": 1, @@ -28,14 +24,10 @@ "postgres": { "url": "postgresql://postgres:postgres@postgres:5432/rivet_engine" }, - "cache": { - "driver": "in_memory" - }, "clickhouse": { "http_url": "http://clickhouse:9300", "native_url": "http://clickhouse:9301", "username": "system", - "password": "default", - "secure": false + "password": "default" } } \ No newline at end of file diff --git a/self-host/compose/dev/vector-client/vector.yaml b/self-host/dev/vector-client/vector.yaml similarity index 100% rename from self-host/compose/dev/vector-client/vector.yaml rename to self-host/dev/vector-client/vector.yaml diff --git a/self-host/compose/dev/vector-server/vector.yaml b/self-host/dev/vector-server/vector.yaml similarity index 100% rename from self-host/compose/dev/vector-server/vector.yaml rename to self-host/dev/vector-server/vector.yaml From 5dfeb48b3466da9a19a4c238b0606e645c5bdd69 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 16:04:12 -0700 Subject: [PATCH 13/19] [slopfix] docs(self-hosting): promote postgres from experimental to recommended OSS multi-node backend --- .../src/content/cookbook/vpc-air-gapped.mdx | 4 +- .../docs/self-hosting/configuration.mdx | 4 +- .../docs/self-hosting/foundationdb.mdx | 2 +- .../content/docs/self-hosting/postgres.mdx | 57 +++++++++++++++++++ .../self-hosting/production-checklist.mdx | 12 ++-- 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/website/src/content/cookbook/vpc-air-gapped.mdx b/website/src/content/cookbook/vpc-air-gapped.mdx index 843aba522d..db26b1e0ed 100644 --- a/website/src/content/cookbook/vpc-air-gapped.mdx +++ b/website/src/content/cookbook/vpc-air-gapped.mdx @@ -107,10 +107,10 @@ If you ship software that runs inside your customers' VPCs, the same setup turns | Backend | Use when | Status | | --- | --- | --- | | [File System](/docs/self-hosting/filesystem) (RocksDB-based) | Single-node deployments, including air-gapped installs | Production-ready, single node only | -| [PostgreSQL](/docs/self-hosting/postgres) | Multi-node deployments | Recommended for multi-node today, but experimental | +| [PostgreSQL](/docs/self-hosting/postgres) | Multi-node and multi-region deployments | Production-ready for multi-node | | FoundationDB | Largest production deployments | [Enterprise](/sales) | -For multi-node deployments, run two or more engine nodes behind a load balancer and add NATS for pub/sub, which replaces the default PostgreSQL `LISTEN`/`NOTIFY` path at high throughput. Neither is needed for a single-node file system install. See the [Production Checklist](/docs/self-hosting/production-checklist). +For multi-node deployments, run two or more engine nodes behind a load balancer, all sharing one PostgreSQL instance. The built-in PostgreSQL pub/sub is sufficient for most deployments; very high-throughput deployments can add NATS as a dedicated pub/sub layer. Neither is needed for a single-node file system install. See the [Production Checklist](/docs/self-hosting/production-checklist). ## Perimeter Checklist diff --git a/website/src/content/docs/self-hosting/configuration.mdx b/website/src/content/docs/self-hosting/configuration.mdx index 781f8bba6a..8fb19f129c 100644 --- a/website/src/content/docs/self-hosting/configuration.mdx +++ b/website/src/content/docs/self-hosting/configuration.mdx @@ -76,5 +76,5 @@ Use `samples: 1` for a uniform random pick that skips slot reads. Use `samples > ## Related - RivetKit actor runtime persistence lives in SQLite. Existing actor KV data is imported into SQLite the first time an actor wakes on the migrated runtime, then the original KV data is left frozen for downgrade safety. -- [PostgreSQL](/docs/self-hosting/postgres): Configure the PostgreSQL backend for multi-node deployments -- [File System](/docs/self-hosting/filesystem): Configure file system storage for development +- [PostgreSQL](/docs/self-hosting/postgres): Configure the PostgreSQL backend for multi-node and multi-region deployments +- [File System](/docs/self-hosting/filesystem): Configure file system storage for single-node deployments diff --git a/website/src/content/docs/self-hosting/foundationdb.mdx b/website/src/content/docs/self-hosting/foundationdb.mdx index bce99b66dd..5de8408f51 100644 --- a/website/src/content/docs/self-hosting/foundationdb.mdx +++ b/website/src/content/docs/self-hosting/foundationdb.mdx @@ -25,7 +25,7 @@ Its strict serializability guarantees, fault tolerance, and ability to scale lin | | RocksDB (File System) | PostgreSQL | FoundationDB | |---|---|---|---| -| **Scalability** | Single node | Primary/replica failover | Linear horizontal scaling | +| **Scalability** | Single node | Multi-node and multi-region | Linear horizontal scaling | | **Fault tolerance** | None | Primary/replica failover | Automatic recovery with no data loss | | **Production readiness** | Development and small deployments | Production-ready for light-to-moderate multi-node workloads | Battle-tested at global scale | diff --git a/website/src/content/docs/self-hosting/postgres.mdx b/website/src/content/docs/self-hosting/postgres.mdx index 34b2fc5809..ef737738fc 100644 --- a/website/src/content/docs/self-hosting/postgres.mdx +++ b/website/src/content/docs/self-hosting/postgres.mdx @@ -8,6 +8,16 @@ skill: true PostgreSQL is the recommended backend for multi-node self-hosted deployments. It is production-ready for light-to-moderate workloads, up to roughly 1,000 concurrent actors, but is not built for enterprise scale beyond that. For a single-node deployment, use the file system backend (RocksDB-based). Teams running larger or high-throughput realtime workloads should contact [enterprise support](https://rivet.dev/sales) about FoundationDB. +## Overview + +PostgreSQL is the storage and coordination backend for self-hosted Rivet deployments that run more than one engine node. Multiple engine nodes can share a single PostgreSQL instance with no extra coordination service to deploy. Rivet handles leader election, failover, and version sequencing internally. + +Use PostgreSQL when you need: + +- **Multiple engine nodes** behind a load balancer for redundancy and horizontal scaling. +- **Multi-region deployments** (deploy one PostgreSQL instance per region, see [Multi-Region](/docs/self-hosting/multi-region)). +- **High availability** with a managed or self-managed primary/replica failover setup. + ## Choosing a Backend Pick your database backend based on how many engine nodes you run: @@ -59,6 +69,41 @@ Multi-node PostgreSQL deployments require NATS as the pub/sub backend so engine See the [production checklist](/docs/self-hosting/production-checklist#nats) and [Configuration](/docs/self-hosting/configuration) for details. +## Requirements and Recommendations + +### Version + +Use PostgreSQL 14 or newer. Rivet is tested against PostgreSQL 18, which is recommended for new deployments. + +### Connection Limits + +Each Rivet engine node opens a pool of direct connections to PostgreSQL and can use well over a hundred connections per node under load. PostgreSQL's default `max_connections` of `100` is too low for even a single busy engine node. + +- Set PostgreSQL `max_connections` to comfortably exceed `(number of engine nodes × 150)` plus headroom for backups, monitoring, and your own queries. +- If you use a managed PostgreSQL service, confirm its connection limit is high enough or pick a tier that allows raising it. Connection exhaustion shows up as engine startup failures or stalled requests under load. + + +Do not work around the connection limit with a connection pooler. See [Do Not Use Connection Poolers](#do-not-use-connection-poolers) below. + + +### Resources + +PostgreSQL is the system of record for the entire deployment, so size it accordingly: + +- Give PostgreSQL dedicated CPU, memory, and fast disk (SSD/NVMe with high IOPS). Avoid co-locating it with other heavy workloads. +- Rivet generates steady write and row-turnover on its internal tables. Keep autovacuum enabled and healthy so dead tuples do not accumulate. + +### High Availability and Backups + +A single PostgreSQL instance is a single point of failure for your whole deployment. + +- Configure a standby replica with automatic failover (managed services such as Amazon RDS, Cloud SQL, and Azure Database provide this). +- Enable automated backups and point-in-time recovery, and periodically test restoring from them. + +### Multi-Region + +Deploy one PostgreSQL instance per region or datacenter. Engine nodes connect to the PostgreSQL instance in their own region. See [Multi-Region](/docs/self-hosting/multi-region) for the full topology. + ## Managed Postgres Compatibility Some hosted PostgreSQL platforms require additional configuration due to platform-specific restrictions. @@ -209,3 +254,15 @@ Do not use: - PgBouncer - Supavisor - AWS RDS Proxy + + +## Troubleshooting + +### Too Many Connections + +Errors like `FATAL: sorry, too many clients already` or engine nodes failing to start under load mean PostgreSQL's `max_connections` is too low. Raise it to account for every engine node (see [Connection Limits](#connection-limits)). Do not add a connection pooler to work around this. + +### Connection Refused or TLS Errors + +- Confirm the engine connects directly to PostgreSQL and not through a pooler (PgBouncer, Supavisor, RDS Proxy). Rivet requires direct connections. +- For TLS errors, verify `sslmode` matches your server and, for custom certificate authorities, that `ssl.root_cert_path` points to the correct CA certificate. See [SSL/TLS Support](#ssltls-support). diff --git a/website/src/content/docs/self-hosting/production-checklist.mdx b/website/src/content/docs/self-hosting/production-checklist.mdx index c554dc2c3c..ad9c653e0d 100644 --- a/website/src/content/docs/self-hosting/production-checklist.mdx +++ b/website/src/content/docs/self-hosting/production-checklist.mdx @@ -34,10 +34,14 @@ Also review the [general production checklist](/docs/general/production-checklis ## PostgreSQL -- **PostgreSQL is recommended for multi-node deployments.** It is production-ready for light-to-moderate workloads (up to roughly 1,000 concurrent actors) but is not built for enterprise scale. Validate the deployment carefully before rollout. -- **Configure automated backups.** Set up regular backups for your PostgreSQL database to prevent data loss. -- **Configure failover.** Set up a standby replica with automatic failover to ensure high availability. -- **Use FoundationDB for the most scalable production-ready deployments.** FoundationDB provides the best performance, scalability, and uptime for Rivet. Contact [enterprise support](https://rivet.dev/sales) for FoundationDB guidance. +- **Use PostgreSQL for multi-node and multi-region deployments.** Multiple engine nodes can share one PostgreSQL instance; no extra coordination service is required. PostgreSQL is production-ready for light-to-moderate workloads (up to roughly 1,000 concurrent actors) but is not built for enterprise scale. See [PostgreSQL](/docs/self-hosting/postgres). +- **Raise `max_connections`.** Each engine node opens well over a hundred connections under load. Size `max_connections` to at least `(number of engine nodes × 150)` plus headroom. PostgreSQL's default of `100` is too low. See [Connection Limits](/docs/self-hosting/postgres#connection-limits). +- **Do not use a connection pooler.** Rivet requires direct connections. Do not put PgBouncer, Supavisor, or RDS Proxy in front of PostgreSQL. +- **Give PostgreSQL dedicated resources.** Provision dedicated CPU, memory, and fast disk, and keep autovacuum healthy. PostgreSQL is the system of record for the whole deployment. +- **Configure automated backups.** Set up regular backups and point-in-time recovery, and test restoring from them. +- **Configure failover.** Set up a standby replica with automatic failover to ensure high availability. A single instance is a single point of failure. +- **Use one PostgreSQL instance per region.** For multi-region deployments, deploy a separate PostgreSQL instance in each region. +- **Use FoundationDB for the largest deployments.** Enterprise teams running at very large scale can contact [enterprise support](https://rivet.dev/sales) for FoundationDB guidance. ## NATS From a783a31b8b2221a7925d467f27830faf486e0153 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 25 Jun 2026 16:04:12 -0700 Subject: [PATCH 14/19] [SLOP(claude-opus-4-8-high)] perf(universaldb): batch leader apply and fold follower commit round-trips --- engine/packages/universaldb/Cargo.toml | 2 +- .../universaldb/src/driver/postgres/commit.rs | 206 +++++++-- .../src/driver/postgres/resolver/apply.rs | 239 +++++++--- .../driver/postgres/resolver/apply_tests.rs | 299 +++++++++++++ .../src/driver/postgres/resolver/mod.rs | 411 +++++++++++++----- .../universaldb/src/driver/postgres/shared.rs | 15 + .../compose/template/src/docker-compose.ts | 1 + self-host/dev-multinode/docker-compose.yml | 3 + 8 files changed, 956 insertions(+), 220 deletions(-) create mode 100644 engine/packages/universaldb/src/driver/postgres/resolver/apply_tests.rs diff --git a/engine/packages/universaldb/Cargo.toml b/engine/packages/universaldb/Cargo.toml index 57dba4ab33..6781c7cde1 100644 --- a/engine/packages/universaldb/Cargo.toml +++ b/engine/packages/universaldb/Cargo.toml @@ -27,7 +27,7 @@ tempfile.workspace = true thiserror.workspace = true tokio-postgres-rustls.workspace = true tokio-postgres.workspace = true -tokio-util.workspace = true +tokio-util = { workspace = true, features = ["rt"] } tokio.workspace = true tracing.workspace = true url.workspace = true diff --git a/engine/packages/universaldb/src/driver/postgres/commit.rs b/engine/packages/universaldb/src/driver/postgres/commit.rs index e61714dd0e..d191c815a3 100644 --- a/engine/packages/universaldb/src/driver/postgres/commit.rs +++ b/engine/packages/universaldb/src/driver/postgres/commit.rs @@ -52,36 +52,43 @@ pub async fn submit( .await .context("failed to get connection for commit submit")?; + // Enqueue the request and wake the leader's drain loop in one round-trip. The autocommit + // statement durably inserts the row and fires the NOTIFY together, so there is no separate + // notify round-trip or second pool acquire. let id: i64 = conn .query_one( - "INSERT INTO udb_commit_requests (epoch, read_version, payload, reply_channel) - VALUES ($1, $2, $3, $4) - RETURNING id", - &[&lease.epoch, &read_version, &payload, &reply_channel], + "WITH ins AS ( + INSERT INTO udb_commit_requests (epoch, read_version, payload, reply_channel) + VALUES ($1, $2, $3, $4) + RETURNING id + ) + SELECT pg_notify($5, id::text), id FROM ins", + &[ + &lease.epoch, + &read_version, + &payload, + &reply_channel, + &commit_channel(&lease.leader_addr), + ], ) .await - .context("failed to enqueue commit request")? - .get(0); - - // Wake the leader's drain loop. - if let Err(err) = conn - .execute( - "SELECT pg_notify($1, $2)", - &[&commit_channel(&lease.leader_addr), &id.to_string()], - ) - .await - { - tracing::debug!( - ?err, - "failed to notify leader; relying on its poll backstop" - ); - } + .context("failed to enqueue and notify commit request")? + .get(1); // Release the connection before waiting so a long wait does not pin a pool slot. The request // row is durable, so await_result re-acquires a connection per poll. drop(conn); - await_result(shared, id, lease.epoch, &mut reply_rx).await + let submit_start = Instant::now(); + let result = await_result(shared, id, lease.epoch, &mut reply_rx).await; + tracing::debug!( + id, + epoch = lease.epoch, + wait_ms = submit_start.elapsed().as_millis() as u64, + ok = result.is_ok(), + "udb commit submit completed" + ); + result } /// Wait for a known leader, returning a retryable error if none is elected in time. @@ -98,53 +105,166 @@ async fn wait_for_leader(shared: &Arc) -> Result { } } -/// Poll the request row until it reaches a terminal status, woken by reply NOTIFYs with a polling -/// backstop. Bails as retryable if the leader epoch advances (our request is now orphaned and will -/// never be applied, so it is definitively not committed). +/// Wait for the commit result, resolved directly from the leader's reply NOTIFY payload on the happy +/// path. A polling `read_status` backstop covers a missed/lagged NOTIFY, and an epoch advance orphans +/// the request (it will never be applied, so it is definitively not committed). async fn await_result( shared: &Arc, id: i64, submit_epoch: i64, reply_rx: &mut tokio::sync::broadcast::Receiver, ) -> Result<()> { + let start = Instant::now(); + // Diagnostics: count how the waiter is driven so we can tell whether the reply NOTIFY is doing + // its job or whether commits are riding the slow poll backstop / getting orphaned by failover. + let mut status_reads = 0u32; + let mut notify_wakes = 0u32; + let mut poll_wakes = 0u32; + + // The backstop runs on a fixed-cadence interval, not a per-iteration sleep: under a flood of + // other commits' replies on this node's shared reply channel (which we skip past), a fresh + // per-iteration sleep would keep resetting and never fire, starving the backstop if our own + // NOTIFY was lost. An interval ticks on wall-clock cadence regardless of loop churn. + let mut poll_interval = tokio::time::interval(RESULT_POLL_INTERVAL); + poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + // Consume the immediate first tick so the first backstop is one interval out, after the reply + // has had a chance to arrive. + poll_interval.tick().await; + loop { - // Re-acquire a connection per poll: the request row is durable, so a transient pool/query - // error just means we retry the poll rather than failing a possibly-applied commit. + // Wait for our reply NOTIFY, falling back to a status read on a poll tick or a lagged + // broadcast. The happy path resolves straight from the payload with no status SELECT. + tokio::select! { + res = reply_rx.recv() => { + match res { + Ok(payload) => { + notify_wakes += 1; + match parse_reply(&payload, id) { + Some(ReplyOutcome::Committed) => { + tracing::debug!( + id, + wait_ms = start.elapsed().as_millis() as u64, + status_reads, + notify_wakes, + poll_wakes, + "udb commit resolved: committed" + ); + return Ok(()); + } + Some(ReplyOutcome::Conflict) => { + tracing::debug!( + id, + wait_ms = start.elapsed().as_millis() as u64, + status_reads, + notify_wakes, + poll_wakes, + "udb commit resolved: conflict" + ); + return Err(DatabaseError::NotCommitted.into()); + } + // Reply for another waiter on the shared channel; keep waiting for ours. + None => continue, + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { + // We may have missed our own reply; fall through to the status backstop. + notify_wakes += 1; + tracing::debug!(id, lagged = n, "udb reply broadcast lagged"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + tracing::warn!(id, "udb reply broadcast closed; re-subscribing"); + *reply_rx = shared + .listener + .listen(&reply_channel(&shared.node_id)) + .await; + continue; + } + } + } + _ = poll_interval.tick() => { + poll_wakes += 1; + } + } + + // Backstop path (poll tick or lagged broadcast): re-acquire a connection and read the durable + // status. A transient pool/query error just means we retry rather than failing a + // possibly-applied commit. + status_reads += 1; match read_status(shared, id).await { - Ok(Some(Status::Committed)) => return Ok(()), - Ok(Some(Status::Conflict)) => return Err(DatabaseError::NotCommitted.into()), + Ok(Some(Status::Committed)) => { + tracing::debug!( + id, + wait_ms = start.elapsed().as_millis() as u64, + status_reads, + notify_wakes, + poll_wakes, + "udb commit resolved: committed (backstop)" + ); + return Ok(()); + } + Ok(Some(Status::Conflict)) => { + tracing::debug!( + id, + wait_ms = start.elapsed().as_millis() as u64, + status_reads, + notify_wakes, + poll_wakes, + "udb commit resolved: conflict (backstop)" + ); + return Err(DatabaseError::NotCommitted.into()); + } Ok(Some(Status::Pending)) => {} Ok(None) => { // The row was GC'd before we observed a terminal status. Treat as not committed // and let the retry loop resubmit. + tracing::warn!( + id, + wait_ms = start.elapsed().as_millis() as u64, + status_reads, + "udb commit row missing before terminal status (gc'd); treating as not committed" + ); return Err(DatabaseError::NotCommitted.into()); } Err(err) => { - tracing::debug!(?err, "transient error polling commit status, retrying"); + tracing::debug!(?err, id, "transient error polling commit status, retrying"); } } // If a new leader took over, our old-epoch request will never be claimed. if let Some(current) = shared.current_lease() { if current.epoch != submit_epoch { + tracing::warn!( + id, + submit_epoch, + current_epoch = current.epoch, + wait_ms = start.elapsed().as_millis() as u64, + notify_wakes, + poll_wakes, + "udb commit orphaned by leader failover; treating as not committed" + ); return Err(DatabaseError::NotCommitted.into()); } } + } +} - tokio::select! { - res = reply_rx.recv() => { - match res { - Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - *reply_rx = shared - .listener - .listen(&reply_channel(&shared.node_id)) - .await; - } - } - } - _ = tokio::time::sleep(RESULT_POLL_INTERVAL) => {} - } +enum ReplyOutcome { + Committed, + Conflict, +} + +/// Parse a leader reply payload (`":committed:"` or `":conflict"`). Returns +/// `None` when the payload is for a different waiter on the shared reply channel or is unparseable. +fn parse_reply(payload: &str, id: i64) -> Option { + let mut parts = payload.split(':'); + let reply_id: i64 = parts.next()?.parse().ok()?; + if reply_id != id { + return None; + } + match parts.next()? { + "committed" => Some(ReplyOutcome::Committed), + "conflict" => Some(ReplyOutcome::Conflict), + _ => None, } } diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs b/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs index d9729de884..278bb70fe7 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/apply.rs @@ -1,66 +1,142 @@ +use std::collections::{BTreeMap, HashMap}; + use anyhow::{Context, Result}; -use deadpool_postgres::Transaction; use crate::{ atomic::apply_atomic_op, options::MutationType, tuple::Versionstamp, tx_ops::Operation, versionstamp::substitute_raw_versionstamp, }; -/// Apply a winning transaction's operations to `kv` inside the leader's batch txn. -/// -/// `commit_version` is the Postgres-resolved version assigned to this commit (`nextval`). It is -/// substituted into the 8-byte committed-version slot of any versionstamped key/value so -/// versionstamps are globally monotonic with commit order across all follower processes. -pub async fn apply( - txn: &Transaction<'_>, - operations: Vec, - commit_version: u64, -) -> Result<()> { - // Distinguishes multiple versionstamped operations within a single commit so their 10-byte - // stamps stay unique (8-byte version shared, 2-byte counter incremented). - let mut versionstamp_counter: u16 = 0; - - for op in operations { - match op { - Operation::SetValue { key, value } => { - upsert(txn, &key, &value).await?; - } - Operation::Clear { key } => { - txn.execute("DELETE FROM kv WHERE key = $1", &[&key]) - .await - .context("failed to clear key")?; - } - Operation::ClearRange { begin, end } => { - txn.execute( - "DELETE FROM kv WHERE key >= $1 AND key < $2", - &[&begin, &end], - ) - .await - .context("failed to clear range")?; +/// A winning commit's Postgres-resolved version and its decoded operations. Winners are folded in id +/// order. +pub struct Winner { + pub commit_version: u64, + pub operations: Vec, +} + +/// The materialized result of folding a batch of winners over the current `kv` state. Each distinct +/// key appears at most once across `upserts` and `point_deletes`, so the batch's point writes +/// collapse to a fixed number of statements regardless of batch size. +pub struct WriteSet { + pub upserts: Vec<(Vec, Vec)>, + pub point_deletes: Vec>, + pub range_deletes: Vec<(Vec, Vec)>, +} + +/// In-memory working state layered over the pre-batch `kv` snapshot. Folding the batch's winners +/// through this overlay in id order reproduces the exact serial semantics of applying each commit +/// one at a time: a later commit's atomic read observes an earlier commit's write because the +/// overlay is the live working state. +struct Overlay<'a> { + /// Pre-batch values for every key read by an atomic op in the batch. The only base read. + base: &'a HashMap, Vec>, + /// Point writes layered over the base. `Some` is a set, `None` is a point tombstone. + points: BTreeMap, Option>>, + /// Range tombstones in fold order. A key with no overlaying point write that falls in any range + /// reads as absent. + ranges: Vec<(Vec, Vec)>, +} + +impl<'a> Overlay<'a> { + fn new(base: &'a HashMap, Vec>) -> Self { + Overlay { + base, + points: BTreeMap::new(), + ranges: Vec::new(), + } + } + + /// Read-through lookup: an overlaying point write wins, then a range tombstone, then the base. + fn get(&self, key: &[u8]) -> Option> { + if let Some(value) = self.points.get(key) { + return value.clone(); + } + if self + .ranges + .iter() + .any(|(begin, end)| key >= begin.as_slice() && key < end.as_slice()) + { + return None; + } + self.base.get(key).cloned() + } + + fn set(&mut self, key: Vec, value: Vec) { + self.points.insert(key, Some(value)); + } + + fn clear(&mut self, key: Vec) { + self.points.insert(key, None); + } + + fn clear_range(&mut self, begin: Vec, end: Vec) { + // Drop any point writes inside the range; the range delete subsumes them, and a later set of + // a key in the range re-adds a point write that wins on read-through again. + let covered: Vec> = self + .points + .range(begin.clone()..end.clone()) + .map(|(key, _)| key.clone()) + .collect(); + for key in covered { + self.points.remove(&key); + } + self.ranges.push((begin, end)); + } + + fn into_write_set(self) -> WriteSet { + let mut upserts = Vec::new(); + let mut point_deletes = Vec::new(); + for (key, value) in self.points { + match value { + Some(value) => upserts.push((key, value)), + None => point_deletes.push(key), } - Operation::AtomicOp { - key, - param, - op_type, - } => { - apply_atomic( - txn, + } + WriteSet { + upserts, + point_deletes, + range_deletes: self.ranges, + } + } +} + +/// Fold every winner's operations, in id order, into a single materialized write-set over the +/// pre-batch `base` snapshot. `base` must contain the current value of every key returned by +/// [`atomic_read_keys`]. +pub fn fold_winners(winners: Vec, base: &HashMap, Vec>) -> Result { + let mut overlay = Overlay::new(base); + + for winner in winners { + // Distinguishes multiple versionstamped operations within a single commit so their 10-byte + // stamps stay unique (8-byte version shared, 2-byte counter incremented). Resets per winner. + let mut versionstamp_counter: u16 = 0; + + for op in winner.operations { + match op { + Operation::SetValue { key, value } => overlay.set(key, value), + Operation::Clear { key } => overlay.clear(key), + Operation::ClearRange { begin, end } => overlay.clear_range(begin, end), + Operation::AtomicOp { key, param, op_type, - commit_version, + } => fold_atomic( + &mut overlay, + key, + param, + op_type, + winner.commit_version, &mut versionstamp_counter, - ) - .await?; + )?, } } } - Ok(()) + Ok(overlay.into_write_set()) } -async fn apply_atomic( - txn: &Transaction<'_>, +fn fold_atomic( + overlay: &mut Overlay<'_>, key: Vec, param: Vec, op_type: MutationType, @@ -73,17 +149,17 @@ async fn apply_atomic( let key = substitute_raw_versionstamp(key, &versionstamp) .map_err(anyhow::Error::msg) .context("failed substituting versionstamped key")?; - upsert(txn, &key, ¶m).await?; + overlay.set(key, param); } MutationType::SetVersionstampedValue => { let versionstamp = build_versionstamp(commit_version, versionstamp_counter); let value = substitute_raw_versionstamp(param, &versionstamp) .map_err(anyhow::Error::msg) .context("failed substituting versionstamped value")?; - upsert(txn, &key, &value).await?; + overlay.set(key, value); } - // Read-modify-write atomics: the leader is the single writer, so reading the live value - // inside the apply txn and writing the result is serializable with no lost update. + // Read-modify-write atomics: the leader is the single writer, so reading the overlay's + // working value and writing the result is serializable with no lost update. MutationType::Add | MutationType::And | MutationType::BitAnd @@ -97,20 +173,10 @@ async fn apply_atomic( | MutationType::ByteMin | MutationType::ByteMax | MutationType::CompareAndClear => { - let current = txn - .query_opt("SELECT value FROM kv WHERE key = $1", &[&key]) - .await - .context("failed to read current value for atomic op")? - .map(|row| row.get::<_, Vec>(0)); - - let new_value = apply_atomic_op(current.as_deref(), ¶m, op_type); - - if let Some(new_value) = new_value { - upsert(txn, &key, &new_value).await?; - } else { - txn.execute("DELETE FROM kv WHERE key = $1", &[&key]) - .await - .context("failed to clear key after atomic op")?; + let current = overlay.get(&key); + match apply_atomic_op(current.as_deref(), ¶m, op_type) { + Some(new_value) => overlay.set(key, new_value), + None => overlay.clear(key), } } } @@ -118,14 +184,39 @@ async fn apply_atomic( Ok(()) } -async fn upsert(txn: &Transaction<'_>, key: &[u8], value: &[u8]) -> Result<()> { - txn.execute( - "INSERT INTO kv (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2", - &[&key, &value], - ) - .await - .context("failed to upsert kv")?; - Ok(()) +/// Every key a winner's atomic op reads, so the leader can fetch them all in one bulk query before +/// folding. Versionstamped ops do not read, so their keys are skipped. +pub fn atomic_read_keys(winners: &[Winner]) -> Vec> { + let mut keys = Vec::new(); + for winner in winners { + for op in &winner.operations { + if let Operation::AtomicOp { key, op_type, .. } = op { + if reads_current_value(*op_type) { + keys.push(key.clone()); + } + } + } + } + keys +} + +fn reads_current_value(op_type: MutationType) -> bool { + match op_type { + MutationType::SetVersionstampedKey | MutationType::SetVersionstampedValue => false, + MutationType::Add + | MutationType::And + | MutationType::BitAnd + | MutationType::Or + | MutationType::BitOr + | MutationType::Xor + | MutationType::BitXor + | MutationType::AppendIfFits + | MutationType::Max + | MutationType::Min + | MutationType::ByteMin + | MutationType::ByteMax + | MutationType::CompareAndClear => true, + } } /// Build a 10-byte versionstamp (plus the 2 user-version bytes the substitution helper ignores) @@ -137,3 +228,9 @@ fn build_versionstamp(commit_version: u64, counter: &mut u16) -> Versionstamp { *counter = counter.wrapping_add(1); Versionstamp::from(bytes) } + +// The fold operates on private types (`Overlay`, `Winner`, `WriteSet`) that integration tests under +// `tests/` cannot reach, so its unit tests live in a source-owned sibling file via this shim. +#[cfg(test)] +#[path = "apply_tests.rs"] +mod tests; diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/apply_tests.rs b/engine/packages/universaldb/src/driver/postgres/resolver/apply_tests.rs new file mode 100644 index 0000000000..95d991176d --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/resolver/apply_tests.rs @@ -0,0 +1,299 @@ +use std::collections::HashMap; + +use super::*; + +fn k(s: &str) -> Vec { + s.as_bytes().to_vec() +} + +fn add_param(n: i64) -> Vec { + n.to_le_bytes().to_vec() +} + +fn read_int(value: &[u8]) -> i64 { + let mut buf = [0u8; 8]; + let len = value.len().min(8); + buf[..len].copy_from_slice(&value[..len]); + i64::from_le_bytes(buf) +} + +fn set(key: &str, value: &str) -> Operation { + Operation::SetValue { + key: k(key), + value: k(value), + } +} + +fn clear(key: &str) -> Operation { + Operation::Clear { key: k(key) } +} + +fn clear_range(begin: &str, end: &str) -> Operation { + Operation::ClearRange { + begin: k(begin), + end: k(end), + } +} + +fn add(key: &str, n: i64) -> Operation { + Operation::AtomicOp { + key: k(key), + param: add_param(n), + op_type: MutationType::Add, + } +} + +fn winner(commit_version: u64, operations: Vec) -> Winner { + Winner { + commit_version, + operations, + } +} + +/// Materialize a write-set over a base map to inspect the resulting `kv` state. +fn materialize(base: &HashMap, Vec>, write_set: WriteSet) -> HashMap, Vec> { + let mut state = base.clone(); + for (begin, end) in &write_set.range_deletes { + state.retain(|key, _| { + !(key.as_slice() >= begin.as_slice() && key.as_slice() < end.as_slice()) + }); + } + for key in &write_set.point_deletes { + state.remove(key); + } + for (key, value) in write_set.upserts { + state.insert(key, value); + } + state +} + +/// Reference oracle: apply each winner's operations one at a time directly to a working state, the +/// exact serial semantics the fold must reproduce. +fn reference(base: &HashMap, Vec>, winners: &[Winner]) -> HashMap, Vec> { + let mut state = base.clone(); + for w in winners { + let mut counter: u16 = 0; + for op in &w.operations { + match op { + Operation::SetValue { key, value } => { + state.insert(key.clone(), value.clone()); + } + Operation::Clear { key } => { + state.remove(key); + } + Operation::ClearRange { begin, end } => { + state.retain(|key, _| { + !(key.as_slice() >= begin.as_slice() && key.as_slice() < end.as_slice()) + }); + } + Operation::AtomicOp { + key, + param, + op_type, + } => match op_type { + MutationType::SetVersionstampedKey => { + let vs = build_versionstamp(w.commit_version, &mut counter); + let new_key = substitute_raw_versionstamp(key.clone(), &vs).unwrap(); + state.insert(new_key, param.clone()); + } + MutationType::SetVersionstampedValue => { + let vs = build_versionstamp(w.commit_version, &mut counter); + let new_value = substitute_raw_versionstamp(param.clone(), &vs).unwrap(); + state.insert(key.clone(), new_value); + } + _ => { + let current = state.get(key).map(|v| v.as_slice()); + match apply_atomic_op(current, param, *op_type) { + Some(v) => { + state.insert(key.clone(), v); + } + None => { + state.remove(key); + } + } + } + }, + } + } + } + state +} + +/// Build a 4-byte-offset-trailed buffer that `substitute_raw_versionstamp` can stamp at `offset`. +fn stampable(prefix: &[u8], offset: u32) -> Vec { + let mut buf = Vec::new(); + buf.extend_from_slice(prefix); + buf.extend_from_slice(&[0u8; 10]); + buf.extend_from_slice(&offset.to_le_bytes()); + buf +} + +/// `fold_winners` consumes its input, so clone for tests that also run the oracle on the same data. +fn fold_winners_clone(winners: &[Winner], base: &HashMap, Vec>) -> WriteSet { + let cloned: Vec = winners + .iter() + .map(|w| Winner { + commit_version: w.commit_version, + operations: w.operations.clone(), + }) + .collect(); + fold_winners(cloned, base).unwrap() +} + +#[test] +fn same_key_set_later_id_wins() { + let winners = vec![ + winner(1, vec![set("a", "first")]), + winner(2, vec![set("a", "second")]), + ]; + let base = HashMap::new(); + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!( + state.get(&k("a")).map(|v| v.as_slice()), + Some(b"second".as_slice()) + ); + assert_eq!(state, reference(&base, &winners)); +} + +#[test] +fn two_adds_same_key_fold_sequentially() { + // 5 -> 6 -> 7 across two commits in one batch. + let mut base = HashMap::new(); + base.insert(k("n"), add_param(5)); + let winners = vec![winner(1, vec![add("n", 1)]), winner(2, vec![add("n", 1)])]; + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!(read_int(state.get(&k("n")).unwrap()), 7); + assert_eq!(state, reference(&base, &winners)); +} + +#[test] +fn set_then_add_atomic_sees_the_set() { + let base = HashMap::new(); + let winners = vec![ + winner(1, vec![set("n", "\x0a\0\0\0\0\0\0\0")]), + winner(2, vec![add("n", 1)]), + ]; + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!(read_int(state.get(&k("n")).unwrap()), 11); + assert_eq!(state, reference(&base, &winners)); +} + +#[test] +fn clear_range_then_add_sees_none() { + let mut base = HashMap::new(); + base.insert(k("r/x"), add_param(100)); + let winners = vec![ + winner(1, vec![clear_range("r/", "r0")]), + winner(2, vec![add("r/x", 1)]), + ]; + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + // The range clear wiped the base 100, so the add starts from absent/0 -> 1. + assert_eq!(read_int(state.get(&k("r/x")).unwrap()), 1); + assert_eq!(state, reference(&base, &winners)); +} + +#[test] +fn set_inside_cleared_range_is_reinserted() { + let mut base = HashMap::new(); + base.insert(k("r/x"), k("old")); + let winners = vec![winner(1, vec![clear_range("r/", "r0"), set("r/x", "new")])]; + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!( + state.get(&k("r/x")).map(|v| v.as_slice()), + Some(b"new".as_slice()) + ); + assert_eq!(state, reference(&base, &winners)); +} + +#[test] +fn versionstamped_key_and_value_distinct_stamps() { + let base = HashMap::new(); + let winners = vec![winner( + 42, + vec![ + Operation::AtomicOp { + key: stampable(b"vk/", 3), + param: k("v1"), + op_type: MutationType::SetVersionstampedKey, + }, + Operation::AtomicOp { + key: k("vv/"), + param: stampable(b"", 0), + op_type: MutationType::SetVersionstampedValue, + }, + ], + )]; + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!(state, reference(&base, &winners)); + + // The versionstamped key embeds commit_version 42 with per-commit counter 0. + let stamped_key: Vec = { + let mut key = b"vk/".to_vec(); + key.extend_from_slice(&42u64.to_be_bytes()); + key.extend_from_slice(&0u16.to_be_bytes()); + key + }; + assert_eq!( + state.get(&stamped_key).map(|v| v.as_slice()), + Some(b"v1".as_slice()) + ); + + // The versionstamped value uses the same commit_version but the next counter (1). + let stamped_value = state.get(&k("vv/")).unwrap(); + assert_eq!(&stamped_value[0..8], &42u64.to_be_bytes()); + assert_eq!(&stamped_value[8..10], &1u16.to_be_bytes()); +} + +#[test] +fn point_delete_and_upsert_disjoint() { + let mut base = HashMap::new(); + base.insert(k("keep"), k("base")); + base.insert(k("drop"), k("base")); + let winners = vec![winner(1, vec![clear("drop"), set("keep", "new")])]; + let ws = fold_winners_clone(&winners, &base); + assert_eq!(ws.point_deletes, vec![k("drop")]); + assert_eq!(ws.upserts, vec![(k("keep"), k("new"))]); + let state = materialize(&base, ws); + assert_eq!(state, reference(&base, &winners)); +} + +/// A multi-op, multi-winner batch mixing every operation kind must equal the serial oracle. +#[test] +fn mixed_batch_matches_oracle() { + let mut base = HashMap::new(); + base.insert(k("c1"), add_param(10)); + base.insert(k("c2"), add_param(20)); + base.insert(k("old"), k("x")); + base.insert(k("range/a"), k("ra")); + base.insert(k("range/b"), k("rb")); + + let winners = vec![ + winner(1, vec![set("c1", "\x01\0\0\0\0\0\0\0"), add("c1", 4)]), + winner(2, vec![clear("old"), add("c2", 5)]), + winner( + 3, + vec![clear_range("range/", "range0"), set("range/a", "back")], + ), + winner(4, vec![add("c2", 100), add("c1", 1)]), + ]; + + let ws = fold_winners_clone(&winners, &base); + let state = materialize(&base, ws); + assert_eq!(state, reference(&base, &winners)); + + // Spot checks of the folded result. + assert_eq!(read_int(state.get(&k("c1")).unwrap()), 6); // 1 +4 +1 + assert_eq!(read_int(state.get(&k("c2")).unwrap()), 125); // 20 +5 +100 + assert!(state.get(&k("old")).is_none()); + assert_eq!( + state.get(&k("range/a")).map(|v| v.as_slice()), + Some(b"back".as_slice()) + ); + assert!(state.get(&k("range/b")).is_none()); +} diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs index f9b6c45461..e07304180c 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -2,15 +2,19 @@ mod apply; mod lease; use std::{ + collections::HashMap, sync::Arc, time::{Duration, Instant}, }; use anyhow::{Context, Result}; use tokio::sync::broadcast; +use tokio_util::task::AbortOnDropHandle; use crate::{conflict_tracker::TransactionConflictTracker, transaction::TXN_TIMEOUT}; +use lease::LEASE_TTL_SECS; + use super::shared::{ ELECTION_CHANNEL, LEASE_ID, LeaseInfo, PostgresShared, WATERMARK_CHANNEL, commit_channel, }; @@ -28,13 +32,6 @@ const POLL_BACKSTOP: Duration = Duration::from_millis(50); /// How long a candidate waits before retrying election when another node holds the lease. const ELECTION_RETRY: Duration = Duration::from_secs(2); -enum DrainOutcome { - /// Processed zero or more requests; still leader. - Drained, - /// Lost the lease (epoch bumped by a new leader). Step down. - LostLease, -} - /// Spawn the per-process resolver task. Every node runs this; only the elected leader drains the /// commit queue. The returned handle is aborted when the owning driver drops, which stops lease /// renewal so the lease expires and another node can take over (node-death / failover path). @@ -119,7 +116,12 @@ async fn notify_election(shared: &Arc) { } } -/// Leader main loop: hold the lease, drain the commit queue on wake or poll, and renew the lease. +/// Leader entry point: publish our lease, compute the cold-window floor, then run renewal and +/// draining as two sibling tasks. They coordinate purely by completion: when either returns (lease +/// lost or error), the other is aborted and the leader steps down. Both operations are safe to +/// hard-abort, so no explicit cancellation signalling is needed. A renew is a single fenced +/// `UPDATE`; a drain batch runs in one Postgres transaction that rolls back cleanly when dropped, +/// leaving its claimed requests `pending` for the next leader. async fn lead(shared: &Arc, epoch: i64) -> Result<()> { // Publish our own lease into the cache immediately so our local commits route to us. shared.set_lease(LeaseInfo { @@ -133,97 +135,191 @@ async fn lead(shared: &Arc, epoch: i64) -> Result<()> { let recovery_version = recovery_floor(shared).await?; let recovery_deadline = Instant::now() + TXN_TIMEOUT; - let tracker = TransactionConflictTracker::new(); + tracing::info!( + epoch, + recovery_version, + cold_window_ms = TXN_TIMEOUT.as_millis() as u64, + "udb leader entering lead loop" + ); + + // Renewal runs in its own task so the drain loop can never starve it: if renewal shared the + // drain loop, a single long drain under sustained load would block renewal past the lease TTL + // and the lease would be lost mid-drain, thrashing leadership. Both are held in abort-on-drop + // handles so a hard abort of this `run` task (driver drop / node death) tears them down. A leaked + // renew task would keep this dead leader's lease alive and block failover. + let mut renew = AbortOnDropHandle::new(tokio::spawn(renew_loop(shared.clone(), epoch))); + let mut drain = AbortOnDropHandle::new(tokio::spawn(drain_loop( + shared.clone(), + epoch, + recovery_version, + recovery_deadline, + ))); - let mut wake_rx = shared - .listener - .listen(&commit_channel(&shared.node_id)) - .await; + // Whichever task returns first (lease lost or error), step down; the other is aborted when its + // handle drops at the end of this scope. A clean exit yields its inner result; a panic surfaces + // through `?` as a join error. + tokio::select! { + res = &mut renew => res?, + res = &mut drain => res?, + } +} - let mut renew_interval = tokio::time::interval(RENEW_INTERVAL); - renew_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - let mut poll_interval = tokio::time::interval(POLL_BACKSTOP); - poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); +/// Lease-renewal loop. Runs on its own task and pool connection so it cannot be starved by drain +/// work. Returns when the lease is definitively gone (epoch bumped by another node, or renewal +/// failing for the whole lease TTL), which causes [`lead`] to abort the drain task and step down. +async fn renew_loop(shared: Arc, epoch: i64) -> Result<()> { + let mut interval = tokio::time::interval(RENEW_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + // The lease was just acquired/renewed (expires_at = now + TTL), so consume the immediate first + // tick and renew after one interval. + interval.tick().await; - // Drain anything already queued before our first wake. - if matches!( - drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, - DrainOutcome::LostLease - ) { - return Ok(()); - } + let mut last_renew = Instant::now(); loop { - tokio::select! { - _ = renew_interval.tick() => { - if !lease::renew(&shared.pool, &shared.node_id, epoch).await? { - tracing::warn!(epoch, "lost udb lease on renew"); - return Ok(()); + interval.tick().await; + + let gap_ms = last_renew.elapsed().as_millis() as u64; + let renew_start = Instant::now(); + match lease::renew(&shared.pool, &shared.node_id, epoch).await { + Ok(true) => { + last_renew = Instant::now(); + let renew_query_ms = renew_start.elapsed().as_millis() as u64; + // With renewal on its own task this gap should track RENEW_INTERVAL closely; a large + // gap now points at pool or Postgres contention. + if gap_ms > RENEW_INTERVAL.as_millis() as u64 * 2 { + tracing::warn!( + epoch, + gap_since_last_renew_ms = gap_ms, + renew_query_ms, + "udb leader renew was delayed (pool or postgres contention)" + ); + } else { + tracing::debug!( + epoch, + gap_since_last_renew_ms = gap_ms, + renew_query_ms, + "udb leader renewed lease" + ); } } - res = wake_rx.recv() => { - match res { - Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - wake_rx = shared.listener.listen(&commit_channel(&shared.node_id)).await; - } - } - if matches!( - drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, - DrainOutcome::LostLease - ) { - return Ok(()); - } + Ok(false) => { + tracing::warn!( + epoch, + gap_since_last_renew_ms = gap_ms, + "udb leader lost lease on renew (epoch bumped by another node); stepping down" + ); + return Ok(()); } - _ = poll_interval.tick() => { - if matches!( - drain(shared, epoch, &tracker, recovery_version, recovery_deadline).await?, - DrainOutcome::LostLease - ) { + Err(err) => { + // A transient renew error is tolerable within the TTL; keep retrying. Only give up + // if we have been unable to renew for the whole lease TTL, at which point we can no + // longer assume we hold the lease. + if last_renew.elapsed() >= Duration::from_secs(LEASE_TTL_SECS as u64) { + tracing::warn!( + ?err, + epoch, + "udb leader renew failing past lease TTL; assuming lease lost and stepping down" + ); return Ok(()); } + tracing::warn!(?err, epoch, "udb leader lease renew errored; will retry"); } } } } -/// The version floor a freshly elected leader continues from: the higher of the durable watermark -/// and the sequence high-water. The LOGGED `udb_version_seq` is crash-safe, so this never regresses. -async fn recovery_floor(shared: &Arc) -> Result { - let durable = lease::current_durable_version(&shared.pool).await?; - - let conn = shared - .pool - .get() - .await - .context("failed to get connection for recovery floor")?; - let seq_high: i64 = conn - .query_one("SELECT last_value FROM udb_version_seq", &[]) - .await - .context("failed to read sequence high water")? - .get(0); - - Ok(durable.max(seq_high).max(0) as u64) -} - -/// Drain pending commit requests in id-ordered batches until none remain. Each batch resolves and -/// applies inside a single Postgres transaction (group commit), fenced on the leader's epoch. -async fn drain( - shared: &Arc, +/// Drain loop. Processes one batch per iteration. Draining until the queue emptied in a single call +/// could run for many seconds under sustained load; processing one batch at a time keeps the loop +/// at a clean await point between batches. A non-empty queue still drains back-to-back with no idle +/// wait, so throughput is unchanged. It blocks on `select!` (wake NOTIFY or poll backstop) only +/// when the queue is empty. Returns when this leader's epoch is fenced out; otherwise [`lead`] +/// aborts it when renewal reports the lease is lost. +async fn drain_loop( + shared: Arc, epoch: i64, - tracker: &TransactionConflictTracker, recovery_version: u64, recovery_deadline: Instant, -) -> Result { +) -> Result<()> { + let tracker = TransactionConflictTracker::new(); + + let mut wake_rx = shared + .listener + .listen(&commit_channel(&shared.node_id)) + .await; + + let mut poll_interval = tokio::time::interval(POLL_BACKSTOP); + poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { - match drain_batch(shared, epoch, tracker, recovery_version, recovery_deadline).await? { - BatchOutcome::Empty => return Ok(DrainOutcome::Drained), - BatchOutcome::Processed => {} - BatchOutcome::LostLease => return Ok(DrainOutcome::LostLease), + match drain_batch( + &shared, + epoch, + &tracker, + recovery_version, + recovery_deadline, + ) + .await? + { + BatchOutcome::LostLease => { + tracing::warn!( + epoch, + "udb leader stepping down: lost lease during drain (epoch fenced on watermark)" + ); + return Ok(()); + } + // More work may be pending; loop immediately to keep throughput up. + BatchOutcome::Processed => continue, + BatchOutcome::Empty => { + tokio::select! { + res = wake_rx.recv() => { + match res { + Ok(_) | Err(broadcast::error::RecvError::Lagged(_)) => {} + Err(broadcast::error::RecvError::Closed) => { + wake_rx = shared + .listener + .listen(&commit_channel(&shared.node_id)) + .await; + } + } + } + _ = poll_interval.tick() => {} + } + } } } } +/// The cold-window rejection floor for a freshly elected leader: the durable watermark +/// (`udb_lease.durable_version`) at election time. +/// +/// Reasoning (do NOT change this back to `max(durable, seq_high)`): +/// +/// A new leader starts with an empty conflict tracker, so it cannot detect a read-write conflict +/// against any committed write it does not already know about. The writes it is missing are exactly +/// the previous leader's winners, and every winner's write is applied to `kv` AND its +/// `commit_version` folded into `durable_version` in the SAME apply transaction. So every missing +/// write has `commit_version <= durable_version`. A committing transaction `T` is therefore safe +/// iff `T.read_version >= durable_version`: every write above its read_version was committed by THIS +/// leader and is in the tracker. Only `T.read_version < durable_version` can race a missing winner, +/// so that is the exact set the cold window must reject. +/// +/// `udb_version_seq.last_value` (the sequence high-water) is NOT a valid floor. Every drained +/// request consumes a `nextval` BEFORE the conflict check, including conflicts and cold rejects, so +/// the sequence races far ahead of `durable_version` with versions that never produced any write. +/// Using `max(durable, seq_high)` rejects essentially every commit for the whole cold window +/// (followers read at `durable_version`, which is always `< seq_high`), turning each failover into +/// a 5s mass-reject storm. The gap `(durable_version, seq_high]` holds only thrown-away loser +/// versions, so nothing in it is a missing write to guard against. +/// +/// Version ASSIGNMENT is unaffected: commit versions still come from `nextval('udb_version_seq')` +/// in `drain_batch`, which is always above the sequence high-water, so uniqueness and monotonicity +/// across failover are preserved independently of this floor. +async fn recovery_floor(shared: &Arc) -> Result { + let durable = lease::current_durable_version(&shared.pool).await?; + Ok(durable.max(0) as u64) +} + enum BatchOutcome { Empty, Processed, @@ -232,7 +328,9 @@ enum BatchOutcome { struct Reply { channel: String, - id: i64, + /// The follower's reply payload, encoding the outcome so the waiter resolves without a status + /// SELECT: `":committed:"` or `":conflict"`. + payload: String, } async fn drain_batch( @@ -273,30 +371,55 @@ async fn drain_batch( return Ok(BatchOutcome::Empty); } + let batch_start = Instant::now(); let cold_window = Instant::now() < recovery_deadline; + let batch_len = rows.len(); let mut max_winner_cv: i64 = 0; - let mut replies = Vec::with_capacity(rows.len()); - - for row in &rows { + let mut replies = Vec::with_capacity(batch_len); + let mut committed_count = 0u32; + let mut conflict_count = 0u32; + let mut cold_reject_count = 0u32; + + // Allocate all commit versions for the batch in one round-trip instead of a `nextval` per row. + // They are assigned to rows in id order (winners and losers alike; losers' versions are + // harmlessly skipped), so versionstamps stay monotonic with commit order. The defensive sort + // keeps assignment monotonic regardless of how Postgres orders the per-row `nextval` evaluation. + let mut versions: Vec = txn + .query( + "SELECT nextval('udb_version_seq') FROM generate_series(1, $1::bigint)", + &[&(batch_len as i64)], + ) + .await + .context("failed to allocate commit versions")? + .iter() + .map(|row| row.get::<_, i64>(0)) + .collect(); + versions.sort_unstable(); + + // Resolve every request in memory in id order. Winners are collected with their version and + // operations for the fold; the bulk status stamp is built for all rows at once. + let mut winners: Vec = Vec::new(); + let mut stamp_ids: Vec = Vec::with_capacity(batch_len); + let mut stamp_statuses: Vec<&str> = Vec::with_capacity(batch_len); + let mut stamp_versions: Vec> = Vec::with_capacity(batch_len); + + for (i, row) in rows.iter().enumerate() { let id: i64 = row.get(0); let read_version: i64 = row.get(1); let payload: Vec = row.get(2); let reply_channel: String = row.get(3); + let commit_version = versions[i]; let decoded = super::codec::decode_commit_request(&payload) .context("failed to decode commit payload")?; - let commit_version: i64 = txn - .query_one("SELECT nextval('udb_version_seq')", &[]) - .await - .context("failed to get next commit version")? - .get(0); - let start_version = read_version.max(0) as u64; // Cold-window guard: a commit whose read_version predates the recovery floor cannot be // safely resolved against this leader's empty window. Reject it as retryable. - let conflicted = if cold_window && start_version < recovery_version { + let cold_rejected = cold_window && start_version < recovery_version; + let conflicted = if cold_rejected { + cold_reject_count += 1; true } else { tracker @@ -308,32 +431,98 @@ async fn drain_batch( .await }; + stamp_ids.push(id); if conflicted { - txn.execute( - "UPDATE udb_commit_requests SET status = 'conflict' WHERE id = $1", - &[&id], - ) - .await - .context("failed to stamp conflict")?; + if !cold_rejected { + conflict_count += 1; + } + stamp_statuses.push("conflict"); + stamp_versions.push(None); } else { - apply::apply(&txn, decoded.operations, commit_version.max(0) as u64) - .await - .context("failed to apply commit")?; - txn.execute( - "UPDATE udb_commit_requests SET status = 'committed', commit_version = $1 WHERE id = $2", - &[&commit_version, &id], - ) - .await - .context("failed to stamp committed")?; + committed_count += 1; + stamp_statuses.push("committed"); + stamp_versions.push(Some(commit_version)); max_winner_cv = max_winner_cv.max(commit_version); + winners.push(apply::Winner { + commit_version: commit_version.max(0) as u64, + operations: decoded.operations, + }); } + let reply_payload = if conflicted { + format!("{id}:conflict") + } else { + format!("{id}:committed:{commit_version}") + }; replies.push(Reply { channel: reply_channel, - id, + payload: reply_payload, }); } + // Bulk-read the pre-batch value of every key a winner's atomic op reads in one query, then fold + // all winners into a single materialized write-set in memory. This collapses the per-row apply + // round-trips to a fixed count independent of batch size. + let atomic_keys = apply::atomic_read_keys(&winners); + let base = if atomic_keys.is_empty() { + HashMap::new() + } else { + txn.query( + "SELECT key, value FROM kv WHERE key = ANY($1::bytea[])", + &[&atomic_keys], + ) + .await + .context("failed to bulk-read atomic op keys")? + .into_iter() + .map(|row| (row.get::<_, Vec>(0), row.get::<_, Vec>(1))) + .collect() + }; + + let apply::WriteSet { + upserts, + point_deletes, + range_deletes, + } = apply::fold_winners(winners, &base).context("failed to fold batch winners")?; + + // Materialize the write-set in O(1) statements per kind. Range deletes run first so a key whose + // final state is a set but that fell inside an earlier range clear is re-inserted by the upsert, + // not removed. + for (begin, end) in &range_deletes { + txn.execute("DELETE FROM kv WHERE key >= $1 AND key < $2", &[begin, end]) + .await + .context("failed to clear range")?; + } + if !point_deletes.is_empty() { + txn.execute( + "DELETE FROM kv WHERE key = ANY($1::bytea[])", + &[&point_deletes], + ) + .await + .context("failed to bulk-delete cleared keys")?; + } + if !upserts.is_empty() { + let (keys, values): (Vec>, Vec>) = upserts.into_iter().unzip(); + txn.execute( + "INSERT INTO kv (key, value) + SELECT * FROM unnest($1::bytea[], $2::bytea[]) + ON CONFLICT (key) DO UPDATE SET value = excluded.value", + &[&keys, &values], + ) + .await + .context("failed to bulk-upsert kv")?; + } + + // Stamp every request's terminal status in one statement instead of a per-row UPDATE. + txn.execute( + "UPDATE udb_commit_requests AS r + SET status = b.status, commit_version = b.cv + FROM unnest($1::bigint[], $2::text[], $3::bigint[]) AS b(id, status, cv) + WHERE r.id = b.id", + &[&stamp_ids, &stamp_statuses, &stamp_versions], + ) + .await + .context("failed to stamp commit statuses")?; + // Advance the watermark, fenced on our epoch. A zombie old leader whose epoch was bumped sees // zero rows updated and must step down before any of its writes become visible. let new_durable: i64 = match txn @@ -362,6 +551,18 @@ async fn drain_batch( notify_after_commit(&conn, new_durable, &replies).await; + tracing::info!( + epoch, + batch_len, + committed = committed_count, + conflict = conflict_count, + cold_reject = cold_reject_count, + cold_window, + new_durable, + batch_ms = batch_start.elapsed().as_millis() as u64, + "udb leader processed commit batch" + ); + Ok(BatchOutcome::Processed) } @@ -383,11 +584,11 @@ async fn notify_after_commit( } let channels: Vec<&str> = replies.iter().map(|r| r.channel.as_str()).collect(); - let ids: Vec = replies.iter().map(|r| r.id.to_string()).collect(); + let payloads: Vec<&str> = replies.iter().map(|r| r.payload.as_str()).collect(); if let Err(err) = conn .execute( "SELECT pg_notify(c, p) FROM unnest($1::text[], $2::text[]) AS t(c, p)", - &[&channels, &ids], + &[&channels, &payloads], ) .await { diff --git a/engine/packages/universaldb/src/driver/postgres/shared.rs b/engine/packages/universaldb/src/driver/postgres/shared.rs index 5b14c4df08..b26f14a361 100644 --- a/engine/packages/universaldb/src/driver/postgres/shared.rs +++ b/engine/packages/universaldb/src/driver/postgres/shared.rs @@ -97,6 +97,21 @@ impl PostgresShared { /// Publish a freshly observed/elected lease into the cache. pub fn set_lease(&self, lease: LeaseInfo) { + let changed = self + .lease_rx + .borrow() + .as_ref() + .map(|prev| prev.epoch != lease.epoch || prev.leader_addr != lease.leader_addr) + .unwrap_or(true); + if changed { + tracing::info!( + epoch = lease.epoch, + leader_addr = %lease.leader_addr, + self_node = %self.node_id, + is_self = (lease.leader_addr == self.node_id), + "udb follower observed leader lease change" + ); + } let _ = self.lease_tx.send(Some(lease)); } diff --git a/self-host/compose/template/src/docker-compose.ts b/self-host/compose/template/src/docker-compose.ts index 4e3781aab2..2245a31c75 100644 --- a/self-host/compose/template/src/docker-compose.ts +++ b/self-host/compose/template/src/docker-compose.ts @@ -292,6 +292,7 @@ export function generateDockerCompose(context: TemplateContext) { restart: "unless-stopped", environment: [ "RUST_LOG_ANSI_COLOR=1", + "RUST_LOG=universaldb::driver::postgres=debug", "RIVET_OTEL_ENABLED=1", "RIVET_OTEL_SAMPLER_RATIO=1", `RIVET_OTEL_GRPC_ENDPOINT=http://${context.getServiceHost("otel-collector", datacenter.name)}:4317`, diff --git a/self-host/dev-multinode/docker-compose.yml b/self-host/dev-multinode/docker-compose.yml index 389510f7b3..28234658d2 100644 --- a/self-host/dev-multinode/docker-compose.yml +++ b/self-host/dev-multinode/docker-compose.yml @@ -179,6 +179,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 @@ -219,6 +220,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 @@ -257,6 +259,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 From 0c861be1cc95dae0ae6def2b97f6c0e809de6128 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Fri, 26 Jun 2026 10:29:03 -0700 Subject: [PATCH 15/19] [slopfix] perf(universaldb): fold postgres drain_batch into claim+nextval and single apply CTE --- .../universaldb/src/conflict_tracker.rs | 66 ++++--- .../src/driver/postgres/resolver/mod.rs | 160 +++++++++------ .../src/driver/rocksdb/transaction_task.rs | 2 +- .../universaldb/tests/kv_upsert_scaling.rs | 185 ++++++++++++++++++ 4 files changed, 325 insertions(+), 88 deletions(-) create mode 100644 engine/packages/universaldb/tests/kv_upsert_scaling.rs diff --git a/engine/packages/universaldb/src/conflict_tracker.rs b/engine/packages/universaldb/src/conflict_tracker.rs index 127e2bf615..3e543a99ee 100644 --- a/engine/packages/universaldb/src/conflict_tracker.rs +++ b/engine/packages/universaldb/src/conflict_tracker.rs @@ -1,4 +1,6 @@ use std::{ + collections::BTreeMap, + ops::Bound, sync::{ Arc, atomic::{AtomicU64, Ordering}, @@ -18,7 +20,6 @@ const TXN_CONFLICT_TTL: Duration = Duration::from_secs(10); struct PreviousTransaction { insert_instant: Instant, start_version: u64, - commit_version: u64, conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, } @@ -33,18 +34,18 @@ struct PreviousTransaction { /// `check_and_insert` takes the commit version from the caller instead of generating it. #[derive(Clone)] pub struct TransactionConflictTracker { - // NOTE: We use a mutex because we need to lock reads across all active txns. This could be optimized to - // only lock txns that have overlapping ranges with the currently checking one, but its a small - // optimization because most txns are going to be very recent and this only stores the last 10 seconds of - // txns. - txns: Arc>>, + // Keyed by commit version, which is unique per committed transaction (unlike start version, which + // concurrent transactions can share). The ordering lets the conflict scan skip transactions whose + // commit version cannot overlap the committing transaction, and lets pruning drop expired entries + // from the front since commit versions grow with commit time. + txns: Arc>>, global_version: Arc, } impl TransactionConflictTracker { pub fn new() -> Self { TransactionConflictTracker { - txns: Arc::new(Mutex::new(Vec::new())), + txns: Arc::new(Mutex::new(BTreeMap::new())), global_version: Arc::new(AtomicU64::new(0)), } } @@ -67,13 +68,23 @@ impl TransactionConflictTracker { ) -> bool { let mut txns = self.txns.lock().await; - // Prune old entries - txns.retain(|txn| txn.insert_instant.elapsed() < TXN_CONFLICT_TTL); + // Prune old entries. Commit versions grow with commit time, so expired entries are + // contiguous at the front of the map. + while let Some((_, txn)) = txns.first_key_value() { + if txn.insert_instant.elapsed() < TXN_CONFLICT_TTL { + break; + } + + txns.pop_first(); + } - for txn2 in &*txns { + // A retained transaction can only conflict if its commit version is greater than this + // transaction's start version, so skip everything at or below it. + for (txn2_commit_version, txn2) in + txns.range((Bound::Excluded(txn1_start_version), Bound::Unbounded)) + { // Check txn versions overlap (intersection or encapsulation) - if txn1_start_version < txn2.commit_version && txn2.start_version < txn1_commit_version - { + if txn2.start_version < txn1_commit_version { for (cr1_start, cr1_end, cr1_type) in &txn1_conflict_ranges { for (cr2_start, cr2_end, cr2_type) in &txn2.conflict_ranges { // Check conflict ranges overlap @@ -88,7 +99,7 @@ impl TransactionConflictTracker { txn1_start_version, txn1_commit_version, txn2_start_version = txn2.start_version, - txn2_commit_version = txn2.commit_version, + txn2_commit_version = %txn2_commit_version, "transaction conflict detected" ); return true; @@ -99,25 +110,26 @@ impl TransactionConflictTracker { } // If no conflicts were detected, save txn data - txns.push(PreviousTransaction { - insert_instant: Instant::now(), - start_version: txn1_start_version, - commit_version: txn1_commit_version, - conflict_ranges: txn1_conflict_ranges, - }); + txns.insert( + txn1_commit_version, + PreviousTransaction { + insert_instant: Instant::now(), + start_version: txn1_start_version, + conflict_ranges: txn1_conflict_ranges, + }, + ); false } - pub async fn remove(&self, txn_start_version: u64) { + pub async fn remove(&self, txn_commit_version: u64) { let mut txns = self.txns.lock().await; + txns.remove(&txn_commit_version); + } - if let Some(i) = txns - .iter() - .enumerate() - .find_map(|(i, txn)| (txn.start_version == txn_start_version).then_some(i)) - { - txns.remove(i); - } + /// Current retained transaction count. Diagnostic: the conflict scan in `check_and_insert` is + /// O(this) per commit, so a growing map directly inflates per-commit service time. + pub async fn len(&self) -> usize { + self.txns.lock().await.len() } } diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs index e07304180c..098f022663 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -351,11 +351,19 @@ async fn drain_batch( .await .context("failed to start drain batch txn")?; - // Claim a batch in id order. FOR UPDATE SKIP LOCKED holds the rows for this txn so they are - // stamped terminal on COMMIT with no intermediate 'claimed' state to clean up. + // Claim a batch in id order and allocate every commit version in the SAME round-trip via an + // inline `nextval`, instead of a separate version-allocation query. FOR UPDATE SKIP LOCKED holds + // the rows for this txn so they are stamped terminal on COMMIT with no intermediate 'claimed' + // state to clean up. + // + // Postgres does NOT guarantee `nextval` is evaluated in output (id) order, so the per-row `cv` + // here may not be monotonic in id. The versions are collected, sorted, and re-assigned to rows in + // id order below, exactly as the separate-query path did, because versionstamp monotonicity with + // commit order is load-bearing (epoxy changelog catch-up + depot PITR). let rows = txn .query( - "SELECT id, read_version, payload, reply_channel + "SELECT id, read_version, payload, reply_channel, + nextval('udb_version_seq') AS cv FROM udb_commit_requests WHERE status = 'pending' AND epoch = $1 ORDER BY id @@ -380,20 +388,10 @@ async fn drain_batch( let mut conflict_count = 0u32; let mut cold_reject_count = 0u32; - // Allocate all commit versions for the batch in one round-trip instead of a `nextval` per row. - // They are assigned to rows in id order (winners and losers alike; losers' versions are - // harmlessly skipped), so versionstamps stay monotonic with commit order. The defensive sort - // keeps assignment monotonic regardless of how Postgres orders the per-row `nextval` evaluation. - let mut versions: Vec = txn - .query( - "SELECT nextval('udb_version_seq') FROM generate_series(1, $1::bigint)", - &[&(batch_len as i64)], - ) - .await - .context("failed to allocate commit versions")? - .iter() - .map(|row| row.get::<_, i64>(0)) - .collect(); + // Re-assign the inline-allocated versions to rows in id order (winners and losers alike; losers' + // versions are harmlessly skipped) so versionstamps stay monotonic with commit order. The sort + // keeps assignment monotonic regardless of how Postgres ordered the per-row `nextval` evaluation. + let mut versions: Vec = rows.iter().map(|row| row.get::<_, i64>(4)).collect(); versions.sort_unstable(); // Resolve every request in memory in id order. Winners are collected with their version and @@ -403,6 +401,11 @@ async fn drain_batch( let mut stamp_statuses: Vec<&str> = Vec::with_capacity(batch_len); let mut stamp_versions: Vec> = Vec::with_capacity(batch_len); + // Sub-phase timers to decompose batch_ms and confirm whether the service time is constant (pure + // M/M/1 queueing) or itself inflates under load. + let mut decode_dur = Duration::ZERO; + let mut conflict_dur = Duration::ZERO; + for (i, row) in rows.iter().enumerate() { let id: i64 = row.get(0); let read_version: i64 = row.get(1); @@ -410,8 +413,10 @@ async fn drain_batch( let reply_channel: String = row.get(3); let commit_version = versions[i]; + let decode_start = Instant::now(); let decoded = super::codec::decode_commit_request(&payload) .context("failed to decode commit payload")?; + decode_dur += decode_start.elapsed(); let start_version = read_version.max(0) as u64; @@ -422,13 +427,16 @@ async fn drain_batch( cold_reject_count += 1; true } else { - tracker + let conflict_start = Instant::now(); + let res = tracker .check_and_insert( start_version, commit_version.max(0) as u64, decoded.conflict_ranges, ) - .await + .await; + conflict_dur += conflict_start.elapsed(); + res }; stamp_ids.push(id); @@ -460,6 +468,8 @@ async fn drain_batch( }); } + let t_resolved = Instant::now(); + // Bulk-read the pre-batch value of every key a winner's atomic op reads in one query, then fold // all winners into a single materialized write-set in memory. This collapses the per-row apply // round-trips to a fixed count independent of batch size. @@ -478,63 +488,74 @@ async fn drain_batch( .collect() }; + let t_read = Instant::now(); + let apply::WriteSet { upserts, point_deletes, range_deletes, } = apply::fold_winners(winners, &base).context("failed to fold batch winners")?; - // Materialize the write-set in O(1) statements per kind. Range deletes run first so a key whose - // final state is a set but that fell inside an earlier range clear is re-inserted by the upsert, - // not removed. - for (begin, end) in &range_deletes { - txn.execute("DELETE FROM kv WHERE key >= $1 AND key < $2", &[begin, end]) - .await - .context("failed to clear range")?; - } - if !point_deletes.is_empty() { - txn.execute( - "DELETE FROM kv WHERE key = ANY($1::bytea[])", - &[&point_deletes], - ) - .await - .context("failed to bulk-delete cleared keys")?; - } - if !upserts.is_empty() { - let (keys, values): (Vec>, Vec>) = upserts.into_iter().unzip(); + let t_fold = Instant::now(); + + let (upsert_keys, upsert_values): (Vec>, Vec>) = upserts.into_iter().unzip(); + let (range_begins, range_ends): (Vec>, Vec>) = + range_deletes.into_iter().unzip(); + + // Range deletes run in their OWN statement BEFORE the apply CTE. Postgres data-modifying CTE + // sub-statements all observe the same snapshot and never see each other's effects, so a range + // delete and an in-range upsert in one CTE would have unspecified results. `range_deletes` are + // ranges (not materialized per-key) and can overlap an upsert key (a key inside a cleared range + // that is also re-set), so the range clear must commit its effect first; the upsert then + // re-inserts the key. Collapsed from a per-range loop into one statement over a pair of arrays. + if !range_begins.is_empty() { txn.execute( - "INSERT INTO kv (key, value) - SELECT * FROM unnest($1::bytea[], $2::bytea[]) - ON CONFLICT (key) DO UPDATE SET value = excluded.value", - &[&keys, &values], + "DELETE FROM kv USING unnest($1::bytea[], $2::bytea[]) AS r(b, e) + WHERE key >= r.b AND key < r.e", + &[&range_begins, &range_ends], ) .await - .context("failed to bulk-upsert kv")?; + .context("failed to clear ranges")?; } - // Stamp every request's terminal status in one statement instead of a per-row UPDATE. - txn.execute( - "UPDATE udb_commit_requests AS r - SET status = b.status, commit_version = b.cv - FROM unnest($1::bigint[], $2::text[], $3::bigint[]) AS b(id, status, cv) - WHERE r.id = b.id", - &[&stamp_ids, &stamp_statuses, &stamp_versions], - ) - .await - .context("failed to stamp commit statuses")?; - - // Advance the watermark, fenced on our epoch. A zombie old leader whose epoch was bumped sees - // zero rows updated and must step down before any of its writes become visible. + // Apply the rest of the batch in one CTE: point deletes, the kv upsert, the terminal status + // stamp, and the epoch-fenced watermark advance. This is safe to fold because the write sets are + // disjoint: `apply::WriteSet` guarantees each key appears at most once across `upserts` and + // `point_deletes` (see apply.rs), and the three tables (`kv`, `udb_commit_requests`, `udb_lease`) + // are independent. The watermark UPDATE is fenced on our epoch: a zombie old leader whose epoch + // was bumped sees zero rows returned and must step down before any of its writes become visible. let new_durable: i64 = match txn .query_opt( - "UPDATE udb_lease - SET durable_version = GREATEST(durable_version, $1) - WHERE id = $2 AND epoch = $3 + "WITH pdel AS ( + DELETE FROM kv WHERE key = ANY($1::bytea[]) + ), up AS ( + INSERT INTO kv (key, value) + SELECT * FROM unnest($2::bytea[], $3::bytea[]) + ON CONFLICT (key) DO UPDATE SET value = excluded.value + ), stamp AS ( + UPDATE udb_commit_requests AS r + SET status = b.status, commit_version = b.cv + FROM unnest($4::bigint[], $5::text[], $6::bigint[]) AS b(id, status, cv) + WHERE r.id = b.id + ) + UPDATE udb_lease + SET durable_version = GREATEST(durable_version, $7) + WHERE id = $8 AND epoch = $9 RETURNING durable_version", - &[&max_winner_cv, &LEASE_ID, &epoch], + &[ + &point_deletes, + &upsert_keys, + &upsert_values, + &stamp_ids, + &stamp_statuses, + &stamp_versions, + &max_winner_cv, + &LEASE_ID, + &epoch, + ], ) .await - .context("failed to advance watermark")? + .context("failed to apply batch and advance watermark")? { Some(row) => row.get(0), None => { @@ -543,14 +564,22 @@ async fn drain_batch( } }; + let t_applied = Instant::now(); + txn.commit().await.context("failed to commit drain batch")?; + let t_committed = Instant::now(); + // Watermark advances strictly after the apply txn is durably committed and visible, so a // reader handed this read_version can never miss a write with commit_version <= read_version. shared.advance_durable_version(new_durable); notify_after_commit(&conn, new_durable, &replies).await; + let t_notified = Instant::now(); + + let tracker_len = tracker.len().await; + tracing::info!( epoch, batch_len, @@ -560,6 +589,17 @@ async fn drain_batch( cold_window, new_durable, batch_ms = batch_start.elapsed().as_millis() as u64, + // Sub-phase decomposition of batch_ms (micros) to separate constant service time from + // load-dependent service inflation. + tracker_len, + decode_us = decode_dur.as_micros() as u64, + conflict_us = conflict_dur.as_micros() as u64, + resolve_us = (t_resolved - batch_start).as_micros() as u64, + read_us = (t_read - t_resolved).as_micros() as u64, + fold_us = (t_fold - t_read).as_micros() as u64, + apply_us = (t_applied - t_fold).as_micros() as u64, + commit_us = (t_committed - t_applied).as_micros() as u64, + notify_us = (t_notified - t_committed).as_micros() as u64, "udb leader processed commit batch" ); diff --git a/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs b/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs index 986e6ba2bd..662a1a1f39 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/transaction_task.rs @@ -427,7 +427,7 @@ impl TransactionTask { Ok(_) => Ok(()), Err(e) => { // If the txn failed due to a rocksdb error, remove it from the conflict tracker - self.txn_conflict_tracker.remove(start_version).await; + self.txn_conflict_tracker.remove(commit_version).await; let err_str = e.to_string(); diff --git a/engine/packages/universaldb/tests/kv_upsert_scaling.rs b/engine/packages/universaldb/tests/kv_upsert_scaling.rs new file mode 100644 index 0000000000..2039258602 --- /dev/null +++ b/engine/packages/universaldb/tests/kv_upsert_scaling.rs @@ -0,0 +1,185 @@ +//! Throwaway diagnostic: isolates the pure-Postgres cost of the resolver's kv write from all of the +//! coordination code around it (conflict tracker, fold, claim SELECT, doorbell/reply NOTIFY, queue +//! round-trips). It does NOTHING but the `kv` upsert the leader's apply CTE performs, so the delta +//! between this floor and the real `batch_ms` is "our code", not Postgres. +//! +//! Run: +//! cargo test -p universaldb --test kv_upsert_scaling -- --ignored --nocapture +//! +//! Two sweeps: +//! 1. batch-size sweep, single serial writer (mirrors the single leader drain): one txn per batch, +//! distinct keys, `unnest` upsert. Shows the per-batch fixed floor + marginal per-row cost of a +//! pure PG write as batch length grows. +//! 2. concurrency sweep: N independent writers doing single-row upsert txns. Shows the raw PG write +//! concurrency the single-leader design forgoes (PG itself scales writers; the leader serializes +//! them through one drain task). + +use std::time::{Duration, Instant}; + +use rivet_test_deps_docker::TestDatabase; +use tokio_postgres::NoTls; +use uuid::Uuid; + +async fn connect(conn_str: &str) -> tokio_postgres::Client { + let (client, connection) = tokio_postgres::connect(conn_str, NoTls) + .await + .expect("connect"); + tokio::spawn(async move { + let _ = connection.await; + }); + client +} + +/// Percentile from a pre-sorted slice of microsecond samples, returned as milliseconds. +fn pct_ms(sorted_us: &[u128], p: f64) -> f64 { + if sorted_us.is_empty() { + return 0.0; + } + let idx = ((sorted_us.len() as f64 * p) as usize).min(sorted_us.len() - 1); + sorted_us[idx] as f64 / 1000.0 +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 8)] +#[ignore = "diagnostic benchmark; run explicitly with --ignored --nocapture"] +async fn kv_upsert_scaling() { + let _ = tracing_subscriber::fmt() + .with_env_filter("warn") + .with_test_writer() + .try_init(); + + let (db_config, docker_config) = TestDatabase::Postgres + .config(Uuid::new_v4(), 1) + .await + .unwrap(); + let mut docker_config = docker_config.unwrap(); + docker_config.start().await.unwrap(); + tokio::time::sleep(Duration::from_secs(4)).await; + + let rivet_config::config::Database::Postgres(postgres_config) = db_config else { + unreachable!(); + }; + let conn_str = postgres_config.url.read().clone(); + + let setup = connect(&conn_str).await; + setup + .batch_execute( + "CREATE TABLE IF NOT EXISTS kv (key BYTEA PRIMARY KEY, value BYTEA NOT NULL)", + ) + .await + .unwrap(); + + // Value size roughly matching a small UDB kv write. + const VALUE_LEN: usize = 64; + + // ===== Sweep 1: batch-size, single serial writer (mirrors the leader drain) ===== + println!("\n=== batch-size sweep (single serial writer, 1 txn/batch + COMMIT) ==="); + println!( + "{:>10} {:>10} {:>16} {:>16} {:>16}", + "batch_len", "batches", "batch_ms p50", "batch_ms p95", "per_row_ms p50" + ); + { + let mut client = connect(&conn_str).await; + let mut key_ctr: u64 = 0; + for &bs in &[1usize, 2, 4, 8, 16, 32, 64, 128, 256] { + // Aim for a similar number of total rows per size so timings are comparable. + let iters = (4096 / bs).max(64); + // Warm up so the first-statement plan/parse cost is not counted. + for _ in 0..3 { + let (keys, vals) = next_batch(&mut key_ctr, bs, VALUE_LEN); + upsert_batch(&mut client, &keys, &vals).await; + } + let mut batch_us = Vec::with_capacity(iters); + for _ in 0..iters { + let (keys, vals) = next_batch(&mut key_ctr, bs, VALUE_LEN); + let t = Instant::now(); + upsert_batch(&mut client, &keys, &vals).await; + batch_us.push(t.elapsed().as_micros()); + } + batch_us.sort_unstable(); + let p50 = pct_ms(&batch_us, 0.5); + println!( + "{:>10} {:>10} {:>16.3} {:>16.3} {:>16.4}", + bs, + iters, + p50, + pct_ms(&batch_us, 0.95), + p50 / bs as f64 + ); + } + } + + // ===== Sweep 2: concurrency, N independent single-row writers ===== + println!("\n=== concurrency sweep (N parallel writers, single-row txn each, 3s) ==="); + println!( + "{:>6} {:>14} {:>14} {:>14}", + "N", "ops/s", "op_ms p50", "op_ms p95" + ); + for &n in &[1usize, 2, 4, 8, 16, 32, 64] { + let run = Duration::from_secs(3); + let mut handles = Vec::with_capacity(n); + for w in 0..n { + let cs = conn_str.clone(); + handles.push(tokio::spawn(async move { + let mut client = connect(&cs).await; + // Disjoint key space per worker so writers do not contend on the same row lock. + let mut key: u64 = (w as u64) << 40; + let mut lat_us = Vec::new(); + let deadline = Instant::now() + run; + while Instant::now() < deadline { + key += 1; + let k = key.to_be_bytes().to_vec(); + let v = vec![0u8; VALUE_LEN]; + let t = Instant::now(); + let txn = client.transaction().await.unwrap(); + txn.execute( + "INSERT INTO kv (key, value) VALUES ($1, $2) + ON CONFLICT (key) DO UPDATE SET value = excluded.value", + &[&k, &v], + ) + .await + .unwrap(); + txn.commit().await.unwrap(); + lat_us.push(t.elapsed().as_micros()); + } + lat_us + })); + } + let mut all = Vec::new(); + for h in handles { + all.extend(h.await.unwrap()); + } + all.sort_unstable(); + println!( + "{:>6} {:>14.0} {:>14.3} {:>14.3}", + n, + all.len() as f64 / 3.0, + pct_ms(&all, 0.5), + pct_ms(&all, 0.95) + ); + } +} + +/// Build a batch of `bs` distinct keys and equal-length values, advancing the shared counter. +fn next_batch(key_ctr: &mut u64, bs: usize, value_len: usize) -> (Vec>, Vec>) { + let mut keys = Vec::with_capacity(bs); + let mut vals = Vec::with_capacity(bs); + for _ in 0..bs { + *key_ctr += 1; + keys.push(key_ctr.to_be_bytes().to_vec()); + vals.push(vec![0u8; value_len]); + } + (keys, vals) +} + +/// One batch upsert in its own transaction with a real COMMIT, matching the leader's apply shape. +async fn upsert_batch(client: &mut tokio_postgres::Client, keys: &[Vec], vals: &[Vec]) { + let txn = client.transaction().await.unwrap(); + txn.execute( + "INSERT INTO kv (key, value) SELECT * FROM unnest($1::bytea[], $2::bytea[]) + ON CONFLICT (key) DO UPDATE SET value = excluded.value", + &[&keys, &vals], + ) + .await + .unwrap(); + txn.commit().await.unwrap(); +} From c2f534e2e1eee36a1b82bb93470bca9924d4b7a3 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Mon, 29 Jun 2026 14:16:28 -0700 Subject: [PATCH 16/19] [slopfix] refactor(universaldb,ups): single/multi-node udb over nats, remove ups postgres driver --- Cargo.lock | 7 +- docs-internal/engine/TEST_DEPENDENCIES.md | 1 - engine/artifacts/config-schema.json | 37 +- .../packages/config/src/config/db/postgres.rs | 12 + engine/packages/config/src/config/mod.rs | 17 +- engine/packages/config/src/config/pubsub.rs | 38 - engine/packages/pools/src/db/udb.rs | 14 + engine/packages/pools/src/db/ups.rs | 25 - .../packages/test-deps-docker/src/database.rs | 1 + engine/packages/universaldb/Cargo.toml | 1 + .../universaldb/src/conflict_tracker.rs | 8 +- .../universaldb/src/driver/postgres/codec.rs | 59 +- .../universaldb/src/driver/postgres/commit.rs | 366 ++---- .../src/driver/postgres/database.rs | 114 +- .../src/driver/postgres/listener.rs | 228 ---- .../universaldb/src/driver/postgres/mod.rs | 4 +- .../universaldb/src/driver/postgres/nats.rs | 149 +++ .../src/driver/postgres/resolver/mod.rs | 734 ++++++------ .../universaldb/src/driver/postgres/shared.rs | 115 +- .../src/driver/postgres/transport.rs | 87 ++ .../src/driver/rocksdb/database.rs | 2 +- engine/packages/universaldb/tests/failover.rs | 56 +- engine/packages/universalpubsub/Cargo.toml | 6 - .../universalpubsub/benches/simple.rs | 126 +- .../universalpubsub/src/driver/mod.rs | 1 - .../src/driver/postgres/doorbell.rs | 133 --- .../src/driver/postgres/mod.rs | 1050 ----------------- .../packages/universalpubsub/src/metrics.rs | 7 - .../universalpubsub/tests/integration.rs | 71 +- .../universalpubsub/tests/reconnect.rs | 48 +- .../rust/universaldb-commit/src/versioned.rs | 62 + .../sdks/schemas/universaldb-commit/v1.bare | 38 +- .../compose/template/src/docker-compose.ts | 52 +- .../src/services/edge/rivet-engine.ts | 12 +- self-host/dev-host/docker-compose.yml | 1 + .../dc-a/rivet-engine/0/config.jsonc | 5 + .../dc-a/rivet-engine/1/config.jsonc | 5 + .../dc-a/rivet-engine/2/config.jsonc | 5 + .../dc-b/rivet-engine/0/config.jsonc | 5 + .../dc-b/rivet-engine/1/config.jsonc | 5 + .../dc-b/rivet-engine/2/config.jsonc | 5 + .../dc-c/rivet-engine/0/config.jsonc | 5 + .../dc-c/rivet-engine/1/config.jsonc | 5 + .../dc-c/rivet-engine/2/config.jsonc | 5 + .../dev-multidc-multinode/docker-compose.yml | 80 ++ self-host/dev-multidc/docker-compose.yml | 3 + self-host/dev-multinode/docker-compose.yml | 25 + .../dev-multinode/rivet-engine/0/config.jsonc | 5 + .../dev-multinode/rivet-engine/1/config.jsonc | 5 + .../dev-multinode/rivet-engine/2/config.jsonc | 5 + self-host/dev/docker-compose.yml | 1 + 51 files changed, 1325 insertions(+), 2526 deletions(-) delete mode 100644 engine/packages/universaldb/src/driver/postgres/listener.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/nats.rs create mode 100644 engine/packages/universaldb/src/driver/postgres/transport.rs delete mode 100644 engine/packages/universalpubsub/src/driver/postgres/doorbell.rs delete mode 100644 engine/packages/universalpubsub/src/driver/postgres/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7b0053c042..868daf76fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8273,6 +8273,7 @@ dependencies = [ "hex", "lazy_static", "rand 0.8.5", + "rivet-async-nats", "rivet-config", "rivet-env", "rivet-metrics", @@ -8303,8 +8304,6 @@ version = "2.3.7" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", - "deadpool-postgres", "futures-util", "lazy_static", "rand 0.8.5", @@ -8314,19 +8313,15 @@ dependencies = [ "rivet-error", "rivet-metrics", "rivet-perf", - "rivet-postgres-util", "rivet-test-deps-docker", "rivet-ups-protocol", "rivet-util", "scc", "serde", "serde_json", - "sha2", "tabled", "tempfile", "tokio", - "tokio-postgres", - "tokio-postgres-rustls", "tokio-util", "tracing", "tracing-subscriber", diff --git a/docs-internal/engine/TEST_DEPENDENCIES.md b/docs-internal/engine/TEST_DEPENDENCIES.md index 7eb03f0f53..e4a7e1807a 100644 --- a/docs-internal/engine/TEST_DEPENDENCIES.md +++ b/docs-internal/engine/TEST_DEPENDENCIES.md @@ -13,7 +13,6 @@ Configure backends via environment variables: - `RIVET_TEST_PUBSUB`: Choose pub/sub backend - `nats` - Runs NATS in Docker - - `postgres_notify` - PostgreSQL in Docker - `memory` - In-memory channels (default) - `RUST_LOG`: Enable debug logs to see container lifecycle details diff --git a/engine/artifacts/config-schema.json b/engine/artifacts/config-schema.json index af2af30077..9147b7a7d1 100644 --- a/engine/artifacts/config-schema.json +++ b/engine/artifacts/config-schema.json @@ -1244,47 +1244,18 @@ "url" ], "properties": { - "ssl": { - "description": "SSL configuration options", + "nats": { + "description": "NATS configuration for UniversalDB multi-node mode.\n\nWhen set, UniversalDB runs in multi-node mode and uses NATS for follower-to-leader commit transport instead of an in-process resolver. When absent, UniversalDB runs single-node. If unset but the UPS pubsub is configured for NATS, this is inherited from that config at startup (see `Root::validate_and_set_defaults`).", "default": null, "anyOf": [ { - "$ref": "#/definitions/PostgresSsl" + "$ref": "#/definitions/Nats" }, { "type": "null" } ] }, - "url": { - "description": "URL to connect to Postgres with\n\nSupports standard PostgreSQL connection parameters including `sslmode`. Supported sslmode values: `disable`, `prefer` (default), `require`. To verify server certificates, use `sslmode=require` with `ssl.root_cert_path`.\n\nExample with sslmode: `postgresql://user:pass@host:5432/db?sslmode=require`\n\nSee: https://docs.rs/postgres/0.19.10/postgres/config/struct.Config.html#url", - "allOf": [ - { - "$ref": "#/definitions/Secret" - } - ] - } - }, - "additionalProperties": false - }, - "Postgres2": { - "type": "object", - "required": [ - "url" - ], - "properties": { - "disable_memory_optimization": { - "description": "When true, force every UPS publish to round-trip through the postgres driver instead of taking the in-process fast path for subjects that have a local subscriber on the same engine pod. Opt-in diagnostic; default false.", - "default": false, - "type": "boolean" - }, - "memory_optimization": { - "deprecated": true, - "type": [ - "boolean", - "null" - ] - }, "ssl": { "description": "SSL configuration options", "default": null, @@ -1298,7 +1269,7 @@ ] }, "url": { - "description": "URL to connect to Postgres with\n\nSupports standard PostgreSQL connection parameters including `sslmode`. Supported sslmode values: `disable`, `prefer` (default), `require`. To verify server certificates, use `sslmode=require` with `ssl.root_cert_path`.\n\nSee: https://docs.rs/postgres/0.19.10/postgres/config/struct.Config.html#url", + "description": "URL to connect to Postgres with\n\nSupports standard PostgreSQL connection parameters including `sslmode`. Supported sslmode values: `disable`, `prefer` (default), `require`. To verify server certificates, use `sslmode=require` with `ssl.root_cert_path`.\n\nExample with sslmode: `postgresql://user:pass@host:5432/db?sslmode=require`\n\nSee: https://docs.rs/postgres/0.19.10/postgres/config/struct.Config.html#url", "allOf": [ { "$ref": "#/definitions/Secret" diff --git a/engine/packages/config/src/config/db/postgres.rs b/engine/packages/config/src/config/db/postgres.rs index f80af52f37..4cf8ea2684 100644 --- a/engine/packages/config/src/config/db/postgres.rs +++ b/engine/packages/config/src/config/db/postgres.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::secret::Secret; +use super::super::pubsub::Nats; + #[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] #[serde(deny_unknown_fields)] pub struct PostgresSsl { @@ -47,6 +49,15 @@ pub struct Postgres { /// SSL configuration options #[serde(default)] pub ssl: Option, + + /// NATS configuration for UniversalDB multi-node mode. + /// + /// When set, UniversalDB runs in multi-node mode and uses NATS for follower-to-leader commit + /// transport instead of an in-process resolver. When absent, UniversalDB runs single-node. + /// If unset but the UPS pubsub is configured for NATS, this is inherited from that config at + /// startup (see `Root::validate_and_set_defaults`). + #[serde(default)] + pub nats: Option, } impl Default for Postgres { @@ -54,6 +65,7 @@ impl Default for Postgres { Self { url: Secret::new("postgresql://postgres:postgres@127.0.0.1:5432/postgres".into()), ssl: None, + nats: None, } } } diff --git a/engine/packages/config/src/config/mod.rs b/engine/packages/config/src/config/mod.rs index caa8bf196e..5ee03c6d2a 100644 --- a/engine/packages/config/src/config/mod.rs +++ b/engine/packages/config/src/config/mod.rs @@ -185,17 +185,14 @@ impl Root { } pub fn validate_and_set_defaults(&mut self) -> Result<()> { - // Set default pubsub to Postgres if configured for database - if self.pubsub.is_none() - && let Some(Database::Postgres(pg)) = &self.database + // When UDB runs on Postgres without an explicit NATS config, inherit the UPS NATS config if + // one is set. The presence of NATS is what selects UDB multi-node mode, so this lets a + // single `pubsub: nats` config drive both UPS and UDB across nodes. + if let Some(PubSub::Nats(nats)) = self.pubsub.clone() + && let Some(Database::Postgres(pg)) = &mut self.database + && pg.nats.is_none() { - self.pubsub = Some(PubSub::PostgresNotify(pubsub::Postgres { - url: pg.url.clone(), - #[allow(deprecated)] - memory_optimization: None, - disable_memory_optimization: false, - ssl: pg.ssl.clone(), - })); + pg.nats = Some(nats); } self.pegboard().validate()?; diff --git a/engine/packages/config/src/config/pubsub.rs b/engine/packages/config/src/config/pubsub.rs index cae3526141..478275c675 100644 --- a/engine/packages/config/src/config/pubsub.rs +++ b/engine/packages/config/src/config/pubsub.rs @@ -3,13 +3,10 @@ use serde::{Deserialize, Serialize}; use crate::secret::Secret; -use super::db::PostgresSsl; - #[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] #[serde(rename_all = "snake_case", deny_unknown_fields)] pub enum PubSub { Nats(Nats), - PostgresNotify(Postgres), Memory(Memory), } @@ -19,41 +16,6 @@ impl Default for PubSub { } } -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct Postgres { - /// URL to connect to Postgres with - /// - /// Supports standard PostgreSQL connection parameters including `sslmode`. - /// Supported sslmode values: `disable`, `prefer` (default), `require`. - /// To verify server certificates, use `sslmode=require` with `ssl.root_cert_path`. - /// - /// See: https://docs.rs/postgres/0.19.10/postgres/config/struct.Config.html#url - pub url: Secret, - #[deprecated] - pub memory_optimization: Option, - /// When true, force every UPS publish to round-trip through the postgres driver instead of - /// taking the in-process fast path for subjects that have a local subscriber on the same - /// engine pod. Opt-in diagnostic; default false. - #[serde(default)] - pub disable_memory_optimization: bool, - /// SSL configuration options - #[serde(default)] - pub ssl: Option, -} - -impl Default for Postgres { - fn default() -> Self { - Self { - url: Secret::new("postgresql://postgres:postgres@127.0.0.1:5432/postgres".into()), - #[allow(deprecated)] - memory_optimization: None, - disable_memory_optimization: false, - ssl: None, - } - } -} - #[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] #[serde(deny_unknown_fields)] pub struct Nats { diff --git a/engine/packages/pools/src/db/udb.rs b/engine/packages/pools/src/db/udb.rs index cf3f40edc7..8049ae7019 100644 --- a/engine/packages/pools/src/db/udb.rs +++ b/engine/packages/pools/src/db/udb.rs @@ -20,6 +20,19 @@ impl Deref for UdbPool { pub async fn setup(config: &Config) -> Result> { let db_driver = match config.database() { config::Database::Postgres(pg) => { + // A NATS config (set directly or inherited from the UPS config in + // `validate_and_set_defaults`) selects UniversalDB multi-node mode. + let nats = pg + .nats + .as_ref() + .map(|nats| universaldb::driver::postgres::NatsConfig { + addresses: nats.addresses.clone(), + username: nats.username.clone(), + password: nats.password.as_ref().map(|p| p.read().clone()), + client_capacity: nats.client_capacity, + subscription_capacity: nats.subscription_capacity, + }); + let postgres_config = universaldb::driver::postgres::PostgresConfig { connection_string: pg.url.read().clone(), ssl_config: pg.ssl.as_ref().map(|ssl| { @@ -29,6 +42,7 @@ pub async fn setup(config: &Config) -> Result> { ssl_client_key_path: ssl.client_key_path.clone(), } }), + nats, }; Arc::new( diff --git a/engine/packages/pools/src/db/ups.rs b/engine/packages/pools/src/db/ups.rs index 9bf1423c18..6b63e5dc00 100644 --- a/engine/packages/pools/src/db/ups.rs +++ b/engine/packages/pools/src/db/ups.rs @@ -94,30 +94,6 @@ pub async fn setup(config: &Config, client_name: &str) -> Result { Arc::new(driver) as ups::PubSubDriverHandle } - config::PubSub::PostgresNotify(pg) => { - tracing::debug!("creating postgres pubsub driver"); - - let (ssl_root_cert_path, ssl_client_cert_path, ssl_client_key_path) = - if let Some(ssl) = &pg.ssl { - ( - ssl.root_cert_path.clone(), - ssl.client_cert_path.clone(), - ssl.client_key_path.clone(), - ) - } else { - (None, None, None) - }; - - Arc::new( - ups::driver::postgres::PostgresDriver::connect( - pg.url.read().clone(), - ssl_root_cert_path, - ssl_client_cert_path, - ssl_client_key_path, - ) - .await?, - ) as ups::PubSubDriverHandle - } config::PubSub::Memory(memory) => { tracing::debug!(channel=%memory.channel, "creating memory pubsub driver"); Arc::new(ups::driver::memory::MemoryDriver::new( @@ -128,7 +104,6 @@ pub async fn setup(config: &Config, client_name: &str) -> Result { let disable_memory_optimization = match config.pubsub() { config::PubSub::Nats(nats) => nats.disable_memory_optimization, - config::PubSub::PostgresNotify(pg) => pg.disable_memory_optimization, config::PubSub::Memory(memory) => memory.disable_memory_optimization, }; Ok(ups::PubSub::new_with_memory_optimization( diff --git a/engine/packages/test-deps-docker/src/database.rs b/engine/packages/test-deps-docker/src/database.rs index 955e4d7897..2ddd0ee916 100644 --- a/engine/packages/test-deps-docker/src/database.rs +++ b/engine/packages/test-deps-docker/src/database.rs @@ -58,6 +58,7 @@ impl TestDatabase { rivet_config::config::Database::Postgres(rivet_config::config::db::Postgres { url: rivet_config::secret::Secret::new(connection_string.clone()), ssl: None, + nats: None, }); let docker_config = DockerRunConfig { diff --git a/engine/packages/universaldb/Cargo.toml b/engine/packages/universaldb/Cargo.toml index 6781c7cde1..7f6b323e62 100644 --- a/engine/packages/universaldb/Cargo.toml +++ b/engine/packages/universaldb/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true +async-nats.workspace = true async-trait.workspace = true base64.workspace = true deadpool-postgres.workspace = true diff --git a/engine/packages/universaldb/src/conflict_tracker.rs b/engine/packages/universaldb/src/conflict_tracker.rs index 3e543a99ee..a257f14ecc 100644 --- a/engine/packages/universaldb/src/conflict_tracker.rs +++ b/engine/packages/universaldb/src/conflict_tracker.rs @@ -57,7 +57,7 @@ impl TransactionConflictTracker { self.global_version.fetch_add(1, Ordering::SeqCst) } - /// Returns `true` on conflict (same polarity as the original rocksdb tracker). The caller + /// Returns `true` on conflicts. The caller /// supplies `commit_version` (e.g. `nextval('udb_version_seq')` on the postgres leader, or /// `next_global_version()` on rocksdb) so version assignment stays the caller's responsibility. pub async fn check_and_insert( @@ -126,10 +126,4 @@ impl TransactionConflictTracker { let mut txns = self.txns.lock().await; txns.remove(&txn_commit_version); } - - /// Current retained transaction count. Diagnostic: the conflict scan in `check_and_insert` is - /// O(this) per commit, so a growing map directly inflates per-commit service time. - pub async fn len(&self) -> usize { - self.txns.lock().await.len() - } } diff --git a/engine/packages/universaldb/src/driver/postgres/codec.rs b/engine/packages/universaldb/src/driver/postgres/codec.rs index 432ede2f34..2e7b975ef0 100644 --- a/engine/packages/universaldb/src/driver/postgres/codec.rs +++ b/engine/packages/universaldb/src/driver/postgres/codec.rs @@ -7,13 +7,17 @@ use crate::{ tx_ops::Operation, }; -/// Decoded form of a `udb_commit_requests.payload` blob. -/// -/// `read_version` is intentionally omitted: it is also denormalized into the `read_version` column, -/// which is what the leader's drain reads, so decoding it here would be dead. +use super::transport::CommitOutcome; + +/// Decoded form of a commit request payload sent from a follower to the leader over NATS. pub struct DecodedCommit { + pub read_version: u64, pub conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, pub operations: Vec, + /// Submitting follower's node id (part of the failover dedup key). + pub client_node_id: Vec, + /// Per-process monotonic counter (part of the failover dedup key). + pub client_seq: u64, } /// Encode a follower's commit request to the versioned BARE wire format with an embedded version @@ -22,6 +26,8 @@ pub fn encode_commit_request( read_version: u64, conflict_ranges: &[(Vec, Vec, ConflictRangeType)], operations: &[Operation], + client_node_id: &[u8], + client_seq: u64, ) -> Result> { let request = proto::CommitRequest { read_version, @@ -34,13 +40,15 @@ pub fn encode_commit_request( }) .collect(), operations: operations.iter().map(operation_to_proto).collect(), + client_node_id: client_node_id.to_vec(), + client_seq, }; versioned::CommitRequest::wrap_latest(request) .serialize_with_embedded_version(proto::PROTOCOL_VERSION) } -/// Decode a `udb_commit_requests.payload` blob produced by [`encode_commit_request`]. +/// Decode a commit request payload produced by [`encode_commit_request`]. pub fn decode_commit_request(payload: &[u8]) -> Result { let request = versioned::CommitRequest::deserialize_with_embedded_version(payload)?; @@ -63,11 +71,52 @@ pub fn decode_commit_request(payload: &[u8]) -> Result { .collect(); Ok(DecodedCommit { + read_version: request.read_version, conflict_ranges, operations, + client_node_id: request.client_node_id, + client_seq: request.client_seq, + }) +} + +/// Encode a leader's commit reply to the versioned BARE wire format with an embedded version header +/// so a follower running older or newer code can still decode it during a rolling deploy. +pub fn encode_commit_reply(outcome: CommitOutcome) -> Result> { + let reply = match outcome { + CommitOutcome::Committed { commit_version } => { + proto::CommitReply::CommitCommitted(proto::CommitCommitted { commit_version }) + } + CommitOutcome::Conflict => proto::CommitReply::CommitConflict, + }; + + versioned::CommitReply::wrap_latest(reply) + .serialize_with_embedded_version(proto::PROTOCOL_VERSION) +} + +/// Decode a commit reply payload produced by [`encode_commit_reply`]. +pub fn decode_commit_reply(payload: &[u8]) -> Result { + let reply = versioned::CommitReply::deserialize_with_embedded_version(payload)?; + Ok(match reply { + proto::CommitReply::CommitCommitted(proto::CommitCommitted { commit_version }) => { + CommitOutcome::Committed { commit_version } + } + proto::CommitReply::CommitConflict => CommitOutcome::Conflict, }) } +/// Encode a durable-version watermark broadcast to the versioned BARE wire format with an embedded +/// version header. +pub fn encode_watermark(durable_version: i64) -> Result> { + versioned::Watermark::wrap_latest(proto::Watermark { durable_version }) + .serialize_with_embedded_version(proto::PROTOCOL_VERSION) +} + +/// Decode a watermark payload produced by [`encode_watermark`], returning the durable version. +pub fn decode_watermark(payload: &[u8]) -> Result { + let watermark = versioned::Watermark::deserialize_with_embedded_version(payload)?; + Ok(watermark.durable_version) +} + fn conflict_range_type_to_proto(kind: ConflictRangeType) -> proto::ConflictRangeType { match kind { ConflictRangeType::Read => proto::ConflictRangeType::Read, diff --git a/engine/packages/universaldb/src/driver/postgres/commit.rs b/engine/packages/universaldb/src/driver/postgres/commit.rs index d191c815a3..21d2335913 100644 --- a/engine/packages/universaldb/src/driver/postgres/commit.rs +++ b/engine/packages/universaldb/src/driver/postgres/commit.rs @@ -4,18 +4,28 @@ use std::{ }; use anyhow::{Context, Result}; +use tokio::sync::oneshot; use crate::{error::DatabaseError, options::ConflictRangeType, tx_ops::Operation}; use super::{ codec, - shared::{LeaseInfo, PostgresShared, commit_channel, reply_channel}, + shared::{LeaseInfo, PostgresShared}, + transport::{CommitJob, CommitOutcome, Responder, Transport}, }; /// How long to wait for a leader to be elected before giving up a submit as retryable. const LEADER_WAIT_TIMEOUT: Duration = Duration::from_secs(5); -/// Backstop poll cadence while waiting for a commit result, in case a reply NOTIFY is missed. -const RESULT_POLL_INTERVAL: Duration = Duration::from_millis(250); +/// Poll cadence while waiting for a leader to appear in the cache. +const LEADER_POLL_INTERVAL: Duration = Duration::from_millis(50); +/// Per-attempt timeout for a NATS commit request. +const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); +/// How many times a multi-node commit resends the same request (same dedup key) across leader +/// failover / indeterminate failures before giving up as retryable. The dedup table makes the +/// resends exactly-once. +const MAX_SUBMIT_ATTEMPTS: usize = 8; +/// Backoff between multi-node resends. +const RESEND_BACKOFF: Duration = Duration::from_millis(100); /// Submit a follower transaction's commit to the leader and await the result. /// @@ -27,8 +37,8 @@ pub async fn submit( operations: Vec, conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, ) -> Result<()> { - // A transaction with no writes and no serializable read ranges has nothing to order or - // validate; it never needs the leader. + // A transaction with no writes and no serializable read ranges has nothing to order or validate; + // it never needs the leader. if operations.is_empty() && conflict_ranges .iter() @@ -37,269 +47,133 @@ pub async fn submit( return Ok(()); } - let lease = wait_for_leader(shared).await?; - let payload = - codec::encode_commit_request(read_version.max(0) as u64, &conflict_ranges, &operations) - .context("failed to encode commit request")?; - let reply_channel = reply_channel(&shared.node_id); - - // Subscribe to our reply channel before inserting so we cannot miss the leader's NOTIFY. - let mut reply_rx = shared.listener.listen(&reply_channel).await; - - let conn = shared - .pool - .get() - .await - .context("failed to get connection for commit submit")?; - - // Enqueue the request and wake the leader's drain loop in one round-trip. The autocommit - // statement durably inserts the row and fires the NOTIFY together, so there is no separate - // notify round-trip or second pool acquire. - let id: i64 = conn - .query_one( - "WITH ins AS ( - INSERT INTO udb_commit_requests (epoch, read_version, payload, reply_channel) - VALUES ($1, $2, $3, $4) - RETURNING id - ) - SELECT pg_notify($5, id::text), id FROM ins", - &[ - &lease.epoch, - &read_version, - &payload, - &reply_channel, - &commit_channel(&lease.leader_addr), - ], - ) - .await - .context("failed to enqueue and notify commit request")? - .get(1); + match &shared.transport { + Transport::SingleNode { commit_tx } => { + submit_local(commit_tx, read_version, operations, conflict_ranges).await + } + Transport::MultiNode(_) => { + submit_nats(shared, read_version, operations, conflict_ranges).await + } + } +} - // Release the connection before waiting so a long wait does not pin a pool slot. The request - // row is durable, so await_result re-acquires a connection per poll. - drop(conn); +/// Single-node: hand the job straight to the in-process leader drain loop and await its result. +async fn submit_local( + commit_tx: &tokio::sync::mpsc::Sender, + read_version: i64, + operations: Vec, + conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, +) -> Result<()> { + let (response_tx, response_rx) = oneshot::channel(); + let job = CommitJob { + read_version: read_version.max(0) as u64, + conflict_ranges, + operations, + dedup_key: None, + responder: Responder::Local(response_tx), + }; - let submit_start = Instant::now(); - let result = await_result(shared, id, lease.epoch, &mut reply_rx).await; - tracing::debug!( - id, - epoch = lease.epoch, - wait_ms = submit_start.elapsed().as_millis() as u64, - ok = result.is_ok(), - "udb commit submit completed" - ); - result -} + if commit_tx.send(job).await.is_err() { + // The leader drain loop is gone (driver shutting down). Retryable. + return Err(DatabaseError::NotCommitted.into()); + } -/// Wait for a known leader, returning a retryable error if none is elected in time. -async fn wait_for_leader(shared: &Arc) -> Result { - let deadline = Instant::now() + LEADER_WAIT_TIMEOUT; - loop { - if let Some(lease) = shared.current_lease() { - return Ok(lease); - } - if Instant::now() >= deadline { - return Err(DatabaseError::NotCommitted.into()); - } - tokio::time::sleep(RESULT_POLL_INTERVAL).await; + match response_rx.await { + Ok(CommitOutcome::Committed { .. }) => Ok(()), + Ok(CommitOutcome::Conflict) => Err(DatabaseError::NotCommitted.into()), + // The leader dropped the job without responding; it was not applied. + Err(_) => Err(DatabaseError::NotCommitted.into()), } } -/// Wait for the commit result, resolved directly from the leader's reply NOTIFY payload on the happy -/// path. A polling `read_status` backstop covers a missed/lagged NOTIFY, and an epoch advance orphans -/// the request (it will never be applied, so it is definitively not committed). -async fn await_result( +/// Multi-node: send the commit to the elected leader over NATS request/reply, resending the same +/// request (same dedup key) across leader failover. The reply carries the commit result directly. +async fn submit_nats( shared: &Arc, - id: i64, - submit_epoch: i64, - reply_rx: &mut tokio::sync::broadcast::Receiver, + read_version: i64, + operations: Vec, + conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, ) -> Result<()> { - let start = Instant::now(); - // Diagnostics: count how the waiter is driven so we can tell whether the reply NOTIFY is doing - // its job or whether commits are riding the slow poll backstop / getting orphaned by failover. - let mut status_reads = 0u32; - let mut notify_wakes = 0u32; - let mut poll_wakes = 0u32; + let Transport::MultiNode(nats) = &shared.transport else { + unreachable!("submit_nats requires the multi-node transport"); + }; - // The backstop runs on a fixed-cadence interval, not a per-iteration sleep: under a flood of - // other commits' replies on this node's shared reply channel (which we skip past), a fresh - // per-iteration sleep would keep resetting and never fire, starving the backstop if our own - // NOTIFY was lost. An interval ticks on wall-clock cadence regardless of loop churn. - let mut poll_interval = tokio::time::interval(RESULT_POLL_INTERVAL); - poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - // Consume the immediate first tick so the first backstop is one interval out, after the reply - // has had a chance to arrive. - poll_interval.tick().await; + // One dedup key for this logical commit, reused across every resend so the leader applies it at + // most once even if an earlier attempt was applied but its reply was lost to a failover. + let client_seq = shared.next_commit_seq(); + let payload = codec::encode_commit_request( + read_version.max(0) as u64, + &conflict_ranges, + &operations, + shared.node_id.as_bytes(), + client_seq as u64, + ) + .context("failed to encode commit request")?; - loop { - // Wait for our reply NOTIFY, falling back to a status read on a poll tick or a lagged - // broadcast. The happy path resolves straight from the payload with no status SELECT. - tokio::select! { - res = reply_rx.recv() => { - match res { - Ok(payload) => { - notify_wakes += 1; - match parse_reply(&payload, id) { - Some(ReplyOutcome::Committed) => { - tracing::debug!( - id, - wait_ms = start.elapsed().as_millis() as u64, - status_reads, - notify_wakes, - poll_wakes, - "udb commit resolved: committed" - ); - return Ok(()); - } - Some(ReplyOutcome::Conflict) => { - tracing::debug!( - id, - wait_ms = start.elapsed().as_millis() as u64, - status_reads, - notify_wakes, - poll_wakes, - "udb commit resolved: conflict" - ); - return Err(DatabaseError::NotCommitted.into()); - } - // Reply for another waiter on the shared channel; keep waiting for ours. - None => continue, - } - } - Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { - // We may have missed our own reply; fall through to the status backstop. - notify_wakes += 1; - tracing::debug!(id, lagged = n, "udb reply broadcast lagged"); - } - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - tracing::warn!(id, "udb reply broadcast closed; re-subscribing"); - *reply_rx = shared - .listener - .listen(&reply_channel(&shared.node_id)) - .await; - continue; - } + let submit_start = Instant::now(); + for attempt in 0..MAX_SUBMIT_ATTEMPTS { + let lease = wait_for_leader(shared).await?; + let subject = nats.subjects.commit(&lease.leader_addr); + + let request = nats.client.request(subject, payload.clone().into()); + match tokio::time::timeout(REQUEST_TIMEOUT, request).await { + Ok(Ok(msg)) => match codec::decode_commit_reply(&msg.payload) { + Ok(CommitOutcome::Committed { .. }) => { + tracing::debug!( + client_seq, + attempt, + wait_ms = submit_start.elapsed().as_millis() as u64, + "udb commit resolved: committed" + ); + return Ok(()); } - } - _ = poll_interval.tick() => { - poll_wakes += 1; - } - } - - // Backstop path (poll tick or lagged broadcast): re-acquire a connection and read the durable - // status. A transient pool/query error just means we retry rather than failing a - // possibly-applied commit. - status_reads += 1; - match read_status(shared, id).await { - Ok(Some(Status::Committed)) => { + Ok(CommitOutcome::Conflict) => { + return Err(DatabaseError::NotCommitted.into()); + } + Err(err) => { + tracing::warn!(?err, client_seq, "malformed udb commit reply; resending"); + } + }, + // Indeterminate (no responder / transport error / timeout): the leader may have died + // before or after applying. Resend the same dedup key; the leader dedups any double apply. + Ok(Err(err)) => { tracing::debug!( - id, - wait_ms = start.elapsed().as_millis() as u64, - status_reads, - notify_wakes, - poll_wakes, - "udb commit resolved: committed (backstop)" + ?err, + client_seq, + attempt, + "udb commit request errored; resending" ); - return Ok(()); } - Ok(Some(Status::Conflict)) => { + Err(_) => { tracing::debug!( - id, - wait_ms = start.elapsed().as_millis() as u64, - status_reads, - notify_wakes, - poll_wakes, - "udb commit resolved: conflict (backstop)" + client_seq, + attempt, + "udb commit request timed out; resending" ); - return Err(DatabaseError::NotCommitted.into()); - } - Ok(Some(Status::Pending)) => {} - Ok(None) => { - // The row was GC'd before we observed a terminal status. Treat as not committed - // and let the retry loop resubmit. - tracing::warn!( - id, - wait_ms = start.elapsed().as_millis() as u64, - status_reads, - "udb commit row missing before terminal status (gc'd); treating as not committed" - ); - return Err(DatabaseError::NotCommitted.into()); - } - Err(err) => { - tracing::debug!(?err, id, "transient error polling commit status, retrying"); } } - // If a new leader took over, our old-epoch request will never be claimed. - if let Some(current) = shared.current_lease() { - if current.epoch != submit_epoch { - tracing::warn!( - id, - submit_epoch, - current_epoch = current.epoch, - wait_ms = start.elapsed().as_millis() as u64, - notify_wakes, - poll_wakes, - "udb commit orphaned by leader failover; treating as not committed" - ); - return Err(DatabaseError::NotCommitted.into()); - } - } + tokio::time::sleep(RESEND_BACKOFF).await; } -} -enum ReplyOutcome { - Committed, - Conflict, + tracing::warn!( + client_seq, + wait_ms = submit_start.elapsed().as_millis() as u64, + "udb commit exhausted resend attempts; treating as not committed" + ); + Err(DatabaseError::NotCommitted.into()) } -/// Parse a leader reply payload (`":committed:"` or `":conflict"`). Returns -/// `None` when the payload is for a different waiter on the shared reply channel or is unparseable. -fn parse_reply(payload: &str, id: i64) -> Option { - let mut parts = payload.split(':'); - let reply_id: i64 = parts.next()?.parse().ok()?; - if reply_id != id { - return None; - } - match parts.next()? { - "committed" => Some(ReplyOutcome::Committed), - "conflict" => Some(ReplyOutcome::Conflict), - _ => None, +/// Wait for a known leader, returning a retryable error if none is elected in time. +async fn wait_for_leader(shared: &Arc) -> Result { + let deadline = Instant::now() + LEADER_WAIT_TIMEOUT; + loop { + if let Some(lease) = shared.current_lease() { + return Ok(lease); + } + if Instant::now() >= deadline { + return Err(DatabaseError::NotCommitted.into()); + } + tokio::time::sleep(LEADER_POLL_INTERVAL).await; } } - -enum Status { - Pending, - Committed, - Conflict, -} - -/// Read the current status of a commit request. `Ok(None)` means the row no longer exists. -async fn read_status(shared: &Arc, id: i64) -> Result> { - let conn = shared - .pool - .get() - .await - .context("failed to get connection for commit status poll")?; - - let row = conn - .query_opt( - "SELECT status FROM udb_commit_requests WHERE id = $1", - &[&id], - ) - .await - .context("failed to read commit request status")?; - - let Some(row) = row else { - return Ok(None); - }; - - let status: String = row.get(0); - let status = match status.as_str() { - "committed" => Status::Committed, - "conflict" => Status::Conflict, - // 'pending' or any in-flight state. - _ => Status::Pending, - }; - Ok(Some(status)) -} diff --git a/engine/packages/universaldb/src/driver/postgres/database.rs b/engine/packages/universaldb/src/driver/postgres/database.rs index 3ff891011f..dc1c42d598 100644 --- a/engine/packages/universaldb/src/driver/postgres/database.rs +++ b/engine/packages/universaldb/src/driver/postgres/database.rs @@ -10,7 +10,7 @@ use std::{ use anyhow::{Context, Result}; use deadpool_postgres::{Config, ManagerConfig, Pool, PoolConfig, RecyclingMethod, Runtime}; use rivet_postgres_util::build_tls_config; -use tokio::task::JoinHandle; +use tokio::{sync::mpsc, task::JoinHandle}; use tokio_postgres_rustls::MakeRustlsConnect; use url::Url; use uuid::Uuid; @@ -24,19 +24,26 @@ use crate::{ }; use super::{ - listener::PgListener, resolver, shared::PostgresShared, transaction::PostgresTransactionDriver, + nats::{self, NatsConfig, NatsTransport, Subjects}, + resolver::{self, ResolverInput}, + shared::PostgresShared, + transaction::PostgresTransactionDriver, + transport::{COMMIT_QUEUE_BOUND, Transport}, }; const GC_INTERVAL: Duration = Duration::from_secs(30); -/// Terminal and orphaned commit-request rows older than this are garbage collected. Must be well -/// beyond the longest a follower could spend awaiting a result, so a result is never deleted before -/// it is observed. -const COMMIT_ROW_MAX_AGE_SECS: i64 = 60; +/// Failover dedup rows older than this are garbage collected. Must be well beyond the longest a +/// follower could spend resending a commit across a leader failover, so a dedup record is never +/// deleted while a resend that needs it could still arrive. +const DEDUP_ROW_MAX_AGE_SECS: i64 = 60; #[derive(Clone, Debug)] pub struct PostgresConfig { pub connection_string: String, pub ssl_config: Option, + /// When set, UniversalDB runs in multi-node mode and uses NATS for follower-to-leader commit + /// transport. When `None`, it runs single-node with an in-process resolver. + pub nats: Option, } #[derive(Clone, Debug)] @@ -47,11 +54,12 @@ pub struct PostgresSslConfig { } impl PostgresConfig { - /// Create a new PostgreSQL configuration with sane defaults + /// Create a new PostgreSQL configuration with sane defaults (single-node). pub fn new(connection_string: String) -> Self { Self { connection_string, ssl_config: None, + nats: None, } } } @@ -89,37 +97,50 @@ impl PostgresDatabaseDriver { Self::init_schema(&conn).await?; } - // Unique per-process node id (no hyphens) used to name this node's NOTIFY channels. Kept - // short so `udb_commit_` stays within Postgres's 63-byte identifier limit. + // Unique per-process node id (no hyphens). Names this node's NATS commit subject and is the + // dedup `client_node_id`. let node_id = Uuid::new_v4().simple().to_string(); - let listener = PgListener::new( - config.connection_string.clone(), - ssl_disabled, - config - .ssl_config - .as_ref() - .and_then(|c| c.ssl_root_cert_path.clone()), - config - .ssl_config - .as_ref() - .and_then(|c| c.ssl_client_cert_path.clone()), - config - .ssl_config - .as_ref() - .and_then(|c| c.ssl_client_key_path.clone()), - ); - - let shared = PostgresShared::new(pool, node_id, listener); - - // Every node runs the resolver; only the elected leader drains the commit queue. - let resolver_handle = resolver::spawn(shared.clone()); + let (shared, resolver_input) = match &config.nats { + None => { + // Single-node: the follower commit path hands jobs straight to the in-process leader + // drain loop. + let (commit_tx, commit_rx) = mpsc::channel(COMMIT_QUEUE_BOUND); + let shared = + PostgresShared::new(pool, node_id, Transport::SingleNode { commit_tx }); + + // Acquire the leader lease behind the correctness gate BEFORE spawning the resolver so + // a failure to acquire fails startup loudly. + let initial_epoch = resolver::acquire_single_node_gate(&shared).await?; + + ( + shared, + ResolverInput::SingleNode { + rx: commit_rx, + initial_epoch, + }, + ) + } + Some(nats_config) => { + // Multi-node: commits travel to the elected leader over NATS request/reply. + let client = nats::connect(nats_config).await?; + let subjects = Subjects::new(&config.connection_string); + let shared = PostgresShared::new( + pool, + node_id, + Transport::MultiNode(NatsTransport { client, subjects }), + ); + + (shared, ResolverInput::MultiNode) + } + }; + let resolver_handle = resolver::spawn(shared.clone(), resolver_input); let gc_handle = Self::spawn_gc(shared.clone()); Ok(PostgresDatabaseDriver { shared, - max_retries: AtomicI32::new(100), + max_retries: AtomicI32::new(10), resolver_handle, gc_handle, }) @@ -180,19 +201,13 @@ impl PostgresDatabaseDriver { CREATE SEQUENCE IF NOT EXISTS udb_version_seq AS BIGINT START WITH 1 INCREMENT BY 1 MINVALUE 1; - CREATE TABLE IF NOT EXISTS udb_commit_requests ( - id BIGSERIAL PRIMARY KEY, - epoch BIGINT NOT NULL, - read_version BIGINT NOT NULL, - payload BYTEA NOT NULL, - reply_channel TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'pending', - commit_version BIGINT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() - ); - - CREATE INDEX IF NOT EXISTS udb_commit_requests_pending - ON udb_commit_requests (id) WHERE status = 'pending';", + CREATE TABLE IF NOT EXISTS udb_applied ( + client_node_id BYTEA NOT NULL, + client_seq BIGINT NOT NULL, + commit_version BIGINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + PRIMARY KEY (client_node_id, client_seq) + );", ) .await .context("failed to initialize postgres schema")?; @@ -200,6 +215,9 @@ impl PostgresDatabaseDriver { Ok(()) } + /// Garbage-collect old failover dedup records. A dedup row only needs to outlive the longest a + /// follower could spend resending a single commit across a leader failover, so terminal rows past + /// [`DEDUP_ROW_MAX_AGE_SECS`] are safe to drop. (Single-node never writes this table.) fn spawn_gc(shared: Arc) -> JoinHandle<()> { tokio::spawn(async move { let mut interval = tokio::time::interval(GC_INTERVAL); @@ -211,20 +229,20 @@ impl PostgresDatabaseDriver { let conn = match shared.pool.get().await { Ok(conn) => conn, Err(err) => { - tracing::debug!(?err, "failed to get connection for commit gc"); + tracing::debug!(?err, "failed to get connection for dedup gc"); continue; } }; if let Err(err) = conn .execute( - "DELETE FROM udb_commit_requests + "DELETE FROM udb_applied WHERE created_at < now() - ($1::bigint * interval '1 second')", - &[&COMMIT_ROW_MAX_AGE_SECS], + &[&DEDUP_ROW_MAX_AGE_SECS], ) .await { - tracing::error!(?err, "failed postgres commit-queue gc"); + tracing::error!(?err, "failed postgres dedup gc"); } } }) diff --git a/engine/packages/universaldb/src/driver/postgres/listener.rs b/engine/packages/universaldb/src/driver/postgres/listener.rs deleted file mode 100644 index e5a7919dfe..0000000000 --- a/engine/packages/universaldb/src/driver/postgres/listener.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; - -use futures_util::future::poll_fn; -use rivet_postgres_util::build_tls_config; -use scc::HashMap; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - sync::{Mutex, broadcast}, -}; -use tokio_postgres::AsyncMessage; -use tokio_postgres_rustls::MakeRustlsConnect; - -/// How long to wait between reconnect attempts for the dedicated LISTEN connection. -const RECONNECT_BACKOFF: Duration = Duration::from_secs(1); -/// Capacity of each channel's broadcast buffer. Notifications are wakeup signals with a polling -/// backstop, so a lagged receiver only delays a wake, never drops a durable commit. -const BROADCAST_CAPACITY: usize = 1024; - -struct Subscription { - tx: broadcast::Sender, -} - -/// Owns a single dedicated Postgres connection used exclusively for `LISTEN`. Demultiplexes -/// incoming `NOTIFY` payloads to per-channel broadcast senders and re-`LISTEN`s every registered -/// channel after a reconnect. -/// -/// This is separate from the deadpool pool because deadpool recycles connections and drops the -/// async notification stream; LISTEN requires owning the connection's message stream directly. -pub struct PgListener { - conn_str: String, - ssl_disabled: bool, - ssl_root_cert_path: Option, - ssl_client_cert_path: Option, - ssl_client_key_path: Option, - channels: Arc>, - client: Arc>>, -} - -impl PgListener { - pub fn new( - conn_str: String, - ssl_disabled: bool, - ssl_root_cert_path: Option, - ssl_client_cert_path: Option, - ssl_client_key_path: Option, - ) -> Self { - let channels: Arc> = Arc::new(HashMap::new()); - let client: Arc>> = Arc::new(Mutex::new(None)); - - tokio::spawn(Self::connection_lifecycle( - conn_str.clone(), - ssl_disabled, - ssl_root_cert_path.clone(), - ssl_client_cert_path.clone(), - ssl_client_key_path.clone(), - channels.clone(), - client.clone(), - )); - - Self { - conn_str, - ssl_disabled, - ssl_root_cert_path, - ssl_client_cert_path, - ssl_client_key_path, - channels, - client, - } - } - - /// Subscribe to a channel, registering a `LISTEN` if this is the first subscriber. Returns a - /// broadcast receiver of notification payloads. Idempotent per channel. - pub async fn listen(&self, channel: &str) -> broadcast::Receiver { - match self.channels.entry_async(channel.to_string()).await { - scc::hash_map::Entry::Occupied(entry) => entry.get().tx.subscribe(), - scc::hash_map::Entry::Vacant(entry) => { - let (tx, rx) = broadcast::channel(BROADCAST_CAPACITY); - entry.insert_entry(Subscription { tx }); - - // Best-effort immediate LISTEN; the lifecycle task re-LISTENs on reconnect. - if let Some(client) = &*self.client.lock().await { - if let Err(err) = client.execute(&format!("LISTEN \"{channel}\""), &[]).await { - tracing::warn!(?err, %channel, "failed to LISTEN, will retry on reconnect"); - } - } - - rx - } - } - } - - async fn connection_lifecycle( - conn_str: String, - ssl_disabled: bool, - ssl_root_cert_path: Option, - ssl_client_cert_path: Option, - ssl_client_key_path: Option, - channels: Arc>, - client: Arc>>, - ) { - loop { - let connected = if ssl_disabled { - Self::connect_and_run(&conn_str, tokio_postgres::NoTls, &channels, &client).await - } else { - match build_tls_config( - ssl_root_cert_path.as_ref(), - ssl_client_cert_path.as_ref(), - ssl_client_key_path.as_ref(), - ) { - Ok(tls_config) => { - Self::connect_and_run( - &conn_str, - MakeRustlsConnect::new(tls_config), - &channels, - &client, - ) - .await - } - Err(err) => { - tracing::error!(?err, "failed to build listener TLS config"); - false - } - } - }; - - if !connected { - tokio::time::sleep(RECONNECT_BACKOFF).await; - } - } - } - - /// Connects, re-LISTENs all channels, then drives the notification poll loop until the - /// connection closes. Returns `true` if a connection was successfully established (so the caller - /// can skip the reconnect backoff). - async fn connect_and_run( - conn_str: &str, - tls: T, - channels: &Arc>, - client: &Arc>>, - ) -> bool - where - T: tokio_postgres::tls::MakeTlsConnect, - T::Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static, - T::TlsConnect: Send, - >::Future: Send, - { - let (new_client, connection) = match tokio_postgres::connect(conn_str, tls).await { - Ok(pair) => pair, - Err(err) => { - tracing::error!(?err, "failed to connect postgres listener"); - return false; - } - }; - - let channels_poll = channels.clone(); - let poll_handle = - tokio::spawn(async move { Self::poll_connection(connection, channels_poll).await }); - - // Re-LISTEN all registered channels on the fresh connection. - let mut registered = Vec::new(); - channels - .iter_async(|k, _| { - registered.push(k.clone()); - true - }) - .await; - for channel in ®istered { - if let Err(err) = new_client - .execute(&format!("LISTEN \"{channel}\""), &[]) - .await - { - tracing::error!(?err, %channel, "failed to re-LISTEN channel after reconnect"); - } - } - - *client.lock().await = Some(new_client); - - // Block until the poll loop ends (connection closed or errored). - let _ = poll_handle.await; - - *client.lock().await = None; - - true - } - - async fn poll_connection( - mut connection: tokio_postgres::Connection, - channels: Arc>, - ) where - S: AsyncRead + AsyncWrite + Unpin, - T: AsyncRead + AsyncWrite + Unpin, - { - loop { - match poll_fn(|cx| connection.poll_message(cx)).await { - Some(Ok(AsyncMessage::Notification(note))) => { - if let Some(sub) = channels.get_async(note.channel()).await { - // Ignore send errors: no active receiver just means no one is waiting - // right now; the polling backstop covers them. - let _ = sub.tx.send(note.payload().to_string()); - } - } - Some(Ok(_)) => {} - Some(Err(err)) => { - tracing::warn!(?err, "postgres listener connection error"); - break; - } - None => { - tracing::warn!("postgres listener connection closed"); - break; - } - } - } - } -} - -impl Clone for PgListener { - fn clone(&self) -> Self { - Self { - conn_str: self.conn_str.clone(), - ssl_disabled: self.ssl_disabled, - ssl_root_cert_path: self.ssl_root_cert_path.clone(), - ssl_client_cert_path: self.ssl_client_cert_path.clone(), - ssl_client_key_path: self.ssl_client_key_path.clone(), - channels: self.channels.clone(), - client: self.client.clone(), - } - } -} diff --git a/engine/packages/universaldb/src/driver/postgres/mod.rs b/engine/packages/universaldb/src/driver/postgres/mod.rs index 64f4bbd1bf..20b80765c0 100644 --- a/engine/packages/universaldb/src/driver/postgres/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/mod.rs @@ -1,10 +1,12 @@ mod codec; mod commit; mod database; -mod listener; +mod nats; mod resolver; mod shared; mod transaction; mod transaction_task; +mod transport; pub use database::{PostgresConfig, PostgresDatabaseDriver, PostgresSslConfig}; +pub use nats::NatsConfig; diff --git a/engine/packages/universaldb/src/driver/postgres/nats.rs b/engine/packages/universaldb/src/driver/postgres/nats.rs new file mode 100644 index 0000000000..8fab78811a --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/nats.rs @@ -0,0 +1,149 @@ +use std::str::FromStr; + +use anyhow::{Context, Result}; +use futures_util::StreamExt; +use tokio::sync::mpsc; + +use super::{ + codec, + transport::{CommitJob, DedupKey, Responder}, +}; + +/// NATS connection settings for UniversalDB multi-node mode. Built from the resolved +/// `database.postgres.nats` config (which may be inherited from the UPS NATS config). +#[derive(Clone, Debug)] +pub struct NatsConfig { + /// `host:port` server addresses. + pub addresses: Vec, + pub username: Option, + pub password: Option, + pub client_capacity: usize, + pub subscription_capacity: usize, +} + +/// The multi-node transport handle: the NATS client plus the cluster-scoped subject names. +pub struct NatsTransport { + pub client: async_nats::Client, + pub subjects: Subjects, +} + +/// Cluster-scoped UniversalDB NATS subjects. The cluster prefix is derived from the Postgres +/// connection string so two separate clusters that happen to share one NATS deployment do not +/// cross-deliver watermark/election broadcasts or commits. +#[derive(Clone)] +pub struct Subjects { + prefix: String, +} + +impl Subjects { + pub fn new(connection_string: &str) -> Self { + Subjects { + prefix: format!("udb.{:016x}", fnv1a_64(connection_string.as_bytes())), + } + } + + /// Subject a follower sends a commit request to, and the elected leader subscribes to. Namespaced + /// by the leader's node id so only the current leader receives commits. + pub fn commit(&self, leader_id: &str) -> String { + format!("{}.commit.{leader_id}", self.prefix) + } + + /// Subject the leader publishes each watermark advance to; every node subscribes. + pub fn watermark(&self) -> String { + format!("{}.watermark", self.prefix) + } + + /// Subject a departing leader publishes to so standby candidates elect immediately. + pub fn election(&self) -> String { + format!("{}.election", self.prefix) + } +} + +/// Connect a NATS client for the UniversalDB multi-node transport. +pub async fn connect(config: &NatsConfig) -> Result { + let server_addrs = config + .addresses + .iter() + .map(|addr| format!("nats://{addr}")) + .map(|url| async_nats::ServerAddr::from_str(&url)) + .collect::, _>>() + .context("failed to parse udb nats addresses")?; + + let mut options = match (&config.username, &config.password) { + (Some(username), Some(password)) => { + async_nats::ConnectOptions::with_user_and_password(username.clone(), password.clone()) + } + _ => async_nats::ConnectOptions::new(), + }; + options = options + .client_capacity(config.client_capacity) + .subscription_capacity(config.subscription_capacity); + + options + .connect(&server_addrs[..]) + .await + .context("failed to connect udb nats client") +} + +/// Leader-side task: subscribe to this leader's commit subject, decode each request into a +/// [`CommitJob`], and forward it into the drain loop's job queue. Returns when the subscription ends +/// (client closed) or the drain loop's receiver is dropped (step-down). +pub async fn run_commit_subscriber( + client: async_nats::Client, + subject: String, + jobs_tx: mpsc::Sender, +) -> Result<()> { + let mut sub = client + .subscribe(subject.clone()) + .await + .with_context(|| format!("failed to subscribe to udb commit subject {subject}"))?; + + while let Some(msg) = sub.next().await { + let Some(reply) = msg.reply.clone() else { + tracing::warn!("udb commit request missing reply subject; dropping"); + continue; + }; + + let decoded = match codec::decode_commit_request(&msg.payload) { + Ok(decoded) => decoded, + Err(err) => { + tracing::warn!(?err, "failed to decode udb commit request; dropping"); + continue; + } + }; + + let job = CommitJob { + read_version: decoded.read_version, + conflict_ranges: decoded.conflict_ranges, + operations: decoded.operations, + dedup_key: Some(DedupKey { + client_node_id: decoded.client_node_id, + client_seq: decoded.client_seq as i64, + }), + responder: Responder::Nats { + client: client.clone(), + reply, + }, + }; + + // A full queue applies backpressure; a closed queue means the drain loop stepped down. + if jobs_tx.send(job).await.is_err() { + break; + } + } + + Ok(()) +} + +/// FNV-1a 64-bit hash. Deterministic across processes (unlike `DefaultHasher`), used only to derive a +/// stable cluster subject prefix. +fn fnv1a_64(bytes: &[u8]) -> u64 { + const OFFSET: u64 = 0xcbf2_9ce4_8422_2325; + const PRIME: u64 = 0x0000_0100_0000_01b3; + let mut hash = OFFSET; + for &b in bytes { + hash ^= b as u64; + hash = hash.wrapping_mul(PRIME); + } + hash +} diff --git a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs index 098f022663..3a779f838f 100644 --- a/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs +++ b/engine/packages/universaldb/src/driver/postgres/resolver/mod.rs @@ -7,171 +7,292 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::{Context, Result}; -use tokio::sync::broadcast; +use anyhow::{Context, Result, bail}; +use futures_util::StreamExt; +use tokio::sync::mpsc; use tokio_util::task::AbortOnDropHandle; use crate::{conflict_tracker::TransactionConflictTracker, transaction::TXN_TIMEOUT}; use lease::LEASE_TTL_SECS; -use super::shared::{ - ELECTION_CHANNEL, LEASE_ID, LeaseInfo, PostgresShared, WATERMARK_CHANNEL, commit_channel, +use super::{ + shared::{LEASE_ID, LeaseInfo, PostgresShared}, + transport::{CommitJob, CommitOutcome, Transport}, }; /// Max commits resolved+applied per batch (group commit). Amortizes the resolver, Postgres /// round-trips, and fsync across the batch. -const DRAIN_BATCH_SIZE: i64 = 256; +const DRAIN_BATCH_SIZE: usize = 256; /// How often a leader renews its lease. Must be comfortably under `LEASE_TTL_SECS`. const RENEW_INTERVAL: Duration = Duration::from_secs(3); -/// Backstop poll cadence so a missed `udb_commit` NOTIFY cannot stall the drain indefinitely. -const POLL_BACKSTOP: Duration = Duration::from_millis(50); - /// How long a candidate waits before retrying election when another node holds the lease. const ELECTION_RETRY: Duration = Duration::from_secs(2); -/// Spawn the per-process resolver task. Every node runs this; only the elected leader drains the -/// commit queue. The returned handle is aborted when the owning driver drops, which stops lease -/// renewal so the lease expires and another node can take over (node-death / failover path). -pub fn spawn(shared: Arc) -> tokio::task::JoinHandle<()> { - tokio::spawn(run(shared)) +/// Single-node leader-acquire gate: total time to keep retrying before giving up and failing +/// startup. Must exceed the lease TTL so a crashed predecessor's lease has time to expire. +const GATE_TOTAL: Duration = Duration::from_secs((LEASE_TTL_SECS as u64) * 2 + 5); +/// Backoff between single-node gate attempts. +const GATE_RETRY: Duration = Duration::from_secs(1); + +/// What feeds the leader drain loop. Single-node owns the process-wide commit receiver and an +/// already-acquired lease epoch (the startup gate ran before this task spawned). Multi-node creates a +/// fresh NATS-fed receiver each time it wins an election. +pub enum ResolverInput { + SingleNode { + rx: mpsc::Receiver, + initial_epoch: i64, + }, + MultiNode, } -async fn run(shared: Arc) { - // A departing leader NOTIFYs this channel after releasing its lease so we elect immediately - // rather than waiting out the full `ELECTION_RETRY` tick. - let mut election_rx = shared.listener.listen(ELECTION_CHANNEL).await; +/// Single-node startup gate: acquire the leader lease, retrying with backoff. Fails if it cannot be +/// acquired within [`GATE_TOTAL`], which means either another engine instance is running against this +/// Postgres (a real misconfiguration in single-node mode) or a previous instance crashed without +/// releasing its lease and it has not yet expired. Each failed attempt warns. +pub async fn acquire_single_node_gate(shared: &Arc) -> Result { + let deadline = Instant::now() + GATE_TOTAL; + let mut attempt = 0u32; + loop { + attempt += 1; + match lease::try_acquire(&shared.pool, &shared.node_id).await { + Ok(Some(acquired)) => { + tracing::debug!( + epoch = acquired.epoch, + attempt, + node_id = %shared.node_id, + "acquired udb postgres single-node leader lease" + ); + return Ok(acquired.epoch); + } + Ok(None) => { + tracing::warn!( + attempt, + "udb postgres single-node could not acquire leader lease. another instance may hold it \ + or a previous instance did not release it; backing off" + ); + } + Err(err) => { + tracing::warn!( + ?err, + attempt, + "udb postgres single-node lease acquire errored, retrying" + ); + } + } + + if Instant::now() >= deadline { + bail!( + "udb postgres single-node failed to acquire leader lease after {attempt} attempts; refusing \ + to start. another engine instance may be running against this postgres, or a \ + previous instance crashed without releasing its lease (wait for it to expire). if you intend \ + to run a multi-node setup you must configure NATS." + ); + } + + tokio::time::sleep(GATE_RETRY).await; + } +} + +/// Spawn the per-process resolver task. The returned handle is aborted when the owning driver drops, +/// which stops lease renewal so the lease expires and another node can take over. +pub fn spawn(shared: Arc, input: ResolverInput) -> tokio::task::JoinHandle<()> { + tokio::spawn(run(shared, input)) +} + +async fn run(shared: Arc, input: ResolverInput) { + match input { + ResolverInput::SingleNode { rx, initial_epoch } => { + run_single_node(shared, rx, initial_epoch).await + } + ResolverInput::MultiNode => run_multi_node(shared).await, + } +} + +/// Single-node: this node is the only node and is always the leader. The startup gate already +/// acquired the lease, so lead immediately. If the lease is ever lost (another node appeared, a real +/// misconfiguration), log loudly and re-acquire through the gate. +async fn run_single_node( + shared: Arc, + mut rx: mpsc::Receiver, + initial_epoch: i64, +) { + let mut epoch = initial_epoch; + loop { + if let Err(err) = lead(&shared, epoch, &mut rx).await { + tracing::error!(?err, "udb postgres single-node leader loop errored"); + } + tracing::error!( + epoch, + "udb postgres single-node lost the leader lease; re-acquiring (another engine instance may be \ + running against this postgres)" + ); + epoch = loop { + match acquire_single_node_gate(&shared).await { + Ok(epoch) => break epoch, + Err(err) => { + tracing::error!(?err, "udb postgres single-node re-acquire failed; retrying"); + } + } + }; + } +} +/// Multi-node: race the lease against other nodes; whoever wins leads until it loses the lease. +async fn run_multi_node(shared: Arc) { loop { match lease::try_acquire(&shared.pool, &shared.node_id).await { Ok(Some(acquired)) => { - tracing::info!(epoch = acquired.epoch, node_id = %shared.node_id, "acquired udb leader lease"); - if let Err(err) = lead(&shared, acquired.epoch).await { + tracing::info!(epoch = acquired.epoch, node_id = %shared.node_id, "acquired udb postgres leader lease"); + + // Each leadership term gets its own NATS-fed commit queue. The subscriber forwards + // decoded commit requests into `rx`; aborting it on step-down stops accepting commits. + let (tx, mut rx) = mpsc::channel(super::transport::COMMIT_QUEUE_BOUND); + let subscriber = spawn_commit_subscriber(&shared, tx); + + if let Err(err) = lead(&shared, acquired.epoch, &mut rx).await { tracing::error!(?err, "udb leader loop errored, stepping down"); } - tracing::info!(epoch = acquired.epoch, "stepped down from udb leader"); - } - Ok(None) => { - wait_for_election_retry(&shared, &mut election_rx).await; + if let Some(handle) = subscriber { + handle.abort(); + } + tracing::info!( + epoch = acquired.epoch, + "stepped down from udb postgres leader" + ); } + Ok(None) => wait_for_election_retry(&shared).await, Err(err) => { tracing::warn!(?err, "failed udb lease acquire attempt"); - wait_for_election_retry(&shared, &mut election_rx).await; + wait_for_election_retry(&shared).await; } } } } -/// Wait before retrying the election: either the `ELECTION_RETRY` backstop elapses, or a departing -/// leader wakes us via `ELECTION_CHANNEL` so handoff is near-instant. -async fn wait_for_election_retry( +/// Spawn the leader's NATS commit subscriber for multi-node. Returns `None` in single-node (no NATS). +fn spawn_commit_subscriber( shared: &Arc, - election_rx: &mut broadcast::Receiver, -) { - tokio::select! { - _ = tokio::time::sleep(ELECTION_RETRY) => {} - res = election_rx.recv() => { - if matches!(res, Err(broadcast::error::RecvError::Closed)) { - // The listener recreates the channel on reconnect; re-subscribe. - *election_rx = shared.listener.listen(ELECTION_CHANNEL).await; + tx: mpsc::Sender, +) -> Option> { + let Transport::MultiNode(nats) = &shared.transport else { + return None; + }; + let client = nats.client.clone(); + let subject = nats.subjects.commit(&shared.node_id); + Some(AbortOnDropHandle::new(tokio::spawn(async move { + if let Err(err) = super::nats::run_commit_subscriber(client, subject, tx).await { + tracing::warn!(?err, "udb commit subscriber ended"); + } + }))) +} + +/// Wait before retrying the election: either the `ELECTION_RETRY` backstop elapses, or a departing +/// leader wakes us via the election broadcast so handoff is near-instant. +async fn wait_for_election_retry(shared: &Arc) { + let Transport::MultiNode(nats) = &shared.transport else { + tokio::time::sleep(ELECTION_RETRY).await; + return; + }; + + let election = nats.client.subscribe(nats.subjects.election()).await; + match election { + Ok(mut sub) => { + tokio::select! { + _ = tokio::time::sleep(ELECTION_RETRY) => {} + _ = sub.next() => {} } } + Err(_) => tokio::time::sleep(ELECTION_RETRY).await, } } -/// Best-effort graceful leadership handoff invoked on shutdown. If this node currently holds the -/// lease, expire it and wake a standby so it takes over immediately instead of waiting out the TTL. -/// Safe to call on a follower: the fenced release matches no row and nothing is notified. The -/// caller must already have stopped lease renewal before calling this. +/// Best-effort graceful leadership handoff invoked on shutdown. If this node holds the lease, expire +/// it and wake standbys so they take over immediately instead of waiting out the TTL. Safe to call on +/// a follower. The caller must already have stopped lease renewal before calling this. pub async fn handoff(shared: &Arc) { match lease::release(&shared.pool, &shared.node_id).await { Ok(true) => { - tracing::info!(node_id = %shared.node_id, "released udb leader lease for graceful handoff"); - notify_election(shared).await; + tracing::info!(node_id = %shared.node_id, "released udb postgres leader lease for graceful handoff"); + if let Transport::MultiNode(nats) = &shared.transport { + if let Err(err) = nats + .client + .publish(nats.subjects.election(), Vec::new().into()) + .await + { + tracing::debug!(?err, "failed to publish udb election wake"); + } + } } Ok(false) => {} - Err(err) => { - tracing::warn!(?err, "failed to release udb lease on shutdown"); - } - } -} - -/// Wake standby candidates so the next election fires immediately after a graceful release. -async fn notify_election(shared: &Arc) { - let conn = match shared.pool.get().await { - Ok(conn) => conn, - Err(err) => { - tracing::debug!(?err, "failed to get connection for election notify"); - return; - } - }; - - if let Err(err) = conn - .execute("SELECT pg_notify($1, '')", &[&ELECTION_CHANNEL]) - .await - { - tracing::debug!(?err, "failed to notify election channel"); + Err(err) => tracing::warn!(?err, "failed to release udb lease on shutdown"), } } /// Leader entry point: publish our lease, compute the cold-window floor, then run renewal and -/// draining as two sibling tasks. They coordinate purely by completion: when either returns (lease -/// lost or error), the other is aborted and the leader steps down. Both operations are safe to -/// hard-abort, so no explicit cancellation signalling is needed. A renew is a single fenced -/// `UPDATE`; a drain batch runs in one Postgres transaction that rolls back cleanly when dropped, -/// leaving its claimed requests `pending` for the next leader. -async fn lead(shared: &Arc, epoch: i64) -> Result<()> { +/// draining. Renewal runs on its own task so a long drain cannot starve it past the lease TTL; the +/// drain loop runs inline so it can borrow the commit receiver. Returns when the lease is lost (renew +/// reports it gone, or an apply is epoch-fenced). +async fn lead( + shared: &Arc, + epoch: i64, + rx: &mut mpsc::Receiver, +) -> Result<()> { // Publish our own lease into the cache immediately so our local commits route to us. shared.set_lease(LeaseInfo { epoch, leader_addr: shared.node_id.clone(), }); - // The recovery floor: a freshly elected leader has a cold conflict window, so reject commits - // whose read_version predates the floor until the window warms (one TXN_TIMEOUT), forcing - // those followers to take a fresh read_version. + // The recovery floor: a freshly elected leader has a cold conflict window, so reject commits whose + // read_version predates the floor until the window warms (one TXN_TIMEOUT), forcing those + // followers to take a fresh read_version. let recovery_version = recovery_floor(shared).await?; let recovery_deadline = Instant::now() + TXN_TIMEOUT; - tracing::info!( + // Seed our read-version cache to the durable floor so our own follower reads are not cold-window + // rejected (a read at `read_version < recovery_version` is rejected during the cold window, and the + // cache otherwise starts at 0). Followers learn the floor from the watermark broadcast / lease poll. + shared.advance_durable_version(recovery_version as i64); + + tracing::debug!( epoch, recovery_version, cold_window_ms = TXN_TIMEOUT.as_millis() as u64, + multi_node = shared.is_multi_node(), "udb leader entering lead loop" ); - // Renewal runs in its own task so the drain loop can never starve it: if renewal shared the - // drain loop, a single long drain under sustained load would block renewal past the lease TTL - // and the lease would be lost mid-drain, thrashing leadership. Both are held in abort-on-drop - // handles so a hard abort of this `run` task (driver drop / node death) tears them down. A leaked - // renew task would keep this dead leader's lease alive and block failover. + let tracker = TransactionConflictTracker::new(); let mut renew = AbortOnDropHandle::new(tokio::spawn(renew_loop(shared.clone(), epoch))); - let mut drain = AbortOnDropHandle::new(tokio::spawn(drain_loop( - shared.clone(), + + let drain = drain_loop( + shared, epoch, + &tracker, recovery_version, recovery_deadline, - ))); + rx, + ); + tokio::pin!(drain); - // Whichever task returns first (lease lost or error), step down; the other is aborted when its - // handle drops at the end of this scope. A clean exit yields its inner result; a panic surfaces - // through `?` as a join error. + // Whichever finishes first (lease lost via renew, or epoch fenced during a drain apply) ends the + // leadership term. The renew handle yields a join result; the inline drain yields directly. tokio::select! { res = &mut renew => res?, - res = &mut drain => res?, + res = &mut drain => res, } } /// Lease-renewal loop. Runs on its own task and pool connection so it cannot be starved by drain -/// work. Returns when the lease is definitively gone (epoch bumped by another node, or renewal -/// failing for the whole lease TTL), which causes [`lead`] to abort the drain task and step down. +/// work. Returns when the lease is definitively gone. async fn renew_loop(shared: Arc, epoch: i64) -> Result<()> { let mut interval = tokio::time::interval(RENEW_INTERVAL); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - // The lease was just acquired/renewed (expires_at = now + TTL), so consume the immediate first - // tick and renew after one interval. + // The lease was just acquired (expires_at = now + TTL), so consume the immediate first tick and + // renew after one interval. interval.tick().await; let mut last_renew = Instant::now(); @@ -179,42 +300,16 @@ async fn renew_loop(shared: Arc, epoch: i64) -> Result<()> { loop { interval.tick().await; - let gap_ms = last_renew.elapsed().as_millis() as u64; - let renew_start = Instant::now(); match lease::renew(&shared.pool, &shared.node_id, epoch).await { - Ok(true) => { - last_renew = Instant::now(); - let renew_query_ms = renew_start.elapsed().as_millis() as u64; - // With renewal on its own task this gap should track RENEW_INTERVAL closely; a large - // gap now points at pool or Postgres contention. - if gap_ms > RENEW_INTERVAL.as_millis() as u64 * 2 { - tracing::warn!( - epoch, - gap_since_last_renew_ms = gap_ms, - renew_query_ms, - "udb leader renew was delayed (pool or postgres contention)" - ); - } else { - tracing::debug!( - epoch, - gap_since_last_renew_ms = gap_ms, - renew_query_ms, - "udb leader renewed lease" - ); - } - } + Ok(true) => last_renew = Instant::now(), Ok(false) => { tracing::warn!( epoch, - gap_since_last_renew_ms = gap_ms, - "udb leader lost lease on renew (epoch bumped by another node); stepping down" + "udb leader lost lease on renew (epoch bumped); stepping down" ); return Ok(()); } Err(err) => { - // A transient renew error is tolerable within the TTL; keep retrying. Only give up - // if we have been unable to renew for the whole lease TTL, at which point we can no - // longer assume we hold the lease. if last_renew.elapsed() >= Duration::from_secs(LEASE_TTL_SECS as u64) { tracing::warn!( ?err, @@ -229,117 +324,76 @@ async fn renew_loop(shared: Arc, epoch: i64) -> Result<()> { } } -/// Drain loop. Processes one batch per iteration. Draining until the queue emptied in a single call -/// could run for many seconds under sustained load; processing one batch at a time keeps the loop -/// at a clean await point between batches. A non-empty queue still drains back-to-back with no idle -/// wait, so throughput is unchanged. It blocks on `select!` (wake NOTIFY or poll backstop) only -/// when the queue is empty. Returns when this leader's epoch is fenced out; otherwise [`lead`] -/// aborts it when renewal reports the lease is lost. +/// Drain loop. Collects one batch of commit jobs from the queue and processes it per iteration. +/// Returns when this leader's epoch is fenced out during an apply, or the queue closes. async fn drain_loop( - shared: Arc, + shared: &Arc, epoch: i64, + tracker: &TransactionConflictTracker, recovery_version: u64, recovery_deadline: Instant, + rx: &mut mpsc::Receiver, ) -> Result<()> { - let tracker = TransactionConflictTracker::new(); - - let mut wake_rx = shared - .listener - .listen(&commit_channel(&shared.node_id)) - .await; - - let mut poll_interval = tokio::time::interval(POLL_BACKSTOP); - poll_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { + let batch = collect_batch(rx).await; + if batch.is_empty() { + // The queue closed (all senders dropped). Step down. + return Ok(()); + } + match drain_batch( - &shared, + shared, epoch, - &tracker, + tracker, recovery_version, recovery_deadline, + batch, ) .await? { BatchOutcome::LostLease => { - tracing::warn!( - epoch, - "udb leader stepping down: lost lease during drain (epoch fenced on watermark)" - ); + tracing::warn!(epoch, "udb leader stepping down: epoch fenced during apply"); return Ok(()); } - // More work may be pending; loop immediately to keep throughput up. BatchOutcome::Processed => continue, - BatchOutcome::Empty => { - tokio::select! { - res = wake_rx.recv() => { - match res { - Ok(_) | Err(broadcast::error::RecvError::Lagged(_)) => {} - Err(broadcast::error::RecvError::Closed) => { - wake_rx = shared - .listener - .listen(&commit_channel(&shared.node_id)) - .await; - } - } - } - _ = poll_interval.tick() => {} - } - } } } } -/// The cold-window rejection floor for a freshly elected leader: the durable watermark -/// (`udb_lease.durable_version`) at election time. -/// -/// Reasoning (do NOT change this back to `max(durable, seq_high)`): -/// -/// A new leader starts with an empty conflict tracker, so it cannot detect a read-write conflict -/// against any committed write it does not already know about. The writes it is missing are exactly -/// the previous leader's winners, and every winner's write is applied to `kv` AND its -/// `commit_version` folded into `durable_version` in the SAME apply transaction. So every missing -/// write has `commit_version <= durable_version`. A committing transaction `T` is therefore safe -/// iff `T.read_version >= durable_version`: every write above its read_version was committed by THIS -/// leader and is in the tracker. Only `T.read_version < durable_version` can race a missing winner, -/// so that is the exact set the cold window must reject. -/// -/// `udb_version_seq.last_value` (the sequence high-water) is NOT a valid floor. Every drained -/// request consumes a `nextval` BEFORE the conflict check, including conflicts and cold rejects, so -/// the sequence races far ahead of `durable_version` with versions that never produced any write. -/// Using `max(durable, seq_high)` rejects essentially every commit for the whole cold window -/// (followers read at `durable_version`, which is always `< seq_high`), turning each failover into -/// a 5s mass-reject storm. The gap `(durable_version, seq_high]` holds only thrown-away loser -/// versions, so nothing in it is a missing write to guard against. -/// -/// Version ASSIGNMENT is unaffected: commit versions still come from `nextval('udb_version_seq')` -/// in `drain_batch`, which is always above the sequence high-water, so uniqueness and monotonicity -/// across failover are preserved independently of this floor. +/// Collect up to [`DRAIN_BATCH_SIZE`] jobs from the queue, blocking for the first one and then +/// draining any immediately-available followers without waiting. Returns an empty batch only when the +/// queue is closed and drained. +async fn collect_batch(rx: &mut mpsc::Receiver) -> Vec { + let mut batch = Vec::with_capacity(DRAIN_BATCH_SIZE); + rx.recv_many(&mut batch, DRAIN_BATCH_SIZE).await; + batch +} + +/// The cold-window rejection floor for a freshly elected leader: the durable watermark at election +/// time. A new leader's tracker is empty, so it cannot detect a conflict against a previous leader's +/// winner; every such winner has `commit_version <= durable_version` (applied and folded into +/// `durable_version` in one txn), so a commit is safe if `read_version >= durable_version`. async fn recovery_floor(shared: &Arc) -> Result { let durable = lease::current_durable_version(&shared.pool).await?; Ok(durable.max(0) as u64) } enum BatchOutcome { - Empty, Processed, LostLease, } -struct Reply { - channel: String, - /// The follower's reply payload, encoding the outcome so the waiter resolves without a status - /// SELECT: `":committed:"` or `":conflict"`. - payload: String, -} - async fn drain_batch( shared: &Arc, epoch: i64, tracker: &TransactionConflictTracker, recovery_version: u64, recovery_deadline: Instant, + mut jobs: Vec, ) -> Result { + let batch_start = Instant::now(); + let batch_len = jobs.len(); + let mut conn = shared .pool .get() @@ -351,128 +405,132 @@ async fn drain_batch( .await .context("failed to start drain batch txn")?; - // Claim a batch in id order and allocate every commit version in the SAME round-trip via an - // inline `nextval`, instead of a separate version-allocation query. FOR UPDATE SKIP LOCKED holds - // the rows for this txn so they are stamped terminal on COMMIT with no intermediate 'claimed' - // state to clean up. - // - // Postgres does NOT guarantee `nextval` is evaluated in output (id) order, so the per-row `cv` - // here may not be monotonic in id. The versions are collected, sorted, and re-assigned to rows in - // id order below, exactly as the separate-query path did, because versionstamp monotonicity with - // commit order is load-bearing (epoxy changelog catch-up + depot PITR). - let rows = txn - .query( - "SELECT id, read_version, payload, reply_channel, - nextval('udb_version_seq') AS cv - FROM udb_commit_requests - WHERE status = 'pending' AND epoch = $1 - ORDER BY id - LIMIT $2 - FOR UPDATE SKIP LOCKED", - &[&epoch, &DRAIN_BATCH_SIZE], - ) - .await - .context("failed to claim commit batch")?; + // Build the failover dedup keys: a job whose (client_node_id, client_seq) is already recorded in + // udb_applied was committed by a prior leader; respond with the recorded version and do not + // re-apply. Single-node jobs carry no dedup key and never hit this path. + let mut dedup_nids: Vec> = Vec::new(); + let mut dedup_seqs: Vec = Vec::new(); + for job in &jobs { + if let Some(key) = &job.dedup_key { + dedup_nids.push(key.client_node_id.clone()); + dedup_seqs.push(key.client_seq); + } + } - if rows.is_empty() { - txn.rollback().await.ok(); - return Ok(BatchOutcome::Empty); + // Pipeline the dedup pre-check and commit-version allocation on the same connection. Versions are + // allocated for every job rather than only to-resolve jobs, so the version count no longer depends + // on the dedup result and the two queries have no data dependency; tokio-postgres pipelines them + // into a single round-trip. A dedup hit wastes its allocated version, but sequence gaps are already + // expected (conflict losers and rolled-back batches burn versions too) and do not affect + // versionstamp monotonicity. + let job_count = jobs.len() as i64; + let dedup_fut = async { + if dedup_nids.is_empty() { + return anyhow::Ok(HashMap::<(Vec, i64), i64>::new()); + } + let applied = txn + .query( + "SELECT a.client_node_id, a.client_seq, a.commit_version + FROM udb_applied a + JOIN unnest($1::bytea[], $2::bigint[]) AS q(nid, seq) + ON a.client_node_id = q.nid AND a.client_seq = q.seq", + &[&dedup_nids, &dedup_seqs], + ) + .await + .context("failed dedup pre-check")? + .into_iter() + .map(|row| { + ( + (row.get::<_, Vec>(0), row.get::<_, i64>(1)), + row.get::<_, i64>(2), + ) + }) + .collect(); + anyhow::Ok(applied) + }; + let versions_fut = async { + let versions = txn + .query( + "SELECT nextval('udb_version_seq') FROM generate_series(1, $1::bigint)", + &[&job_count], + ) + .await + .context("failed to allocate commit versions")? + .into_iter() + .map(|row| row.get::<_, i64>(0)) + .collect::>(); + anyhow::Ok(versions) + }; + let (applied, mut versions) = tokio::try_join!(dedup_fut, versions_fut)?; + + // Postgres does not guarantee nextval is evaluated in row order, so the versions are sorted and + // assigned to to-resolve jobs in arrival order to keep versionstamps monotonic with commit order + // (load-bearing for epoxy changelog catch-up + depot PITR). + versions.sort_unstable(); + + // Classify each job: a dedup hit resolves immediately; everything else needs version assignment + // and conflict resolution. + let mut outcomes: Vec> = vec![None; jobs.len()]; + let mut resolve_indices: Vec = Vec::with_capacity(jobs.len()); + for (i, job) in jobs.iter().enumerate() { + if let Some(key) = &job.dedup_key { + if let Some(&cv) = applied.get(&(key.client_node_id.clone(), key.client_seq)) { + outcomes[i] = Some(CommitOutcome::Committed { commit_version: cv }); + continue; + } + } + resolve_indices.push(i); } - let batch_start = Instant::now(); let cold_window = Instant::now() < recovery_deadline; - let batch_len = rows.len(); + let mut winners: Vec = Vec::new(); + let mut winner_dedup_nids: Vec> = Vec::new(); + let mut winner_dedup_seqs: Vec = Vec::new(); + let mut winner_dedup_cvs: Vec = Vec::new(); let mut max_winner_cv: i64 = 0; - let mut replies = Vec::with_capacity(batch_len); let mut committed_count = 0u32; let mut conflict_count = 0u32; let mut cold_reject_count = 0u32; - // Re-assign the inline-allocated versions to rows in id order (winners and losers alike; losers' - // versions are harmlessly skipped) so versionstamps stay monotonic with commit order. The sort - // keeps assignment monotonic regardless of how Postgres ordered the per-row `nextval` evaluation. - let mut versions: Vec = rows.iter().map(|row| row.get::<_, i64>(4)).collect(); - versions.sort_unstable(); + for (slot, &i) in resolve_indices.iter().enumerate() { + let commit_version = versions[slot]; + let job = &mut jobs[i]; + let start_version = job.read_version; + let conflict_ranges = std::mem::take(&mut job.conflict_ranges); - // Resolve every request in memory in id order. Winners are collected with their version and - // operations for the fold; the bulk status stamp is built for all rows at once. - let mut winners: Vec = Vec::new(); - let mut stamp_ids: Vec = Vec::with_capacity(batch_len); - let mut stamp_statuses: Vec<&str> = Vec::with_capacity(batch_len); - let mut stamp_versions: Vec> = Vec::with_capacity(batch_len); - - // Sub-phase timers to decompose batch_ms and confirm whether the service time is constant (pure - // M/M/1 queueing) or itself inflates under load. - let mut decode_dur = Duration::ZERO; - let mut conflict_dur = Duration::ZERO; - - for (i, row) in rows.iter().enumerate() { - let id: i64 = row.get(0); - let read_version: i64 = row.get(1); - let payload: Vec = row.get(2); - let reply_channel: String = row.get(3); - let commit_version = versions[i]; - - let decode_start = Instant::now(); - let decoded = super::codec::decode_commit_request(&payload) - .context("failed to decode commit payload")?; - decode_dur += decode_start.elapsed(); - - let start_version = read_version.max(0) as u64; - - // Cold-window guard: a commit whose read_version predates the recovery floor cannot be - // safely resolved against this leader's empty window. Reject it as retryable. let cold_rejected = cold_window && start_version < recovery_version; let conflicted = if cold_rejected { cold_reject_count += 1; true } else { - let conflict_start = Instant::now(); - let res = tracker - .check_and_insert( - start_version, - commit_version.max(0) as u64, - decoded.conflict_ranges, - ) - .await; - conflict_dur += conflict_start.elapsed(); - res + tracker + .check_and_insert(start_version, commit_version.max(0) as u64, conflict_ranges) + .await }; - stamp_ids.push(id); if conflicted { if !cold_rejected { conflict_count += 1; } - stamp_statuses.push("conflict"); - stamp_versions.push(None); + outcomes[i] = Some(CommitOutcome::Conflict); } else { committed_count += 1; - stamp_statuses.push("committed"); - stamp_versions.push(Some(commit_version)); + outcomes[i] = Some(CommitOutcome::Committed { commit_version }); max_winner_cv = max_winner_cv.max(commit_version); + if let Some(key) = &job.dedup_key { + winner_dedup_nids.push(key.client_node_id.clone()); + winner_dedup_seqs.push(key.client_seq); + winner_dedup_cvs.push(commit_version); + } winners.push(apply::Winner { commit_version: commit_version.max(0) as u64, - operations: decoded.operations, + operations: std::mem::take(&mut job.operations), }); } - - let reply_payload = if conflicted { - format!("{id}:conflict") - } else { - format!("{id}:committed:{commit_version}") - }; - replies.push(Reply { - channel: reply_channel, - payload: reply_payload, - }); } - let t_resolved = Instant::now(); - - // Bulk-read the pre-batch value of every key a winner's atomic op reads in one query, then fold - // all winners into a single materialized write-set in memory. This collapses the per-row apply - // round-trips to a fixed count independent of batch size. + // Bulk-read the pre-batch value of every key a winner's atomic op reads, then fold all winners + // into one materialized write-set in memory. let atomic_keys = apply::atomic_read_keys(&winners); let base = if atomic_keys.is_empty() { HashMap::new() @@ -488,26 +546,19 @@ async fn drain_batch( .collect() }; - let t_read = Instant::now(); - let apply::WriteSet { upserts, point_deletes, range_deletes, } = apply::fold_winners(winners, &base).context("failed to fold batch winners")?; - let t_fold = Instant::now(); - let (upsert_keys, upsert_values): (Vec>, Vec>) = upserts.into_iter().unzip(); let (range_begins, range_ends): (Vec>, Vec>) = range_deletes.into_iter().unzip(); - // Range deletes run in their OWN statement BEFORE the apply CTE. Postgres data-modifying CTE - // sub-statements all observe the same snapshot and never see each other's effects, so a range - // delete and an in-range upsert in one CTE would have unspecified results. `range_deletes` are - // ranges (not materialized per-key) and can overlap an upsert key (a key inside a cleared range - // that is also re-set), so the range clear must commit its effect first; the upsert then - // re-inserts the key. Collapsed from a per-range loop into one statement over a pair of arrays. + // Range deletes run in their own statement before the apply CTE: a range delete and an in-range + // upsert in one CTE would have unspecified ordering, so the clear must commit its effect first and + // the upsert then re-inserts the key. if !range_begins.is_empty() { txn.execute( "DELETE FROM kv USING unnest($1::bytea[], $2::bytea[]) AS r(b, e) @@ -518,12 +569,9 @@ async fn drain_batch( .context("failed to clear ranges")?; } - // Apply the rest of the batch in one CTE: point deletes, the kv upsert, the terminal status - // stamp, and the epoch-fenced watermark advance. This is safe to fold because the write sets are - // disjoint: `apply::WriteSet` guarantees each key appears at most once across `upserts` and - // `point_deletes` (see apply.rs), and the three tables (`kv`, `udb_commit_requests`, `udb_lease`) - // are independent. The watermark UPDATE is fenced on our epoch: a zombie old leader whose epoch - // was bumped sees zero rows returned and must step down before any of its writes become visible. + // Apply the rest of the batch in one CTE: point deletes, the kv upsert, the dedup records for + // multi-node winners, and the epoch-fenced watermark advance. A zombie old leader whose epoch was + // bumped sees zero rows from the lease UPDATE and steps down before any write becomes visible. let new_durable: i64 = match txn .query_opt( "WITH pdel AS ( @@ -532,11 +580,10 @@ async fn drain_batch( INSERT INTO kv (key, value) SELECT * FROM unnest($2::bytea[], $3::bytea[]) ON CONFLICT (key) DO UPDATE SET value = excluded.value - ), stamp AS ( - UPDATE udb_commit_requests AS r - SET status = b.status, commit_version = b.cv - FROM unnest($4::bigint[], $5::text[], $6::bigint[]) AS b(id, status, cv) - WHERE r.id = b.id + ), applied AS ( + INSERT INTO udb_applied (client_node_id, client_seq, commit_version) + SELECT * FROM unnest($4::bytea[], $5::bigint[], $6::bigint[]) + ON CONFLICT (client_node_id, client_seq) DO NOTHING ) UPDATE udb_lease SET durable_version = GREATEST(durable_version, $7) @@ -546,9 +593,9 @@ async fn drain_batch( &point_deletes, &upsert_keys, &upsert_values, - &stamp_ids, - &stamp_statuses, - &stamp_versions, + &winner_dedup_nids, + &winner_dedup_seqs, + &winner_dedup_cvs, &max_winner_cv, &LEASE_ID, &epoch, @@ -564,23 +611,39 @@ async fn drain_batch( } }; - let t_applied = Instant::now(); - txn.commit().await.context("failed to commit drain batch")?; - let t_committed = Instant::now(); - - // Watermark advances strictly after the apply txn is durably committed and visible, so a + // The watermark advances strictly after the apply txn is durably committed and visible, so a // reader handed this read_version can never miss a write with commit_version <= read_version. shared.advance_durable_version(new_durable); - notify_after_commit(&conn, new_durable, &replies).await; - - let t_notified = Instant::now(); + if let Transport::MultiNode(nats) = &shared.transport { + match super::codec::encode_watermark(new_durable) { + Ok(payload) => { + if let Err(err) = nats + .client + .publish(nats.subjects.watermark(), payload.into()) + .await + { + tracing::debug!(?err, "failed to publish udb watermark"); + } + } + Err(err) => tracing::error!(?err, "failed to encode udb watermark"), + } + } - let tracker_len = tracker.len().await; + // Respond to every job (dedup hits, winners, losers). Responses are independent per job, so fan the + // replies out concurrently instead of awaiting each publish in series. + futures_util::stream::iter(jobs.into_iter().enumerate()) + .for_each_concurrent(None, |(i, job)| { + let outcome = outcomes[i].expect("every job must be resolved"); + async move { + job.responder.respond(outcome).await; + } + }) + .await; - tracing::info!( + tracing::debug!( epoch, batch_len, committed = committed_count, @@ -589,49 +652,8 @@ async fn drain_batch( cold_window, new_durable, batch_ms = batch_start.elapsed().as_millis() as u64, - // Sub-phase decomposition of batch_ms (micros) to separate constant service time from - // load-dependent service inflation. - tracker_len, - decode_us = decode_dur.as_micros() as u64, - conflict_us = conflict_dur.as_micros() as u64, - resolve_us = (t_resolved - batch_start).as_micros() as u64, - read_us = (t_read - t_resolved).as_micros() as u64, - fold_us = (t_fold - t_read).as_micros() as u64, - apply_us = (t_applied - t_fold).as_micros() as u64, - commit_us = (t_committed - t_applied).as_micros() as u64, - notify_us = (t_notified - t_committed).as_micros() as u64, "udb leader processed commit batch" ); Ok(BatchOutcome::Processed) } - -/// Wake watermark listeners and the followers waiting on each processed request. Best-effort: a -/// missed NOTIFY is covered by the follower's polling backstop and the watermark refresh timer. -async fn notify_after_commit( - conn: &deadpool_postgres::Client, - new_durable: i64, - replies: &[Reply], -) { - if let Err(err) = conn - .execute( - "SELECT pg_notify($1, $2)", - &[&WATERMARK_CHANNEL, &new_durable.to_string()], - ) - .await - { - tracing::debug!(?err, "failed to notify watermark"); - } - - let channels: Vec<&str> = replies.iter().map(|r| r.channel.as_str()).collect(); - let payloads: Vec<&str> = replies.iter().map(|r| r.payload.as_str()).collect(); - if let Err(err) = conn - .execute( - "SELECT pg_notify(c, p) FROM unnest($1::text[], $2::text[]) AS t(c, p)", - &[&channels, &payloads], - ) - .await - { - tracing::debug!(?err, "failed to notify commit replies"); - } -} diff --git a/engine/packages/universaldb/src/driver/postgres/shared.rs b/engine/packages/universaldb/src/driver/postgres/shared.rs index b26f14a361..5bb98fcbf2 100644 --- a/engine/packages/universaldb/src/driver/postgres/shared.rs +++ b/engine/packages/universaldb/src/driver/postgres/shared.rs @@ -1,87 +1,90 @@ use std::{ sync::{ Arc, - atomic::{AtomicI64, Ordering}, + atomic::{AtomicI64, AtomicU64, Ordering}, }, time::Duration, }; use deadpool_postgres::Pool; +use futures_util::StreamExt; use tokio::sync::{Notify, watch}; -use super::listener::PgListener; +use super::transport::Transport; /// The singleton row id of `udb_lease`. pub const LEASE_ID: i32 = 1; -/// How often the follower refreshes its cached lease row (epoch, leader channel, watermark) as a -/// backstop to the `udb_watermark` NOTIFY. A stale-but-older watermark only widens the conflict -/// window, so this can be loose. +/// How often a node refreshes its cached lease row (epoch, leader id, watermark) as a backstop to the +/// watermark broadcast. A stale-but-older watermark only widens the conflict window, so this can be +/// loose. const LEASE_REFRESH_INTERVAL: Duration = Duration::from_millis(500); -/// Channel a follower NOTIFYs (and the leader LISTENs) to wake the leader's drain loop. -pub fn commit_channel(node_id: &str) -> String { - format!("udb_commit_{node_id}") -} - -/// Channel the leader NOTIFYs (and a follower LISTENs) to deliver a commit result. -pub fn reply_channel(node_id: &str) -> String { - format!("udb_reply_{node_id}") -} - -/// Channel the leader NOTIFYs on every watermark advance; all nodes LISTEN. -pub const WATERMARK_CHANNEL: &str = "udb_watermark"; - -/// Channel a departing leader NOTIFYs after releasing its lease so a standby candidate elects -/// immediately instead of waiting out `ELECTION_RETRY`. All non-leader candidates LISTEN. -pub const ELECTION_CHANNEL: &str = "udb_election"; - /// Cached view of the current leader lease, as seen by a follower. #[derive(Clone, Debug)] pub struct LeaseInfo { pub epoch: i64, - /// Node id of the current leader, used to build its commit channel. + /// Node id of the current leader, used to build its commit subject. pub leader_addr: String, } -/// Process-wide state shared by the follower transaction tasks and the leader resolver. Every node -/// is both a follower (it submits its own commits) and a candidate leader. +/// Process-wide state shared by the follower transaction tasks and the leader resolver. Every node is +/// both a follower (it submits its own commits) and, in multi-node mode, a candidate leader. pub struct PostgresShared { pub pool: Pool, - /// Unique per-process id used to name this node's NOTIFY channels. + /// Unique per-process id. Names this node's commit subject and is the dedup `client_node_id`. pub node_id: String, - pub listener: PgListener, + /// How follower commits reach the leader (in-process channel or NATS). + pub transport: Transport, /// Highest durable commit version (`udb_lease.durable_version`); the follower read version. durable_version: AtomicI64, /// Pinged whenever `durable_version` advances. watermark_notify: Notify, + /// Per-process monotonic commit sequence, the dedup `client_seq`. + commit_seq: AtomicU64, lease_tx: watch::Sender>, lease_rx: watch::Receiver>, } impl PostgresShared { - pub fn new(pool: Pool, node_id: String, listener: PgListener) -> Arc { + pub fn new(pool: Pool, node_id: String, transport: Transport) -> Arc { let (lease_tx, lease_rx) = watch::channel(None); let shared = Arc::new(Self { pool, node_id, - listener, + transport, durable_version: AtomicI64::new(0), watermark_notify: Notify::new(), + commit_seq: AtomicU64::new(0), lease_tx, lease_rx, }); - tokio::spawn(Self::cache_refresh_task(shared.clone())); + // Single-node advances `durable_version` and the lease cache in-process, so it needs no + // cross-process refresh. Multi-node refreshes from the NATS watermark broadcast and a lease + // row poll. + if matches!(shared.transport, Transport::MultiNode(_)) { + tokio::spawn(Self::cache_refresh_task(shared.clone())); + } shared } + /// Whether this driver is running in multi-node mode. + pub fn is_multi_node(&self) -> bool { + matches!(self.transport, Transport::MultiNode(_)) + } + /// The cached follower read version (`durable_version`). pub fn read_version(&self) -> i64 { self.durable_version.load(Ordering::SeqCst) } + /// Allocate the next per-process commit sequence for the failover dedup key. + pub fn next_commit_seq(&self) -> i64 { + self.commit_seq.fetch_add(1, Ordering::Relaxed) as i64 + } + /// Advance the cached watermark monotonically and wake any waiters. pub fn advance_durable_version(&self, version: i64) { let prev = self.durable_version.fetch_max(version, Ordering::SeqCst); @@ -104,7 +107,7 @@ impl PostgresShared { .map(|prev| prev.epoch != lease.epoch || prev.leader_addr != lease.leader_addr) .unwrap_or(true); if changed { - tracing::info!( + tracing::debug!( epoch = lease.epoch, leader_addr = %lease.leader_addr, self_node = %self.node_id, @@ -115,27 +118,41 @@ impl PostgresShared { let _ = self.lease_tx.send(Some(lease)); } - /// Background task: keep `durable_version` and the cached lease fresh via the `udb_watermark` - /// NOTIFY plus a periodic poll of `udb_lease`. + /// Background task (multi-node only): keep `durable_version` and the cached lease fresh via the + /// NATS watermark broadcast plus a periodic poll of `udb_lease`. async fn cache_refresh_task(shared: Arc) { - let mut watermark_rx = shared.listener.listen(WATERMARK_CHANNEL).await; + let Transport::MultiNode(nats) = &shared.transport else { + return; + }; + + let mut watermark_sub = match nats.client.subscribe(nats.subjects.watermark()).await { + Ok(sub) => sub, + Err(err) => { + tracing::error!( + ?err, + "failed to subscribe to udb watermark; relying on lease poll" + ); + return shared.lease_poll_only().await; + } + }; + let mut interval = tokio::time::interval(LEASE_REFRESH_INTERVAL); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { tokio::select! { - notify = watermark_rx.recv() => { - match notify { - Ok(payload) => { - if let Ok(version) = payload.parse::() { - shared.advance_durable_version(version); + msg = watermark_sub.next() => { + match msg { + Some(msg) => { + match super::codec::decode_watermark(&msg.payload) { + Ok(version) => shared.advance_durable_version(version), + Err(err) => { + tracing::debug!(?err, "failed to decode udb watermark") + } } } - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} - Err(tokio::sync::broadcast::error::RecvError::Closed) => { - // Re-subscribe; the listener recreates the channel on reconnect. - watermark_rx = shared.listener.listen(WATERMARK_CHANNEL).await; - } + // The subscription ended (client closed). Fall back to lease polling only. + None => return shared.lease_poll_only().await, } } _ = interval.tick() => { @@ -145,6 +162,16 @@ impl PostgresShared { } } + /// Degraded refresh path: poll the lease row when the watermark subscription is unavailable. + async fn lease_poll_only(self: Arc) { + let mut interval = tokio::time::interval(LEASE_REFRESH_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + interval.tick().await; + self.refresh_lease_row().await; + } + } + async fn refresh_lease_row(&self) { let conn = match self.pool.get().await { Ok(conn) => conn, diff --git a/engine/packages/universaldb/src/driver/postgres/transport.rs b/engine/packages/universaldb/src/driver/postgres/transport.rs new file mode 100644 index 0000000000..8e1b90ecda --- /dev/null +++ b/engine/packages/universaldb/src/driver/postgres/transport.rs @@ -0,0 +1,87 @@ +use tokio::sync::{mpsc, oneshot}; + +use crate::{options::ConflictRangeType, tx_ops::Operation}; + +use super::{codec, nats::NatsTransport}; + +/// Bound on the in-process commit queue feeding the leader drain loop. The leader drains in large +/// batches, so this only needs to absorb a brief burst between batches. +pub const COMMIT_QUEUE_BOUND: usize = 4096; + +/// How the follower commit path reaches the leader resolver. +/// +/// Single-node keeps an in-process channel directly into the leader drain loop (this node is always +/// the leader). Multi-node sends the commit to the elected leader over NATS request/reply; the reply +/// carries the commit result. +pub enum Transport { + SingleNode { + /// Sender into the leader drain loop's job queue. The matching receiver is owned by the + /// resolver task spawned at startup. + commit_tx: mpsc::Sender, + }, + MultiNode(NatsTransport), +} + +/// The outcome of resolving a single commit. +#[derive(Clone, Copy, Debug)] +pub enum CommitOutcome { + /// The commit won resolution and its writes were durably applied at `commit_version`. + Committed { commit_version: i64 }, + /// The commit lost resolution (read-write conflict or cold-window reject). Maps to the + /// retryable `DatabaseError::NotCommitted` on the follower. + Conflict, +} + +/// How the leader delivers a commit result back to the waiting follower. +pub enum Responder { + /// Single-node: resolve the follower's `oneshot` directly. + Local(oneshot::Sender), + /// Multi-node: publish the encoded outcome to the NATS request's reply inbox. + Nats { + client: async_nats::Client, + reply: async_nats::Subject, + }, +} + +impl Responder { + /// Deliver the outcome to the follower. Best-effort: a follower that already gave up (oneshot + /// dropped, or no NATS responder) is covered by its own retry path. + pub async fn respond(self, outcome: CommitOutcome) { + match self { + Responder::Local(tx) => { + let _ = tx.send(outcome); + } + Responder::Nats { client, reply } => { + let payload = match codec::encode_commit_reply(outcome) { + Ok(payload) => payload, + Err(err) => { + tracing::error!(?err, "failed to encode udb commit reply"); + return; + } + }; + if let Err(err) = client.publish(reply, payload.into()).await { + tracing::debug!(?err, "failed to publish udb commit reply"); + } + } + } + } +} + +/// A single commit handed to the leader drain loop, transport-agnostic. Single-node jobs are built by +/// the follower commit path; multi-node jobs are built by the NATS commit subscriber from a decoded +/// request. +pub struct CommitJob { + pub read_version: u64, + pub conflict_ranges: Vec<(Vec, Vec, ConflictRangeType)>, + pub operations: Vec, + /// Failover dedup key, present only in multi-node (single-node has no lost-reply window). + pub dedup_key: Option, + pub responder: Responder, +} + +/// Identifies a single logical commit across follower resends so the leader applies it exactly once. +#[derive(Clone, Debug)] +pub struct DedupKey { + pub client_node_id: Vec, + pub client_seq: i64, +} diff --git a/engine/packages/universaldb/src/driver/rocksdb/database.rs b/engine/packages/universaldb/src/driver/rocksdb/database.rs index ba186d872e..718ced8836 100644 --- a/engine/packages/universaldb/src/driver/rocksdb/database.rs +++ b/engine/packages/universaldb/src/driver/rocksdb/database.rs @@ -48,7 +48,7 @@ impl RocksDbDatabaseDriver { Ok(RocksDbDatabaseDriver { db: Arc::new(db), - max_retries: AtomicI32::new(100), + max_retries: AtomicI32::new(10), txn_conflict_tracker: TransactionConflictTracker::new(), }) } diff --git a/engine/packages/universaldb/tests/failover.rs b/engine/packages/universaldb/tests/failover.rs index 5fb394453c..6293161760 100644 --- a/engine/packages/universaldb/tests/failover.rs +++ b/engine/packages/universaldb/tests/failover.rs @@ -1,21 +1,45 @@ use std::{sync::Arc, time::Duration}; -use rivet_test_deps_docker::TestDatabase; +use rivet_test_deps_docker::{TestDatabase, TestPubSub}; use tokio_postgres::NoTls; -use universaldb::{Database, utils::IsolationLevel::*}; +use universaldb::{Database, driver::postgres::NatsConfig, utils::IsolationLevel::*}; use uuid::Uuid; const ALPHA_KEY: &[u8] = b"failover/alpha"; const BETA_KEY: &[u8] = b"failover/beta"; -/// Build a fresh Postgres-backed `Database`. Each call spins up an independent driver (its own pool, -/// node id, listener, and resolver), so two of them against one Postgres model two engine nodes. -async fn make_db(connection_string: &str) -> Database { - let driver = universaldb::driver::PostgresDatabaseDriver::new_with_config( - universaldb::driver::postgres::PostgresConfig::new(connection_string.to_string()), - ) - .await - .unwrap(); +/// Boot a NATS container and return the multi-node UniversalDB NATS config plus the docker handle +/// (kept alive for the test duration). Leader failover is a multi-node scenario, so the drivers must +/// share a NATS deployment for follower-to-leader commit transport and watermark/election broadcast. +async fn setup_nats() -> (NatsConfig, rivet_test_deps_docker::DockerRunConfig) { + let (pubsub_config, docker_config) = TestPubSub::Nats.config(Uuid::new_v4(), 1).await.unwrap(); + let mut docker_config = docker_config.unwrap(); + docker_config.start().await.unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + + let rivet_config::config::PubSub::Nats(nats) = pubsub_config else { + unreachable!(); + }; + let config = NatsConfig { + addresses: nats.addresses.clone(), + username: nats.username.clone(), + password: nats.password.as_ref().map(|p| p.read().clone()), + client_capacity: nats.client_capacity, + subscription_capacity: nats.subscription_capacity, + }; + (config, docker_config) +} + +/// Build a fresh multi-node Postgres-backed `Database`. Each call spins up an independent driver (its +/// own pool, node id, NATS client, and resolver), so two of them against one Postgres + one NATS model +/// two engine nodes. +async fn make_db(connection_string: &str, nats: &NatsConfig) -> Database { + let mut config = + universaldb::driver::postgres::PostgresConfig::new(connection_string.to_string()); + config.nats = Some(nats.clone()); + let driver = universaldb::driver::PostgresDatabaseDriver::new_with_config(config) + .await + .unwrap(); Database::new(Arc::new(driver)) } @@ -124,16 +148,18 @@ async fn test_postgres_leader_failover() { }; let connection_string = postgres_config.url.read().clone(); + let (nats_config, _nats_docker) = setup_nats().await; + let raw = connect_raw(&connection_string).await; // Node 1 comes up first and deterministically wins the first election (epoch 1). - let db1 = make_db(&connection_string).await; + let db1 = make_db(&connection_string, &nats_config).await; let lease1 = wait_for_lease(&raw, Duration::from_secs(15), |l| l.epoch == 1).await; let leader1_addr = lease1.leader_addr.clone(); // Node 2 joins while node 1 holds a valid lease, so it loses the election and runs as a // follower. - let db2 = make_db(&connection_string).await; + let db2 = make_db(&connection_string, &nats_config).await; // Leader (node 1) commits data. The version sequence and watermark advance. write_key(&db1, ALPHA_KEY, b"1").await; @@ -243,13 +269,15 @@ async fn test_postgres_graceful_handoff() { }; let connection_string = postgres_config.url.read().clone(); + let (nats_config, _nats_docker) = setup_nats().await; + let raw = connect_raw(&connection_string).await; // Node 1 wins the first election; node 2 joins as a follower. - let db1 = make_db(&connection_string).await; + let db1 = make_db(&connection_string, &nats_config).await; let lease1 = wait_for_lease(&raw, Duration::from_secs(15), |l| l.epoch == 1).await; let leader1_addr = lease1.leader_addr.clone(); - let db2 = make_db(&connection_string).await; + let db2 = make_db(&connection_string, &nats_config).await; write_key(&db1, ALPHA_KEY, b"1").await; let lease_before = read_lease(&raw).await.unwrap(); diff --git a/engine/packages/universalpubsub/Cargo.toml b/engine/packages/universalpubsub/Cargo.toml index 7043f44fac..c9c9dfafe1 100644 --- a/engine/packages/universalpubsub/Cargo.toml +++ b/engine/packages/universalpubsub/Cargo.toml @@ -10,23 +10,17 @@ edition.workspace = true anyhow.workspace = true async-nats.workspace = true async-trait.workspace = true -base64.workspace = true -deadpool-postgres.workspace = true futures-util.workspace = true lazy_static.workspace = true rand.workspace = true rivet-error.workspace = true rivet-metrics.workspace = true rivet-perf.workspace = true -rivet-postgres-util.workspace = true rivet-ups-protocol.workspace = true rivet-util.workspace = true scc.workspace = true serde_json.workspace = true serde.workspace = true -sha2.workspace = true -tokio-postgres-rustls.workspace = true -tokio-postgres.workspace = true tokio-util.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/engine/packages/universalpubsub/benches/simple.rs b/engine/packages/universalpubsub/benches/simple.rs index 7edc5ff926..efb5624d2e 100644 --- a/engine/packages/universalpubsub/benches/simple.rs +++ b/engine/packages/universalpubsub/benches/simple.rs @@ -6,7 +6,7 @@ use std::time::{Duration, Instant}; use anyhow::*; use futures_util::future::join_all; -use rivet_test_deps_docker::{TestDatabase, TestPubSub}; +use rivet_test_deps_docker::TestPubSub; use std::future::Future; use tabled::{builder::Builder, settings::Style}; use universalpubsub::{NextOutput, PubSub, PublishOpts}; @@ -654,98 +654,6 @@ async fn setup_nats_single_mem() -> Result<(PubSub, PubSub)> { Ok((pubsub.clone(), pubsub)) } -async fn setup_pg_pair() -> Result<(PubSub, PubSub)> { - let test_id = Uuid::new_v4(); - let (db_config, mut docker) = TestDatabase::Postgres.config(test_id, 1).await?; - if let Some(ref mut d) = docker { - d.start().await?; - } - tokio::time::sleep(Duration::from_secs(5)).await; - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!() - }; - let url = pg.url.read().clone(); - let driver_pub = universalpubsub::driver::postgres::PostgresDriver::connect( - url.clone(), - false, - None, - None, - None, - ) - .await?; - let driver_sub = - universalpubsub::driver::postgres::PostgresDriver::connect(url, false, None, None, None) - .await?; - Ok(( - PubSub::new_with_memory_optimization(Arc::new(driver_pub), false), - PubSub::new_with_memory_optimization(Arc::new(driver_sub), false), - )) -} - -async fn setup_pg_single() -> Result<(PubSub, PubSub)> { - let test_id = Uuid::new_v4(); - let (db_config, mut docker) = TestDatabase::Postgres.config(test_id, 1).await?; - if let Some(ref mut d) = docker { - d.start().await?; - } - tokio::time::sleep(Duration::from_secs(5)).await; - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!() - }; - let url = pg.url.read().clone(); - let driver = - universalpubsub::driver::postgres::PostgresDriver::connect(url, false, None, None, None) - .await?; - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - Ok((pubsub.clone(), pubsub)) -} - -async fn setup_pg_pair_mem() -> Result<(PubSub, PubSub)> { - let test_id = Uuid::new_v4(); - let (db_config, mut docker) = TestDatabase::Postgres.config(test_id, 1).await?; - if let Some(ref mut d) = docker { - d.start().await?; - } - tokio::time::sleep(Duration::from_secs(5)).await; - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!() - }; - let url = pg.url.read().clone(); - let driver_pub = universalpubsub::driver::postgres::PostgresDriver::connect( - url.clone(), - true, - None, - None, - None, - ) - .await?; - let driver_sub = - universalpubsub::driver::postgres::PostgresDriver::connect(url, true, None, None, None) - .await?; - Ok(( - PubSub::new_with_memory_optimization(Arc::new(driver_pub), true), - PubSub::new_with_memory_optimization(Arc::new(driver_sub), true), - )) -} - -async fn setup_pg_single_mem() -> Result<(PubSub, PubSub)> { - let test_id = Uuid::new_v4(); - let (db_config, mut docker) = TestDatabase::Postgres.config(test_id, 1).await?; - if let Some(ref mut d) = docker { - d.start().await?; - } - tokio::time::sleep(Duration::from_secs(5)).await; - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!() - }; - let url = pg.url.read().clone(); - let driver = - universalpubsub::driver::postgres::PostgresDriver::connect(url, true, None, None, None) - .await?; - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), true); - Ok((pubsub.clone(), pubsub)) -} - async fn setup_mem_pair() -> Result<(PubSub, PubSub)> { let test_id = Uuid::new_v4(); let (pubsub_config, _docker) = TestPubSub::Memory.config(test_id, 1).await?; @@ -891,16 +799,6 @@ async fn main() -> Result<()> { let results = run_benches("nats-mem", publisher.clone(), subscriber.clone(), iters).await?; all_results.insert("nats-mem".to_string(), results); - // Postgres (no memory optimization) - let (publisher, subscriber) = setup_pg_pair().await?; - let results = run_benches("pg-nomem", publisher.clone(), subscriber.clone(), iters).await?; - all_results.insert("pg-nomem".to_string(), results); - - // Postgres (memory optimization) - let (publisher, subscriber) = setup_pg_pair_mem().await?; - let results = run_benches("pg-mem", publisher.clone(), subscriber.clone(), iters).await?; - all_results.insert("pg-mem".to_string(), results); - // NATS single connection (no memory optimization) let (publisher, subscriber) = setup_nats_single().await?; let results = run_benches( @@ -923,28 +821,6 @@ async fn main() -> Result<()> { .await?; all_results.insert("nats-single-mem".to_string(), results); - // Postgres single connection (no memory optimization) - let (publisher, subscriber) = setup_pg_single().await?; - let results = run_benches( - "pg-single-nomem", - publisher.clone(), - subscriber.clone(), - iters, - ) - .await?; - all_results.insert("pg-single-nomem".to_string(), results); - - // Postgres single connection (memory optimization) - let (publisher, subscriber) = setup_pg_single_mem().await?; - let results = run_benches( - "pg-single-mem", - publisher.clone(), - subscriber.clone(), - iters, - ) - .await?; - all_results.insert("pg-single-mem".to_string(), results); - // Print results table print_results_table(&all_results); diff --git a/engine/packages/universalpubsub/src/driver/mod.rs b/engine/packages/universalpubsub/src/driver/mod.rs index 1e79e4383b..b19a3aa85f 100644 --- a/engine/packages/universalpubsub/src/driver/mod.rs +++ b/engine/packages/universalpubsub/src/driver/mod.rs @@ -8,7 +8,6 @@ use crate::InboxSubject; pub mod memory; pub mod nats; -pub mod postgres; pub type PubSubDriverHandle = Arc; diff --git a/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs b/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs deleted file mode 100644 index f08ff7bbf3..0000000000 --- a/engine/packages/universalpubsub/src/driver/postgres/doorbell.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time::Duration; - -use deadpool_postgres::Pool; -use tokio::sync::Notify; -use tokio::time::Instant; - -/// Number of doorbell shards. A subject maps to a shard via `hash(subject_hash) % K`. -/// Subscribers LISTEN their subject's shard channel; publishers wake the local -/// doorbell task which NOTIFYs the shard. -pub const DOORBELL_SHARD_COUNT: usize = 32; - -/// Debounce window. Caps each (process, shard) NOTIFY rate at one per window, which -/// bounds how many backends are woken per shard over time. -const DOORBELL_WINDOW: Duration = Duration::from_millis(5); - -/// Returns the NOTIFY channel name for a doorbell shard. -pub fn shard_channel(shard: usize) -> String { - format!("ups_db_{shard}") -} - -/// Returns the doorbell shard for a subject hash. -pub fn shard_for(subject_hash: &str) -> usize { - use std::hash::{DefaultHasher, Hash, Hasher}; - let mut hasher = DefaultHasher::new(); - subject_hash.hash(&mut hasher); - (hasher.finish() as usize) % DOORBELL_SHARD_COUNT -} - -/// Coalesced, payload-free NOTIFY doorbell. -/// -/// Publishers call [`Doorbell::mark_dirty`] after committing a row. A single -/// per-process task drains dirty shards and emits at most one NOTIFY per shard per -/// debounce window using leading-edge fire plus a trailing-edge flush. The doorbell -/// is a latency optimization only. Correctness comes from the table plus the -/// subscriber poll backstop, so a dropped or failed NOTIFY only adds latency. -pub struct Doorbell { - dirty: [AtomicBool; DOORBELL_SHARD_COUNT], - notify: Notify, - pool: Arc, -} - -impl Doorbell { - pub fn new(pool: Arc) -> Arc { - let doorbell = Arc::new(Self { - dirty: std::array::from_fn(|_| AtomicBool::new(false)), - notify: Notify::new(), - pool, - }); - - let task_doorbell = doorbell.clone(); - tokio::spawn(async move { task_doorbell.run().await }); - - doorbell - } - - /// Marks a shard dirty and wakes the doorbell task. Never blocks. - pub fn mark_dirty(&self, shard: usize) { - self.dirty[shard].store(true, Ordering::Release); - self.notify.notify_one(); - } - - async fn run(self: Arc) { - // Per-shard timestamp of the last NOTIFY emitted by this process. - let mut last_notify: [Option; DOORBELL_SHARD_COUNT] = [None; DOORBELL_SHARD_COUNT]; - // Per-shard deadline for a pending trailing-edge NOTIFY, if any. - let mut trailing: [Option; DOORBELL_SHARD_COUNT] = [None; DOORBELL_SHARD_COUNT]; - - loop { - // Arm on the next pending trailing deadline so the trailing edge fires - // even with no further publishes. Wait on the notify permit otherwise. - let next_deadline = trailing.iter().filter_map(|x| *x).min(); - match next_deadline { - Some(deadline) => { - tokio::select! { - _ = self.notify.notified() => {} - _ = tokio::time::sleep_until(deadline) => {} - } - } - None => { - self.notify.notified().await; - } - } - - let now = Instant::now(); - for shard in 0..DOORBELL_SHARD_COUNT { - let is_dirty = self.dirty[shard].swap(false, Ordering::AcqRel); - if is_dirty { - match last_notify[shard] { - Some(last) if now.duration_since(last) < DOORBELL_WINDOW => { - // Within the window. Defer to a trailing-edge NOTIFY at - // window end so at most one NOTIFY fires per shard per W. - if trailing[shard].is_none() { - trailing[shard] = Some(last + DOORBELL_WINDOW); - } - } - _ => { - // Leading edge. Fire immediately for low idle latency. - self.notify_shard(shard).await; - last_notify[shard] = Some(now); - trailing[shard] = None; - } - } - } - - // Flush a trailing-edge NOTIFY whose window has elapsed. - if let Some(deadline) = trailing[shard] { - if now >= deadline { - self.notify_shard(shard).await; - last_notify[shard] = Some(now); - trailing[shard] = None; - } - } - } - } - } - - async fn notify_shard(&self, shard: usize) { - let channel = shard_channel(shard); - match self.pool.get().await { - Ok(conn) => { - // Payload-free doorbell. The payload lives in the table. - if let Err(err) = conn.execute("SELECT pg_notify($1, '')", &[&channel]).await { - tracing::warn!(?err, %channel, "failed to emit doorbell notify"); - } - } - Err(err) => { - tracing::warn!(?err, %channel, "failed to get connection for doorbell notify"); - } - } - } -} diff --git a/engine/packages/universalpubsub/src/driver/postgres/mod.rs b/engine/packages/universalpubsub/src/driver/postgres/mod.rs deleted file mode 100644 index 2a492fbccf..0000000000 --- a/engine/packages/universalpubsub/src/driver/postgres/mod.rs +++ /dev/null @@ -1,1050 +0,0 @@ -use anyhow::{Context, Result}; -use async_trait::async_trait; -use deadpool_postgres::{Config, ManagerConfig, Pool, PoolConfig, RecyclingMethod, Runtime}; -use futures_util::future::poll_fn; -use rivet_postgres_util::build_tls_config; -use rivet_util::throttle::Backoff; -use scc::HashMap; -use std::collections::VecDeque; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{Mutex, broadcast}; -use tokio_postgres::AsyncMessage; -use tokio_postgres_rustls::MakeRustlsConnect; -use tracing::Instrument; -use uuid::Uuid; - -use crate::driver::{PubSubDriver, SubscriberDriver, SubscriberDriverHandle}; -use crate::metrics; -use crate::pubsub::DriverOutput; - -mod doorbell; - -use doorbell::{Doorbell, shard_channel, shard_for}; - -/// The transport is the table, not the NOTIFY payload, so there is no per-message -/// size cap from the 8000-byte NOTIFY limit. Match the NATS ceiling so chunking -/// behaves identically across drivers. -pub const POSTGRES_MAX_MESSAGE_SIZE: usize = 1024 * 1024; - -/// Poll backstop interval. Every subscriber reads its table on this interval -/// regardless of doorbell wakeups. This is the correctness floor that makes delivery -/// independent of any NOTIFY arriving. -const POLL_INTERVAL: Duration = Duration::from_secs(1); - -/// Idle-in-transaction timeout applied to the LISTEN connection. A wedged listener -/// holding a transaction open would otherwise fill the shared notify queue and fail -/// NOTIFY cluster-wide. Bounding it keeps a stuck listener degrading to added latency -/// rather than a cluster outage. -const LISTEN_IDLE_IN_TRANSACTION_TIMEOUT_MS: i64 = 30_000; - -/// How often this process refreshes its node liveness heartbeat. One heartbeat per -/// process keeps all of its subscriber registrations alive at once. -const NODE_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(10); -/// How recent a node's heartbeat must be for its subscribers to count as live -/// responders. -const NODE_TTL_SECS: i64 = 30; - -/// How often to GC expired broadcast messages. -const MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(5); -/// Max age before a broadcast message row is garbage collected. Must exceed the poll -/// interval plus the reconnect gap. A subscriber that falls behind this misses -/// messages, matching NATS-core at-most-once semantics for slow consumers. -const MESSAGE_MAX_AGE_SECS: i64 = 10; - -/// How often to GC dead nodes and the subscriber rows orphaned by them. -const REGISTRY_GC_INTERVAL: Duration = Duration::from_secs(30); - -/// How often to GC orphaned queue messages. -const QUEUE_MESSAGE_GC_INTERVAL: Duration = Duration::from_secs(300); -/// Max age before an unconsumed queue message is garbage collected. -const QUEUE_MESSAGE_MAX_AGE_SECS: i64 = 3600; - -/// Per-shard signal carried over a subscriber's in-process wakeup channel. -#[derive(Clone)] -enum ShardSignal { - /// A doorbell NOTIFY landed for this shard. Poll the table. - Wakeup, - /// A local request found no responders for the given reply subject. The matching - /// reply subscriber surfaces a no-responders result. - NoResponders { subject: String }, -} - -#[derive(Clone)] -pub struct PostgresDriver { - pool: Arc, - client: Arc>>, - /// Identifies this process in the subscriber registry. A single heartbeat keeps - /// all of this node's registrations live. - node_id: String, - /// Wakeup channels keyed by doorbell shard channel name. Shared by broadcast and - /// queue subscribers whose subjects map to the same shard. - shard_subscriptions: Arc>>, - doorbell: Arc, - client_ready: tokio::sync::watch::Receiver, -} - -impl PostgresDriver { - #[tracing::instrument(skip(conn_str))] - pub async fn connect( - conn_str: String, - ssl_root_cert_path: Option, - ssl_client_cert_path: Option, - ssl_client_key_path: Option, - ) -> Result { - // Create deadpool config from connection string - let mut config = Config::new(); - config.url = Some(conn_str.clone()); - config.pool = Some(PoolConfig { - max_size: 64, - ..Default::default() - }); - config.manager = Some(ManagerConfig { - recycling_method: RecyclingMethod::Fast, - }); - - // Create the pool - tracing::debug!("creating postgres pool"); - - // Build TLS configuration with optional custom certificates - let tls_config = build_tls_config( - ssl_root_cert_path.as_ref(), - ssl_client_cert_path.as_ref(), - ssl_client_key_path.as_ref(), - )?; - - let tls = MakeRustlsConnect::new(tls_config); - - let pool = config - .create_pool(Some(Runtime::Tokio1), tls) - .context("failed to create postgres pool")?; - tracing::debug!("postgres pool created successfully"); - - let pool = Arc::new(pool); - let shard_subscriptions: Arc>> = - Arc::new(HashMap::new()); - let client: Arc>> = Arc::new(Mutex::new(None)); - let node_id = Uuid::new_v4().to_string(); - - // Create channel for client ready notifications - let (ready_tx, client_ready) = tokio::sync::watch::channel(false); - - // Spawn connection lifecycle task - tokio::spawn(Self::spawn_connection_lifecycle( - conn_str.clone(), - shard_subscriptions.clone(), - client.clone(), - ready_tx, - ssl_root_cert_path.clone(), - ssl_client_cert_path.clone(), - ssl_client_key_path.clone(), - )); - - let doorbell = Doorbell::new(pool.clone()); - - let driver = Self { - pool, - client, - node_id, - shard_subscriptions, - doorbell, - client_ready, - }; - - // Wait for initial connection to be established - driver.wait_for_client().await?; - - // Create tables eagerly so they exist before any publish or subscribe. - { - tracing::debug!("configuring postgres udb tables"); - let conn = driver - .pool - .get() - .await - .context("failed to get connection for table creation")?; - conn.batch_execute( - // Broadcast transport table. UNLOGGED gives at-most-once across a - // crash, matching NATS-core semantics, and avoids WAL fsync on every - // publish. The real subject is stored so receivers can verify it and - // reject DefaultHasher subject-hash collisions. - "CREATE UNLOGGED TABLE IF NOT EXISTS ups_messages ( \ - id BIGSERIAL PRIMARY KEY, \ - subject_hash TEXT NOT NULL, \ - subject TEXT NOT NULL, \ - payload BYTEA NOT NULL, \ - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ - ); \ - CREATE INDEX IF NOT EXISTS ups_messages_subject_id \ - ON ups_messages (subject_hash, id); \ - CREATE TABLE IF NOT EXISTS ups_nodes ( \ - node_id TEXT PRIMARY KEY, \ - heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ - ); \ - CREATE TABLE IF NOT EXISTS ups_subs ( \ - id TEXT PRIMARY KEY, \ - node_id TEXT NOT NULL, \ - subject_hash TEXT NOT NULL, \ - subject TEXT NOT NULL \ - ); \ - CREATE INDEX IF NOT EXISTS ups_subs_subject \ - ON ups_subs (subject_hash); \ - CREATE TABLE IF NOT EXISTS ups_queue_subs ( \ - id TEXT PRIMARY KEY, \ - node_id TEXT NOT NULL, \ - subject_hash TEXT NOT NULL, \ - queue_hash TEXT NOT NULL \ - ); \ - CREATE INDEX IF NOT EXISTS ups_queue_subs_subject_queue \ - ON ups_queue_subs (subject_hash, queue_hash); \ - CREATE UNLOGGED TABLE IF NOT EXISTS ups_queue_messages ( \ - id BIGSERIAL PRIMARY KEY, \ - subject_hash TEXT NOT NULL, \ - queue_hash TEXT NOT NULL, \ - payload BYTEA NOT NULL, \ - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() \ - ); \ - CREATE INDEX IF NOT EXISTS ups_queue_messages_idx \ - ON ups_queue_messages (subject_hash, queue_hash, id);", - ) - .await - .context("failed to create tables")?; - tracing::debug!("postgres udb tables ready"); - } - - // Register this node and start its liveness heartbeat. - driver.heartbeat_node().await?; - let heartbeat_driver = driver.clone(); - tokio::spawn(async move { - let mut interval = tokio::time::interval(NODE_HEARTBEAT_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - interval.tick().await; - if let Err(e) = heartbeat_driver.heartbeat_node().await { - tracing::warn!(?e, "failed to heartbeat node"); - } - } - }); - - // Spawn GC task for expired broadcast messages - let message_gc_driver = driver.clone(); - tokio::spawn(async move { - let mut interval = tokio::time::interval(MESSAGE_GC_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - interval.tick().await; - if let Ok(conn) = message_gc_driver.pool.get().await { - let result = conn - .execute( - "DELETE FROM ups_messages \ - WHERE created_at < NOW() - ($1::bigint * INTERVAL '1 second')", - &[&MESSAGE_MAX_AGE_SECS], - ) - .await; - if let Err(e) = result { - tracing::warn!(?e, "failed to gc broadcast messages"); - } - } - } - }); - - // Spawn GC task for dead nodes and orphaned subscriber rows. - let registry_gc_driver = driver.clone(); - tokio::spawn(async move { - let mut interval = tokio::time::interval(REGISTRY_GC_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - interval.tick().await; - if let Ok(conn) = registry_gc_driver.pool.get().await { - if let Err(e) = conn - .execute( - "DELETE FROM ups_nodes \ - WHERE heartbeat_at < NOW() - ($1::bigint * INTERVAL '1 second')", - &[&NODE_TTL_SECS], - ) - .await - { - tracing::warn!(?e, "failed to gc dead nodes"); - } - if let Err(e) = conn - .execute( - "DELETE FROM ups_subs \ - WHERE node_id NOT IN (SELECT node_id FROM ups_nodes)", - &[], - ) - .await - { - tracing::warn!(?e, "failed to gc orphaned subs"); - } - if let Err(e) = conn - .execute( - "DELETE FROM ups_queue_subs \ - WHERE node_id NOT IN (SELECT node_id FROM ups_nodes)", - &[], - ) - .await - { - tracing::warn!(?e, "failed to gc orphaned queue subs"); - } - } - } - }); - - // Spawn GC task for orphaned queue messages - let gc_driver = driver.clone(); - tokio::spawn(async move { - let mut interval = tokio::time::interval(QUEUE_MESSAGE_GC_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - interval.tick().await; - if let Ok(conn) = gc_driver.pool.get().await { - let result = conn - .execute( - "DELETE FROM ups_queue_messages \ - WHERE created_at < NOW() - ($1::bigint * INTERVAL '1 second')", - &[&QUEUE_MESSAGE_MAX_AGE_SECS], - ) - .await; - if let Err(e) = result { - tracing::warn!(?e, "failed to gc queue messages"); - } - } - } - }); - - Ok(driver) - } - - /// Manages the connection lifecycle with automatic reconnection - async fn spawn_connection_lifecycle( - conn_str: String, - shard_subscriptions: Arc>>, - client: Arc>>, - ready_tx: tokio::sync::watch::Sender, - ssl_root_cert_path: Option, - ssl_client_cert_path: Option, - ssl_client_key_path: Option, - ) { - let mut backoff = Backoff::default(); - - // Build TLS configuration with optional custom certificates - let tls_config = match build_tls_config( - ssl_root_cert_path.as_ref(), - ssl_client_cert_path.as_ref(), - ssl_client_key_path.as_ref(), - ) { - std::result::Result::Ok(config) => config, - std::result::Result::Err(e) => { - tracing::error!(?e, "failed to build TLS config"); - return; - } - }; - - let tls = MakeRustlsConnect::new(tls_config); - - loop { - match tokio_postgres::connect(&conn_str, tls.clone()).await { - Result::Ok((new_client, conn)) => { - tracing::debug!("postgres listen connection established"); - // Reset backoff on successful connection - backoff = Backoff::default(); - - // Spawn the polling task immediately - // This must be done before any operations on the client - let shard_subscriptions_clone = shard_subscriptions.clone(); - let poll_handle = tokio::spawn(async move { - Self::poll_connection(conn, shard_subscriptions_clone).await; - }); - - // Bound a stuck listener so it cannot wedge the shared notify queue. - if let Result::Err(e) = new_client - .execute( - &format!( - "SET idle_in_transaction_session_timeout = '{}'", - LISTEN_IDLE_IN_TRANSACTION_TIMEOUT_MS - ), - &[], - ) - .await - { - tracing::warn!(?e, "failed to set idle_in_transaction_session_timeout"); - } - - // Get shard channels to re-subscribe to - let mut channels = Vec::new(); - shard_subscriptions - .iter_async(|k, _| { - channels.push(k.clone()); - true - }) - .await; - - if !channels.is_empty() { - tracing::debug!( - channels = channels.len(), - "re-subscribing to doorbell shards after reconnection" - ); - } - - for channel in channels.iter() { - tracing::debug!(?channel, "re-subscribing to channel"); - if let Result::Err(e) = new_client - .execute(&format!("LISTEN \"{}\"", channel), &[]) - .await - { - tracing::error!(?e, %channel, "failed to re-subscribe to channel"); - } else { - tracing::debug!(%channel, "successfully re-subscribed to channel"); - } - } - - // Update the client reference and signal ready - // Do this AFTER re-subscribing to ensure LISTEN is complete - *client.lock().await = Some(new_client); - let _ = ready_tx.send(true); - - // Wait for the polling task to complete (when the connection closes) - let _ = poll_handle.await; - - // Clear the client reference on disconnect - *client.lock().await = None; - - // Notify that client is disconnected - let _ = ready_tx.send(false); - } - Result::Err(e) => { - tracing::error!(?e, "failed to connect to postgres, retrying"); - backoff.tick().await; - } - } - } - } - - /// Polls the connection for notifications until it closes or errors - async fn poll_connection( - mut conn: tokio_postgres::Connection, - shard_subscriptions: Arc>>, - ) where - T: tokio_postgres::tls::TlsStream + Unpin, - { - loop { - match poll_fn(|cx| conn.poll_message(cx)).await { - Some(std::result::Result::Ok(AsyncMessage::Notification(note))) => { - tracing::trace!(channel = %note.channel(), "received doorbell wakeup"); - // Doorbell notifications are payload-free wakeup signals only. - // Subscribers read their payload from the table. - if let Some(sub) = shard_subscriptions.get_async(note.channel()).await { - let _ = sub.send(ShardSignal::Wakeup); - } else { - tracing::trace!(channel = %note.channel(), "wakeup for unknown shard"); - } - } - Some(std::result::Result::Ok(_)) => { - // Ignore other async messages - } - Some(std::result::Result::Err(err)) => { - tracing::error!(?err, "postgres connection error"); - break; - } - None => { - tracing::warn!("postgres connection closed"); - break; - } - } - } - } - - /// Wait for the client to be connected - async fn wait_for_client(&self) -> Result<()> { - let mut ready_rx = self.client_ready.clone(); - tokio::time::timeout(tokio::time::Duration::from_secs(5), async { - loop { - // Check if client is already available - if self.client.lock().await.is_some() { - return Ok(()); - } - - // Wait for the ready signal to change - ready_rx - .changed() - .await - .context("connection lifecycle task ended")?; - } - }) - .await - .context("timeout waiting for postgres client connection")? - } - - fn hash_subject(&self, subject: &str) -> String { - // Postgres channel names have a 64 character limit, but this hash is also the - // table index key. Collisions are possible and resolved by verifying the real - // subject stored alongside each row. - let mut hasher = DefaultHasher::new(); - subject.hash(&mut hasher); - format!("ups_{:x}", hasher.finish()) - } - - fn hash_queue(&self, queue: &str) -> String { - let mut hasher = DefaultHasher::new(); - queue.hash(&mut hasher); - format!("{:x}", hasher.finish()) - } - - /// Upserts this node's liveness heartbeat. Re-inserts the row if a GC pass removed - /// it after a transient stall. - async fn heartbeat_node(&self) -> Result<()> { - let conn = self - .pool - .get() - .await - .context("failed to get connection for node heartbeat")?; - conn.execute( - "INSERT INTO ups_nodes (node_id, heartbeat_at) VALUES ($1, NOW()) \ - ON CONFLICT (node_id) DO UPDATE SET heartbeat_at = NOW()", - &[&self.node_id], - ) - .await - .context("failed to upsert node heartbeat")?; - Ok(()) - } - - /// Returns the current max broadcast message id, used as a subscriber's starting - /// cursor so it only sees future messages (NATS at-most-once, no replay). - async fn current_max_id(&self) -> Result { - let conn = self - .pool - .get() - .await - .context("failed to get connection for cursor init")?; - let row = conn - .query_one("SELECT COALESCE(MAX(id), 0) FROM ups_messages", &[]) - .await - .context("failed to read current max id")?; - Ok(row.get(0)) - } - - /// Ensures this process is LISTENing on the given doorbell shard and returns a - /// wakeup receiver plus a drop guard that UNLISTENs once no receivers remain. - async fn ensure_shard_listen( - &self, - shard: usize, - ) -> ( - broadcast::Receiver, - tokio_util::sync::DropGuard, - ) { - let channel = shard_channel(shard); - - match self.shard_subscriptions.entry_async(channel.clone()).await { - scc::hash_map::Entry::Occupied(existing) => { - let rx = existing.subscribe(); - let drop_guard = - self.spawn_shard_cleanup_task(channel.clone(), existing.get().clone()); - (rx, drop_guard) - } - scc::hash_map::Entry::Vacant(e) => { - let (tx, rx) = broadcast::channel(1024); - e.insert_entry(tx.clone()); - metrics::POSTGRES_SUBSCRIPTION_COUNT.set(self.shard_subscriptions.len() as i64); - - if let Some(client) = &*self.client.lock().await { - match client - .execute(&format!("LISTEN \"{channel}\""), &[]) - .instrument(tracing::trace_span!("pg_listen")) - .await - { - Result::Ok(_) => { - tracing::debug!(%channel, "successfully subscribed to shard"); - } - Result::Err(e) => { - tracing::warn!(?e, %channel, "failed to LISTEN, will retry on reconnection"); - } - } - } else { - tracing::debug!(%channel, "client not connected, will LISTEN on reconnection"); - } - - let drop_guard = self.spawn_shard_cleanup_task(channel.clone(), tx.clone()); - (rx, drop_guard) - } - } - } - - fn spawn_shard_cleanup_task( - &self, - channel: String, - tx: broadcast::Sender, - ) -> tokio_util::sync::DropGuard { - let driver = self.clone(); - let token = tokio_util::sync::CancellationToken::new(); - let drop_guard = token.clone().drop_guard(); - - tokio::spawn(async move { - token.cancelled().await; - if tx.receiver_count() == 0 { - if let Some(client) = &*driver.client.lock().await { - let sql = format!("UNLISTEN \"{}\"", channel); - if let Err(err) = client.execute(sql.as_str(), &[]).await { - tracing::warn!(?err, %channel, "failed to UNLISTEN channel"); - } else { - tracing::trace!(%channel, "unlistened channel"); - } - } - driver.shard_subscriptions.remove_async(&channel).await; - metrics::POSTGRES_SUBSCRIPTION_COUNT.set(driver.shard_subscriptions.len() as i64); - } - }); - - drop_guard - } - - /// Inserts the broadcast row and any active queue-group rows in one transaction. - async fn try_publish_to_db( - &self, - subject: &str, - subject_hash: &str, - payload: &[u8], - ) -> Result<()> { - let mut conn = self - .pool - .get() - .await - .context("failed to get connection for publish")?; - let tx = conn - .transaction() - .await - .context("failed to begin publish transaction")?; - - // Broadcast row. - tx.execute( - "INSERT INTO ups_messages (subject_hash, subject, payload) VALUES ($1, $2, $3)", - &[&subject_hash, &subject, &payload], - ) - .await - .context("failed to insert broadcast message")?; - - // Queue rows for every live queue group on this subject. Batched into the same - // transaction so a crash never strands a row mid-publish. - let rows = tx - .query( - "SELECT DISTINCT s.queue_hash FROM ups_queue_subs s \ - JOIN ups_nodes n ON s.node_id = n.node_id \ - WHERE s.subject_hash = $1 \ - AND n.heartbeat_at > NOW() - ($2::bigint * INTERVAL '1 second')", - &[&subject_hash, &NODE_TTL_SECS], - ) - .await - .context("failed to query active queue subs")?; - - for row in rows { - let queue_hash: String = row.get(0); - tx.execute( - "INSERT INTO ups_queue_messages (subject_hash, queue_hash, payload) \ - VALUES ($1, $2, $3)", - &[&subject_hash, &queue_hash, &payload], - ) - .await - .context("failed to insert queue message")?; - } - - tx.commit().await.context("failed to commit publish")?; - - Ok(()) - } - - /// Returns whether any live subscriber (broadcast or queue) exists for the subject - /// anywhere in the fleet. Used to decide whether a request surfaces a no-responders - /// result instead of waiting out its timeout. - async fn has_responders(&self, subject_hash: &str, subject: &str) -> Result { - let conn = self - .pool - .get() - .await - .context("failed to get connection for responder check")?; - let row = conn - .query_one( - "SELECT \ - EXISTS( \ - SELECT 1 FROM ups_subs s \ - JOIN ups_nodes n ON s.node_id = n.node_id \ - WHERE s.subject_hash = $1 AND s.subject = $2 \ - AND n.heartbeat_at > NOW() - ($3::bigint * INTERVAL '1 second') \ - ) \ - OR EXISTS( \ - SELECT 1 FROM ups_queue_subs s \ - JOIN ups_nodes n ON s.node_id = n.node_id \ - WHERE s.subject_hash = $1 \ - AND n.heartbeat_at > NOW() - ($3::bigint * INTERVAL '1 second') \ - )", - &[&subject_hash, &subject, &NODE_TTL_SECS], - ) - .await - .context("failed to check responders")?; - Ok(row.get(0)) - } - - /// Delivers a no-responders result to the local reply subscriber. The requester is - /// always in this process, so the signal is routed in-memory over the reply - /// subject's shard channel rather than the table. - async fn signal_no_responders(&self, reply_subject: &str) { - let reply_hash = self.hash_subject(reply_subject); - let channel = shard_channel(shard_for(&reply_hash)); - if let Some(tx) = self.shard_subscriptions.get_async(&channel).await { - let _ = tx.send(ShardSignal::NoResponders { - subject: reply_subject.to_string(), - }); - } - } -} - -#[async_trait] -impl PubSubDriver for PostgresDriver { - async fn subscribe( - &self, - subject: &str, - reply_id: Option, - ) -> Result { - let subject_hash = self.hash_subject(subject); - let shard = shard_for(&subject_hash); - - // Capture the cursor before LISTENing. Any message inserted after this point - // has a higher id and is delivered either by the doorbell wakeup or the poll - // backstop, so there is no subscribe/publish race. - let cursor = self.current_max_id().await?; - - let (rx, drop_guard) = self.ensure_shard_listen(shard).await; - - // Register in the responder registry so requests to this subject can detect - // responders. Reply inboxes are never request targets, so they skip the - // registry to keep request latency off this path. - let sub_id = if reply_id.is_none() { - let sub_id = Uuid::new_v4().to_string(); - let conn = self - .pool - .get() - .await - .context("failed to get connection for subscribe")?; - conn.execute( - "INSERT INTO ups_subs (id, node_id, subject_hash, subject) \ - VALUES ($1, $2, $3, $4)", - &[&sub_id, &self.node_id, &subject_hash, &subject], - ) - .await - .context("failed to register subscriber")?; - Some(sub_id) - } else { - None - }; - - Ok(Box::new(PostgresSubscriber { - subject: subject.to_string(), - subject_hash, - pool: self.pool.clone(), - cursor, - buffer: VecDeque::new(), - rx, - sub_id, - _drop_guard: drop_guard, - })) - } - - async fn queue_subscribe(&self, subject: &str, queue: &str) -> Result { - let subject_hash = self.hash_subject(subject); - let queue_hash = self.hash_queue(queue); - let shard = shard_for(&subject_hash); - - // Register this subscriber in the database so publishers know the queue exists - let sub_id = Uuid::new_v4().to_string(); - { - let conn = self - .pool - .get() - .await - .context("failed to get connection for queue subscribe")?; - conn.execute( - "INSERT INTO ups_queue_subs (id, node_id, subject_hash, queue_hash) \ - VALUES ($1, $2, $3, $4)", - &[&sub_id, &self.node_id, &subject_hash, &queue_hash], - ) - .await - .context("failed to register queue subscriber")?; - } - - let (rx, drop_guard) = self.ensure_shard_listen(shard).await; - - Ok(Box::new(PostgresQueueSubscriber { - subject: subject.to_string(), - subject_hash, - queue_hash, - sub_id, - pool: self.pool.clone(), - rx, - _drop_guard: drop_guard, - })) - } - - async fn publish( - &self, - subject: &str, - payload: &[u8], - reply_subject: Option<&str>, - ) -> Result<()> { - let subject_hash = self.hash_subject(subject); - let shard = shard_for(&subject_hash); - - // Request semantics: if a reply is expected and no responder exists anywhere, - // surface a no-responders result immediately instead of persisting a message - // nobody will read. - if let Some(reply_subject) = reply_subject { - match self.has_responders(&subject_hash, subject).await { - Result::Ok(false) => { - self.signal_no_responders(reply_subject).await; - return Ok(()); - } - Result::Ok(true) => {} - Result::Err(e) => { - // On a failed check, fall through to a normal publish rather than - // risk a false no-responders result. - tracing::warn!(?e, %subject, "responder check failed, publishing anyway"); - } - } - } - - // Persist the message, retrying on transient connection errors. The row is - // committed before the doorbell rings so any wakeup observes it. - let mut backoff = Backoff::default(); - loop { - match self - .try_publish_to_db(subject, &subject_hash, payload) - .await - { - Result::Ok(()) => break, - Result::Err(e) => { - if !backoff.tick().await { - tracing::warn!(?e, %subject, "failed to publish, cannot retry again"); - return Err(e); - } - tracing::debug!(?e, "publish failed, retrying"); - } - } - } - - // Ring the doorbell. Best-effort: the subscriber poll backstop covers a - // dropped or coalesced wakeup, so publish never blocks on NOTIFY. - self.doorbell.mark_dirty(shard); - - Ok(()) - } - - async fn flush(&self) -> Result<()> { - Ok(()) - } - - fn max_message_size(&self) -> usize { - POSTGRES_MAX_MESSAGE_SIZE - } -} - -pub struct PostgresSubscriber { - subject: String, - subject_hash: String, - pool: Arc, - cursor: i64, - buffer: VecDeque>, - rx: broadcast::Receiver, - /// Responder-registry row id, present for non-inbox subscriptions. Deleted on drop. - sub_id: Option, - _drop_guard: tokio_util::sync::DropGuard, -} - -impl PostgresSubscriber { - /// Reads new rows past the cursor into the buffer, advancing the cursor. Rows - /// whose stored subject does not match are skipped (DefaultHasher collisions) but - /// still advance the cursor. - async fn fetch(&mut self) -> Result<()> { - let conn = self - .pool - .get() - .await - .context("failed to get connection for poll")?; - let rows = conn - .query( - "SELECT id, subject, payload FROM ups_messages \ - WHERE subject_hash = $1 AND id > $2 ORDER BY id", - &[&self.subject_hash, &self.cursor], - ) - .await - .context("failed to poll broadcast messages")?; - - for row in rows { - let id: i64 = row.get(0); - let subject: String = row.get(1); - let payload: Vec = row.get(2); - self.cursor = id; - if subject == self.subject { - self.buffer.push_back(payload); - } - } - - Ok(()) - } -} - -#[async_trait] -impl SubscriberDriver for PostgresSubscriber { - async fn next(&mut self) -> Result { - loop { - if let Some(payload) = self.buffer.pop_front() { - return Ok(DriverOutput::Message { - subject: self.subject.clone(), - payload, - }); - } - - if let Err(e) = self.fetch().await { - // Transient DB errors must not kill the subscriber; the next poll - // tick retries. - tracing::warn!(?e, subject = %self.subject, "failed to poll, will retry"); - } - - if !self.buffer.is_empty() { - continue; - } - - // Wait for a doorbell wakeup, a no-responders signal, or the poll backstop. - tokio::select! { - res = self.rx.recv() => { - match res { - std::result::Result::Ok(ShardSignal::Wakeup) => {} - std::result::Result::Ok(ShardSignal::NoResponders { subject }) - if subject == self.subject => - { - return Ok(DriverOutput::NoResponders); - } - std::result::Result::Ok(ShardSignal::NoResponders { .. }) => {} - Err(broadcast::error::RecvError::Lagged(_)) => {} - Err(broadcast::error::RecvError::Closed) => { - return Ok(DriverOutput::Unsubscribed); - } - } - } - _ = tokio::time::sleep(POLL_INTERVAL) => {} - } - } - } -} - -impl Drop for PostgresSubscriber { - fn drop(&mut self) { - let Some(sub_id) = self.sub_id.take() else { - return; - }; - let pool = self.pool.clone(); - tokio::spawn(async move { - if let Ok(conn) = pool.get().await { - if let Err(e) = conn - .execute("DELETE FROM ups_subs WHERE id = $1", &[&sub_id]) - .await - { - tracing::warn!(?e, %sub_id, "failed to deregister subscriber"); - } - } - }); - } -} - -pub struct PostgresQueueSubscriber { - subject: String, - subject_hash: String, - queue_hash: String, - sub_id: String, - pool: Arc, - rx: broadcast::Receiver, - _drop_guard: tokio_util::sync::DropGuard, -} - -impl PostgresQueueSubscriber { - /// Attempts to atomically claim and delete one pending message for this (subject, queue). - async fn claim_message(&self) -> Result>> { - let conn = self - .pool - .get() - .await - .context("failed to get connection for queue claim")?; - - let rows = conn - .query( - "WITH claimed AS ( \ - SELECT id, payload FROM ups_queue_messages \ - WHERE subject_hash = $1 AND queue_hash = $2 \ - ORDER BY id \ - LIMIT 1 \ - FOR UPDATE SKIP LOCKED \ - ) \ - DELETE FROM ups_queue_messages \ - WHERE id IN (SELECT id FROM claimed) \ - RETURNING payload", - &[&self.subject_hash, &self.queue_hash], - ) - .await - .context("failed to claim queue message")?; - - Ok(rows.into_iter().next().map(|row| row.get::<_, Vec>(0))) - } -} - -#[async_trait] -impl SubscriberDriver for PostgresQueueSubscriber { - async fn next(&mut self) -> Result { - loop { - // Drain any messages that arrived before or between wakeups. - match self.claim_message().await { - Result::Ok(Some(payload)) => { - return Ok(DriverOutput::Message { - subject: self.subject.clone(), - payload, - }); - } - Result::Ok(None) => {} - Result::Err(e) => { - tracing::warn!(?e, subject = %self.subject, "failed to claim, will retry"); - } - } - - // Wait for any shard signal or the poll backstop, then loop back to claim. - tokio::select! { - res = self.rx.recv() => { - match res { - std::result::Result::Ok(_) => {} - Err(broadcast::error::RecvError::Lagged(_)) => {} - Err(broadcast::error::RecvError::Closed) => { - return Ok(DriverOutput::Unsubscribed); - } - } - } - _ = tokio::time::sleep(POLL_INTERVAL) => {} - } - } - } -} - -impl Drop for PostgresQueueSubscriber { - fn drop(&mut self) { - let pool = self.pool.clone(); - let sub_id = self.sub_id.clone(); - tokio::spawn(async move { - if let Ok(conn) = pool.get().await { - if let Err(e) = conn - .execute("DELETE FROM ups_queue_subs WHERE id = $1", &[&sub_id]) - .await - { - tracing::warn!(?e, %sub_id, "failed to deregister queue subscriber"); - } - } - }); - } -} diff --git a/engine/packages/universalpubsub/src/metrics.rs b/engine/packages/universalpubsub/src/metrics.rs index 9c508e963a..93edf9eb1e 100644 --- a/engine/packages/universalpubsub/src/metrics.rs +++ b/engine/packages/universalpubsub/src/metrics.rs @@ -25,13 +25,6 @@ lazy_static::lazy_static! { "Number of subject entries in the memory driver.", *REGISTRY ).unwrap(); - // Postgres driver metrics - pub static ref POSTGRES_SUBSCRIPTION_COUNT: IntGauge = register_int_gauge_with_registry!( - "ups_postgres_subscription_count", - "Number of subscription entries in the postgres driver.", - *REGISTRY - ).unwrap(); - // Message metrics pub static ref MESSAGE_RECV_COUNT: IntCounterVec = register_int_counter_vec_with_registry!( "ups_message_recv_count", diff --git a/engine/packages/universalpubsub/tests/integration.rs b/engine/packages/universalpubsub/tests/integration.rs index 368fd4a537..f849935e45 100644 --- a/engine/packages/universalpubsub/tests/integration.rs +++ b/engine/packages/universalpubsub/tests/integration.rs @@ -1,7 +1,7 @@ use anyhow::Result; use futures_util::StreamExt; use rivet_error::RivetError; -use rivet_test_deps_docker::{TestDatabase, TestPubSub}; +use rivet_test_deps_docker::TestPubSub; use std::{ sync::Arc, time::{Duration, Instant}, @@ -119,29 +119,6 @@ async fn test_nats_no_responders() { test_no_responders(&pubsub).await.unwrap(); } -#[tokio::test] -async fn test_postgres_no_responders() { - setup_logging(); - - let test_id = Uuid::new_v4(); - let (db_config, docker_config) = TestDatabase::Postgres.config(test_id, 1).await.unwrap(); - let mut docker = docker_config.unwrap(); - docker.start().await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!(); - }; - let url = pg.url.read().clone(); - - let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) - .await - .unwrap(); - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - - test_no_responders(&pubsub).await.unwrap(); -} - #[tokio::test] async fn test_memory_no_responders() { setup_logging(); @@ -158,52 +135,6 @@ async fn test_memory_no_responders() { test_no_responders(&pubsub).await.unwrap(); } -#[tokio::test] -async fn test_postgres_driver_with_memory() { - setup_logging(); - - let test_id = Uuid::new_v4(); - let (db_config, docker_config) = TestDatabase::Postgres.config(test_id, 1).await.unwrap(); - let mut docker = docker_config.unwrap(); - docker.start().await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!(); - }; - let url = pg.url.read().clone(); - - let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) - .await - .unwrap(); - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), true); - - test_inner(&pubsub).await; -} - -#[tokio::test] -async fn test_postgres_driver_without_memory() { - setup_logging(); - - let test_id = Uuid::new_v4(); - let (db_config, docker_config) = TestDatabase::Postgres.config(test_id, 1).await.unwrap(); - let mut docker = docker_config.unwrap(); - docker.start().await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!(); - }; - let url = pg.url.read().clone(); - - let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) - .await - .unwrap(); - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - - test_inner(&pubsub).await; -} - #[tokio::test] async fn test_memory_driver() { setup_logging(); diff --git a/engine/packages/universalpubsub/tests/reconnect.rs b/engine/packages/universalpubsub/tests/reconnect.rs index 8c15f6ab54..c08892d8d3 100644 --- a/engine/packages/universalpubsub/tests/reconnect.rs +++ b/engine/packages/universalpubsub/tests/reconnect.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use rivet_test_deps_docker::{TestDatabase, TestPubSub}; +use rivet_test_deps_docker::TestPubSub; use std::{sync::Arc, time::Duration}; use universalpubsub::{NextOutput, PubSub, PublishOpts}; use uuid::Uuid; @@ -80,52 +80,6 @@ async fn test_nats_driver_without_memory_reconnect() { test_all_inner(&pubsub, &docker, true).await; } -#[tokio::test] -async fn test_postgres_driver_with_memory_reconnect() { - setup_logging(); - - let test_id = Uuid::new_v4(); - let (db_config, docker_config) = TestDatabase::Postgres.config(test_id, 1).await.unwrap(); - let mut docker = docker_config.unwrap(); - docker.start().await.unwrap(); - tokio::time::sleep(Duration::from_secs(5)).await; - - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!(); - }; - let url = pg.url.read().clone(); - - let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) - .await - .unwrap(); - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), true); - - test_all_inner(&pubsub, &docker, false).await; -} - -#[tokio::test] -async fn test_postgres_driver_without_memory_reconnect() { - setup_logging(); - - let test_id = Uuid::new_v4(); - let (db_config, docker_config) = TestDatabase::Postgres.config(test_id, 1).await.unwrap(); - let mut docker = docker_config.unwrap(); - docker.start().await.unwrap(); - tokio::time::sleep(Duration::from_secs(5)).await; - - let rivet_config::config::Database::Postgres(pg) = db_config else { - unreachable!(); - }; - let url = pg.url.read().clone(); - - let driver = universalpubsub::driver::postgres::PostgresDriver::connect(url, None, None, None) - .await - .unwrap(); - let pubsub = PubSub::new_with_memory_optimization(Arc::new(driver), false); - - test_all_inner(&pubsub, &docker, false).await; -} - async fn test_all_inner( pubsub: &PubSub, docker: &rivet_test_deps_docker::DockerRunConfig, diff --git a/engine/sdks/rust/universaldb-commit/src/versioned.rs b/engine/sdks/rust/universaldb-commit/src/versioned.rs index a9d637aa81..804f533a64 100644 --- a/engine/sdks/rust/universaldb-commit/src/versioned.rs +++ b/engine/sdks/rust/universaldb-commit/src/versioned.rs @@ -36,3 +36,65 @@ impl OwnedVersionedData for CommitRequest { } } } + +pub enum CommitReply { + V1(v1::CommitReply), +} + +impl OwnedVersionedData for CommitReply { + type Latest = v1::CommitReply; + + fn wrap_latest(latest: v1::CommitReply) -> Self { + CommitReply::V1(latest) + } + + fn unwrap_latest(self) -> Result { + match self { + CommitReply::V1(data) => Ok(data), + } + } + + fn deserialize_version(payload: &[u8], version: u16) -> Result { + match version { + 1 => Ok(CommitReply::V1(serde_bare::from_slice(payload)?)), + _ => bail!("invalid version: {version}"), + } + } + + fn serialize_version(self, _version: u16) -> Result> { + match self { + CommitReply::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + } + } +} + +pub enum Watermark { + V1(v1::Watermark), +} + +impl OwnedVersionedData for Watermark { + type Latest = v1::Watermark; + + fn wrap_latest(latest: v1::Watermark) -> Self { + Watermark::V1(latest) + } + + fn unwrap_latest(self) -> Result { + match self { + Watermark::V1(data) => Ok(data), + } + } + + fn deserialize_version(payload: &[u8], version: u16) -> Result { + match version { + 1 => Ok(Watermark::V1(serde_bare::from_slice(payload)?)), + _ => bail!("invalid version: {version}"), + } + } + + fn serialize_version(self, _version: u16) -> Result> { + match self { + Watermark::V1(data) => serde_bare::to_vec(&data).map_err(Into::into), + } + } +} diff --git a/engine/sdks/schemas/universaldb-commit/v1.bare b/engine/sdks/schemas/universaldb-commit/v1.bare index 2377312f84..c85d43f447 100644 --- a/engine/sdks/schemas/universaldb-commit/v1.bare +++ b/engine/sdks/schemas/universaldb-commit/v1.bare @@ -1,9 +1,9 @@ -# Commit-queue wire format for the Postgres leader-resolver UDB driver. +# Commit wire format for the multi-node Postgres leader-resolver UDB driver. # -# Followers encode a CommitRequest into the `payload` column of -# `udb_commit_requests`; the leader decodes it to resolve and apply. -# Rust-only (never leaves the engine), but versioned so rolling deploys can -# skew follower vs leader code. +# In multi-node mode a follower encodes a CommitRequest and sends it to the +# elected leader over NATS request/reply; the leader decodes it to resolve and +# apply. Rust-only (never leaves the engine), but versioned so rolling deploys +# can skew follower vs leader code. type ConflictRangeType enum { READ @@ -67,4 +67,32 @@ type CommitRequest struct { readVersion: u64 conflictRanges: list operations: list + # Failover dedup key. `clientNodeId` is the submitting follower's node id and + # `clientSeq` a per-process monotonic counter, unique per logical commit. The + # leader records committed (clientNodeId, clientSeq) so a follower that resends + # the same commit after an indeterminate leader failure is applied exactly once. + clientNodeId: data + clientSeq: u64 +} + +# Reply the leader sends back to the follower over the NATS request/reply inbox +# after resolving a commit. `CommitCommitted` carries the assigned commit +# version; `CommitConflict` (read-write conflict or cold-window reject) carries +# nothing and maps to the retryable NotCommitted on the follower. +type CommitCommitted struct { + commitVersion: i64 +} + +type CommitConflict void + +type CommitReply union { + CommitCommitted | + CommitConflict +} + +# Durable-version watermark the leader broadcasts on every applied batch so +# followers advance their read-snapshot floor. Best-effort; a lease-row poll is +# the backstop. +type Watermark struct { + durableVersion: i64 } diff --git a/self-host/compose/template/src/docker-compose.ts b/self-host/compose/template/src/docker-compose.ts index 2245a31c75..703559ced7 100644 --- a/self-host/compose/template/src/docker-compose.ts +++ b/self-host/compose/template/src/docker-compose.ts @@ -120,7 +120,12 @@ export function generateDockerCompose(context: TemplateContext) { const dcEnginePeerNetworkName = `${dcNetworkName}-engine-peer`; const dcToCoreNetworkName = `${dcNetworkName}-to-core`; - //const natsServiceName = context.getServiceName("nats", datacenter.name); + // A datacenter with more than one engine needs NATS: the engines coordinate the UniversalDB + // leader/follower commit transport and the UPS pubsub across nodes through it. A single-engine + // datacenter runs UniversalDB single-node (in-process resolver) and UPS in-process Memory, so it + // needs no NATS. + const useNats = datacenter.engines > 1; + const natsServiceName = context.getServiceName("nats", datacenter.name); const vectorServerServiceName = context.getServiceName( "vector-server", datacenter.name, @@ -154,18 +159,25 @@ export function generateDockerCompose(context: TemplateContext) { driver: "bridge", }; - //services[natsServiceName] = { - // restart: "unless-stopped", - // image: "nats:2.10.22-scratch", - // networks: [dcNetworkName], - // ports: isPrimary ? [`4222:4222`] : undefined, - // healthcheck: { - // test: ["CMD", "nats-server", "--health"], - // interval: "2s", - // timeout: "10s", - // retries: 10, - // }, - //}; + if (useNats) { + services[natsServiceName] = { + restart: "unless-stopped", + image: "nats:2.10.22-alpine", + // Enable the HTTP monitoring port so the healthcheck can hit /healthz. + command: ["-m", "8222"], + networks: [dcNetworkName], + ports: isPrimary ? [`4222:4222`] : undefined, + healthcheck: { + test: [ + "CMD-SHELL", + "wget -q -O /dev/null http://127.0.0.1:8222/healthz || exit 1", + ], + interval: "2s", + timeout: "10s", + retries: 10, + }, + }; + } const postgresVolumeName = context.getVolumeName( "postgres", @@ -174,9 +186,9 @@ export function generateDockerCompose(context: TemplateContext) { services[postgresServiceName] = { restart: "unless-stopped", image: "postgres:18-alpine", - // Each engine opens a UDB connection pool (up to 64 connections) plus a - // dedicated LISTEN connection and pubsub, so a multi-engine datacenter - // needs far more than the default max_connections of 100. + // Each engine opens a UDB connection pool (up to 64 connections), so a + // multi-engine datacenter needs far more than the default max_connections + // of 100. command: ["postgres", "-c", "max_connections=500"], environment: [ "POSTGRES_USER=postgres", @@ -213,7 +225,9 @@ export function generateDockerCompose(context: TemplateContext) { command: "infinity", stop_grace_period: "0s", depends_on: { - //[natsServiceName]: { condition: "service_healthy" }, + ...(useNats + ? { [natsServiceName]: { condition: "service_healthy" } } + : {}), [postgresServiceName]: { condition: "service_healthy" }, }, volumes: [ @@ -300,7 +314,9 @@ export function generateDockerCompose(context: TemplateContext) { ], stop_grace_period: "0s", depends_on: { - //[natsServiceName]: { condition: "service_healthy" }, + ...(useNats + ? { [natsServiceName]: { condition: "service_healthy" } } + : {}), [vectorClientServiceName]: { condition: "service_started", }, diff --git a/self-host/compose/template/src/services/edge/rivet-engine.ts b/self-host/compose/template/src/services/edge/rivet-engine.ts index 52d33dfc1b..1c5b8247da 100644 --- a/self-host/compose/template/src/services/edge/rivet-engine.ts +++ b/self-host/compose/template/src/services/edge/rivet-engine.ts @@ -37,7 +37,7 @@ export function generateDatacenterRivetEngine( // Config structure matching Rust schema in engine/packages/config/src/config/mod.rs. // Values that match the engine's defaults are omitted. - const config = { + const config: Record = { auth: { admin_token: "dev", }, @@ -56,6 +56,16 @@ export function generateDatacenterRivetEngine( }, }; + // A multi-engine datacenter coordinates through NATS: UPS uses it for cross-node pubsub, and + // UniversalDB inherits this config (see Root::validate_and_set_defaults) to run multi-node, + // routing follower commits to the elected leader over NATS. A single-engine datacenter omits + // this and falls back to in-process Memory pubsub + single-node UniversalDB. + if (datacenter.engines > 1) { + config.nats = { + addresses: [`${context.getServiceHost("nats", datacenter.name)}:4222`], + }; + } + context.writeDatacenterServiceFile( "rivet-engine", datacenter.name, diff --git a/self-host/dev-host/docker-compose.yml b/self-host/dev-host/docker-compose.yml index 94619e9324..fc44c89a26 100644 --- a/self-host/dev-host/docker-compose.yml +++ b/self-host/dev-host/docker-compose.yml @@ -157,6 +157,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://127.0.0.1:4317 diff --git a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc index 0532bce5c3..29ca3ee404 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-a:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc index 0532bce5c3..29ca3ee404 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-a:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc index 0532bce5c3..29ca3ee404 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-a:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc index e75a22f77c..0fe743310a 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-b:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc index e75a22f77c..0fe743310a 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-b:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc index e75a22f77c..0fe743310a 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-b:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc index cb4c587a19..5a639b2fd4 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-c:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc index cb4c587a19..5a639b2fd4 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-c:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc index cb4c587a19..5a639b2fd4 100644 --- a/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc +++ b/self-host/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc @@ -51,5 +51,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats-dc-c:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multidc-multinode/docker-compose.yml b/self-host/dev-multidc-multinode/docker-compose.yml index e4ccd4fc70..81b39d39eb 100644 --- a/self-host/dev-multidc-multinode/docker-compose.yml +++ b/self-host/dev-multidc-multinode/docker-compose.yml @@ -83,6 +83,23 @@ services: condition: service_healthy prometheus: condition: service_healthy + nats-dc-a: + restart: unless-stopped + image: nats:2.10.22-alpine + command: + - '-m' + - '8222' + networks: + - rivet-network-dc-a + ports: + - '4222:4222' + healthcheck: + test: + - CMD-SHELL + - wget -q -O /dev/null http://127.0.0.1:8222/healthz || exit 1 + interval: 2s + timeout: 10s + retries: 10 postgres-dc-a: restart: unless-stopped image: postgres:18-alpine @@ -122,6 +139,8 @@ services: command: infinity stop_grace_period: 0s depends_on: + nats-dc-a: + condition: service_healthy postgres-dc-a: condition: service_healthy volumes: @@ -187,11 +206,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-a:4317 stop_grace_period: 0s depends_on: + nats-dc-a: + condition: service_healthy vector-client-dc-a: condition: service_started otel-collector-dc-a: @@ -230,11 +252,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-a:4317 stop_grace_period: 0s depends_on: + nats-dc-a: + condition: service_healthy vector-client-dc-a: condition: service_started otel-collector-dc-a: @@ -271,11 +296,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-a:4317 stop_grace_period: 0s depends_on: + nats-dc-a: + condition: service_healthy vector-client-dc-a: condition: service_started otel-collector-dc-a: @@ -372,6 +400,21 @@ services: condition: service_completed_successfully networks: - rivet-network-dc-a + nats-dc-b: + restart: unless-stopped + image: nats:2.10.22-alpine + command: + - '-m' + - '8222' + networks: + - rivet-network-dc-b + healthcheck: + test: + - CMD-SHELL + - wget -q -O /dev/null http://127.0.0.1:8222/healthz || exit 1 + interval: 2s + timeout: 10s + retries: 10 postgres-dc-b: restart: unless-stopped image: postgres:18-alpine @@ -409,6 +452,8 @@ services: command: infinity stop_grace_period: 0s depends_on: + nats-dc-b: + condition: service_healthy postgres-dc-b: condition: service_healthy volumes: @@ -472,11 +517,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-b:4317 stop_grace_period: 0s depends_on: + nats-dc-b: + condition: service_healthy vector-client-dc-b: condition: service_started otel-collector-dc-b: @@ -513,11 +561,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-b:4317 stop_grace_period: 0s depends_on: + nats-dc-b: + condition: service_healthy vector-client-dc-b: condition: service_started otel-collector-dc-b: @@ -554,11 +605,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-b:4317 stop_grace_period: 0s depends_on: + nats-dc-b: + condition: service_healthy vector-client-dc-b: condition: service_started otel-collector-dc-b: @@ -653,6 +707,21 @@ services: condition: service_completed_successfully networks: - rivet-network-dc-b + nats-dc-c: + restart: unless-stopped + image: nats:2.10.22-alpine + command: + - '-m' + - '8222' + networks: + - rivet-network-dc-c + healthcheck: + test: + - CMD-SHELL + - wget -q -O /dev/null http://127.0.0.1:8222/healthz || exit 1 + interval: 2s + timeout: 10s + retries: 10 postgres-dc-c: restart: unless-stopped image: postgres:18-alpine @@ -690,6 +759,8 @@ services: command: infinity stop_grace_period: 0s depends_on: + nats-dc-c: + condition: service_healthy postgres-dc-c: condition: service_healthy volumes: @@ -753,11 +824,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-c:4317 stop_grace_period: 0s depends_on: + nats-dc-c: + condition: service_healthy vector-client-dc-c: condition: service_started otel-collector-dc-c: @@ -794,11 +868,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-c:4317 stop_grace_period: 0s depends_on: + nats-dc-c: + condition: service_healthy vector-client-dc-c: condition: service_started otel-collector-dc-c: @@ -835,11 +912,14 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-c:4317 stop_grace_period: 0s depends_on: + nats-dc-c: + condition: service_healthy vector-client-dc-c: condition: service_started otel-collector-dc-c: diff --git a/self-host/dev-multidc/docker-compose.yml b/self-host/dev-multidc/docker-compose.yml index 437ad5bdee..77938647f1 100644 --- a/self-host/dev-multidc/docker-compose.yml +++ b/self-host/dev-multidc/docker-compose.yml @@ -186,6 +186,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-a:4317 @@ -341,6 +342,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-b:4317 @@ -492,6 +494,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector-dc-c:4317 diff --git a/self-host/dev-multinode/docker-compose.yml b/self-host/dev-multinode/docker-compose.yml index 28234658d2..349b19edbc 100644 --- a/self-host/dev-multinode/docker-compose.yml +++ b/self-host/dev-multinode/docker-compose.yml @@ -79,6 +79,23 @@ services: condition: service_healthy prometheus: condition: service_healthy + nats: + restart: unless-stopped + image: nats:2.10.22-alpine + command: + - '-m' + - '8222' + networks: + - rivet-network + ports: + - '4222:4222' + healthcheck: + test: + - CMD-SHELL + - wget -q -O /dev/null http://127.0.0.1:8222/healthz || exit 1 + interval: 2s + timeout: 10s + retries: 10 postgres: restart: unless-stopped image: postgres:18-alpine @@ -117,6 +134,8 @@ services: command: infinity stop_grace_period: 0s depends_on: + nats: + condition: service_healthy postgres: condition: service_healthy volumes: @@ -185,6 +204,8 @@ services: - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 stop_grace_period: 0s depends_on: + nats: + condition: service_healthy vector-client: condition: service_started otel-collector: @@ -226,6 +247,8 @@ services: - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 stop_grace_period: 0s depends_on: + nats: + condition: service_healthy vector-client: condition: service_started otel-collector: @@ -265,6 +288,8 @@ services: - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 stop_grace_period: 0s depends_on: + nats: + condition: service_healthy vector-client: condition: service_started otel-collector: diff --git a/self-host/dev-multinode/rivet-engine/0/config.jsonc b/self-host/dev-multinode/rivet-engine/0/config.jsonc index b25680c399..13e9996d6e 100644 --- a/self-host/dev-multinode/rivet-engine/0/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/0/config.jsonc @@ -29,5 +29,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multinode/rivet-engine/1/config.jsonc b/self-host/dev-multinode/rivet-engine/1/config.jsonc index b25680c399..13e9996d6e 100644 --- a/self-host/dev-multinode/rivet-engine/1/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/1/config.jsonc @@ -29,5 +29,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev-multinode/rivet-engine/2/config.jsonc b/self-host/dev-multinode/rivet-engine/2/config.jsonc index b25680c399..13e9996d6e 100644 --- a/self-host/dev-multinode/rivet-engine/2/config.jsonc +++ b/self-host/dev-multinode/rivet-engine/2/config.jsonc @@ -29,5 +29,10 @@ "native_url": "http://clickhouse:9301", "username": "system", "password": "default" + }, + "nats": { + "addresses": [ + "nats:4222" + ] } } \ No newline at end of file diff --git a/self-host/dev/docker-compose.yml b/self-host/dev/docker-compose.yml index 7511952a55..2234f54130 100644 --- a/self-host/dev/docker-compose.yml +++ b/self-host/dev/docker-compose.yml @@ -179,6 +179,7 @@ services: restart: unless-stopped environment: - RUST_LOG_ANSI_COLOR=1 + - RUST_LOG=universaldb::driver::postgres=debug - RIVET_OTEL_ENABLED=1 - RIVET_OTEL_SAMPLER_RATIO=1 - RIVET_OTEL_GRPC_ENDPOINT=http://otel-collector:4317 From 711cbf5398b991d135f2fbc98c818558ae81d193 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Wed, 1 Jul 2026 13:53:08 -0700 Subject: [PATCH 17/19] [slopfix] fix(pegboard): decrement kv_storage_used metric on actor delete --- .../pegboard/src/workflows/actor/metrics.rs | 35 + .../pegboard/src/workflows/actor2/metrics.rs | 43 + examples/kitchen-sink/dist-server/server.mjs | 23013 ++++++++-------- .../kitchen-sink/dist-server/server.mjs.map | 2 +- .../kitchen-sink/scripts/seed-large-dbs.ts | 223 + .../src/actors/testing/grow-db.ts | 170 + examples/kitchen-sink/src/index.ts | 2 + 7 files changed, 12356 insertions(+), 11132 deletions(-) create mode 100644 examples/kitchen-sink/scripts/seed-large-dbs.ts create mode 100644 examples/kitchen-sink/src/actors/testing/grow-db.ts diff --git a/engine/packages/pegboard/src/workflows/actor/metrics.rs b/engine/packages/pegboard/src/workflows/actor/metrics.rs index 95becf10d8..fb4749edd4 100644 --- a/engine/packages/pegboard/src/workflows/actor/metrics.rs +++ b/engine/packages/pegboard/src/workflows/actor/metrics.rs @@ -120,6 +120,10 @@ pub(crate) async fn pegboard_actor_metrics(ctx: &mut WorkflowCtx, input: &Input) }) .await?; + // The actor's kv is deleted on destroy, so remove its contribution from the running kv + // storage total. + ctx.v(2).activity(RemoveKvMetricsInput {}).await?; + Ok(()) } @@ -323,6 +327,37 @@ async fn record_kv_metrics(ctx: &ActivityCtx, input: &RecordKvMetricsInput) -> R Ok(()) } +#[derive(Debug, Serialize, Deserialize, Hash)] +struct RemoveKvMetricsInput {} + +#[activity(RemoveKvMetrics)] +async fn remove_kv_metrics(ctx: &ActivityCtx, input: &RemoveKvMetricsInput) -> Result<()> { + let mut state = ctx.state::()?; + + let namespace_id = state.namespace_id; + let name = &state.name; + let last_kv_storage_size = state.last_kv_storage_size; + + if last_kv_storage_size != 0 { + ctx.udb()? + .txn("pegboard_actor_remove_kv_metrics", |tx| async move { + namespace::keys::metric::inc( + &tx.with_subspace(namespace::keys::subspace()), + namespace_id, + namespace::keys::metric::Metric::KvStorageUsed(name.to_string()), + -last_kv_storage_size, + ); + + Ok(()) + }) + .await?; + } + + state.last_kv_storage_size = 0; + + Ok(()) +} + #[signal("pegboard_actor_metrics_pause")] pub struct Pause { pub ts: i64, diff --git a/engine/packages/pegboard/src/workflows/actor2/metrics.rs b/engine/packages/pegboard/src/workflows/actor2/metrics.rs index 1944cb203d..e9caa66618 100644 --- a/engine/packages/pegboard/src/workflows/actor2/metrics.rs +++ b/engine/packages/pegboard/src/workflows/actor2/metrics.rs @@ -119,6 +119,10 @@ pub(crate) async fn pegboard_actor2_metrics(ctx: &mut WorkflowCtx, input: &Input }) .await?; + // The actor's kv is deleted on destroy, so remove its contribution from the running kv + // storage total. + ctx.v(2).activity(RemoveKvMetricsInput {}).await?; + Ok(()) } @@ -169,6 +173,8 @@ async fn record_metrics(ctx: &ActivityCtx, input: &RecordMetricsInput) -> Result let name = &state.name; ctx.udb()? .txn("pegboard_actor2_record_metrics", |tx| async move { + tx.priority(Priority::Low)?; + namespace::keys::metric::inc( &tx.with_subspace(namespace::keys::subspace()), namespace_id, @@ -212,6 +218,8 @@ async fn record_kv_metrics(ctx: &ActivityCtx, input: &RecordKvMetricsInput) -> R .txn("pegboard_actor2_kv_size_scan", |tx| { let last_key = &last_key; async move { + tx.priority(Priority::Low)?; + let start = Instant::now(); let tx = tx.with_subspace(keys::subspace()); @@ -296,6 +304,8 @@ async fn record_kv_metrics(ctx: &ActivityCtx, input: &RecordKvMetricsInput) -> R ctx.udb()? .txn("pegboard_actor2_record_kv_metrics", |tx| async move { + tx.priority(Priority::Low)?; + namespace::keys::metric::inc( &tx.with_subspace(namespace::keys::subspace()), namespace_id, @@ -312,6 +322,39 @@ async fn record_kv_metrics(ctx: &ActivityCtx, input: &RecordKvMetricsInput) -> R Ok(()) } +#[derive(Debug, Serialize, Deserialize, Hash)] +struct RemoveKvMetricsInput {} + +#[activity(RemoveKvMetrics)] +async fn remove_kv_metrics(ctx: &ActivityCtx, input: &RemoveKvMetricsInput) -> Result<()> { + let mut state = ctx.state::()?; + + let namespace_id = state.namespace_id; + let name = &state.name; + let last_kv_storage_size = state.last_kv_storage_size; + + if last_kv_storage_size != 0 { + ctx.udb()? + .txn("pegboard_actor2_remove_kv_metrics", |tx| async move { + tx.priority(Priority::Low)?; + + namespace::keys::metric::inc( + &tx.with_subspace(namespace::keys::subspace()), + namespace_id, + namespace::keys::metric::Metric::KvStorageUsed(name.to_string()), + -last_kv_storage_size, + ); + + Ok(()) + }) + .await?; + } + + state.last_kv_storage_size = 0; + + Ok(()) +} + #[signal("pegboard_actor_metrics_pause")] pub struct Pause { pub ts: i64, diff --git a/examples/kitchen-sink/dist-server/server.mjs b/examples/kitchen-sink/dist-server/server.mjs index 83c744a547..ac7fbedb6f 100644 --- a/examples/kitchen-sink/dist-server/server.mjs +++ b/examples/kitchen-sink/dist-server/server.mjs @@ -4,235 +4,95 @@ var __export = (target, all) => { __defProp(target, name, { get: all[name], enumerable: true }); }; +// src/server.ts +import { existsSync, readFileSync } from "fs"; +import { resolve } from "path"; +import * as v8 from "v8"; +import { serve } from "@hono/node-server"; +import { serveStatic } from "@hono/node-server/serve-static"; +import { Hono as Hono3 } from "hono"; + // src/index.ts import { setup as setup2 } from "rivetkit"; -// src/mode.ts -function resolveMode() { - const explicit = process.env.RIVET_KITCHEN_SINK_MODE; - if (explicit === "serverless" || explicit === "serverful" || explicit === "serverless-local") { - return explicit; - } - if (explicit !== void 0 && explicit !== "") { - throw new Error( - `RIVET_KITCHEN_SINK_MODE must be one of "serverless", "serverful", or "serverless-local" (got "${explicit}")` - ); - } - if (process.env.RIVET_RUN_ENGINE === "1") return "serverless-local"; - if (process.env.RIVET_SERVERLESS_URL !== void 0) return "serverless-local"; - if (process.env.KITCHEN_SINK_SERVERLESS_URL !== void 0) { - return "serverless-local"; - } - return "serverless"; -} - -// src/actors/counter/counter.ts -import { actor, event } from "rivetkit"; -var counter = actor({ - options: { - canHibernateWebSocket: false, - sleepGracePeriod: 5e3 - }, - state: { count: 0 }, - events: { - newCount: event() - }, - onWebSocket(_c, websocket) { - websocket.addEventListener("message", (event21) => { - if (websocket.readyState !== 1) return; - websocket.send(event21.data); - }); - }, - actions: { - increment: (c, x) => { - c.state.count += x; - c.broadcast("newCount", c.state.count); - return c.state.count; - }, - setCount: (c, x) => { - c.state.count = x; - c.broadcast("newCount", x); - return c.state.count; - }, - getCount: (c) => { - return c.state.count; - }, - noop: (_c) => { - return { ok: true }; - }, - goToSleep: (c) => { - c.sleep(); - return { ok: true }; - } - } -}); - -// src/actors/counter/counter-conn.ts -import { actor as actor2, event as event2 } from "rivetkit"; -var counterConn = actor2({ - state: { - connectionCount: 0 - }, - events: { - newCount: event2() - }, - connState: { count: 0 }, - onConnect: (c, conn) => { - c.state.connectionCount += 1; +// src/actors/actions/action-inputs.ts +import { actor } from "rivetkit"; +var inputActor = actor({ + createState: (_c, input) => { + return { + initialInput: input, + onCreateInput: void 0 + }; }, - onDisconnect: (c, conn) => { - c.state.connectionCount -= 1; + onCreate: (c, input) => { + c.state.onCreateInput = input; }, actions: { - increment: (c, x) => { - c.conn.state.count += x; - c.broadcast("newCount", c.conn.state.count); - }, - setCount: (c, x) => { - c.conn.state.count = x; - c.broadcast("newCount", x); - }, - getCount: (c) => { - return c.conn.state.count; - }, - getConnectionCount: (c) => { - return c.state.connectionCount; + getInputs: (c) => { + return { + initialInput: c.state.initialInput, + onCreateInput: c.state.onCreateInput + }; } } }); -// src/actors/counter/conn-params.ts -import { actor as actor3, event as event3 } from "rivetkit"; -var counterWithParams = actor3({ - state: { count: 0, initializers: [] }, - events: { - newCount: event3() - }, - createConnState: (c, params) => { - return { - name: params.name || "anonymous" - }; - }, - onConnect: (c, conn) => { - c.state.initializers.push(conn.state.name); +// src/actors/actions/action-timeout.ts +import { actor as actor2 } from "rivetkit"; +var shortTimeoutActor = actor2({ + state: { value: 0 }, + options: { + actionTimeout: 50 + // 50ms timeout }, actions: { - increment: (c, x) => { - c.state.count += x; - c.broadcast("newCount", { - count: c.state.count, - by: c.conn.state.name - }); - return c.state.count; + quickAction: async (_c) => { + return "quick response"; }, - getInitializers: (c) => { - return c.state.initializers; + slowAction: async (_c) => { + await new Promise((resolve2) => setTimeout(resolve2, 100)); + return "slow response"; } } }); - -// src/actors/counter/lifecycle.ts -import { actor as actor4 } from "rivetkit"; -var counterWithLifecycle = actor4({ - state: { - count: 0, - events: [] - }, - createConnState: (c, params) => ({ - joinTime: Date.now() - }), - onWake: (c) => { - c.state.events.push("onWake"); - }, - onSleep: async (c) => { - c.state.events.push("onSleep:start"); - await new Promise((resolve) => setTimeout(resolve, 1e3)); - c.state.events.push("onSleep:end"); - }, - onBeforeConnect: (c, params) => { - if (params?.trackLifecycle) c.state.events.push("onBeforeConnect"); - }, - onConnect: (c, conn) => { - if (conn.params?.trackLifecycle) c.state.events.push("onConnect"); - }, - onDisconnect: (c, conn) => { - if (conn.params?.trackLifecycle) c.state.events.push("onDisconnect"); +var longTimeoutActor = actor2({ + state: { value: 0 }, + options: { + actionTimeout: 200 + // 200ms timeout }, actions: { - getEvents: (c) => { - return c.state.events; - }, - increment: (c, x) => { - c.state.count += x; - return c.state.count; + delayedAction: async (_c) => { + await new Promise((resolve2) => setTimeout(resolve2, 100)); + return "delayed response"; } } }); - -// src/actors/counter/ping-pong-counter.ts -import { actor as actor5 } from "rivetkit"; -var pingPongCounter = actor5({ - state: { - pingCount: 0 - }, - onWebSocket(ctx, websocket) { - websocket.addEventListener("message", (event21) => { - const data = event21.data; - if (typeof data !== "string") return; - let parsed; - try { - parsed = JSON.parse(data); - } catch { - return; - } - if (parsed?.type === "ping") { - ctx.state.pingCount = ctx.state.pingCount + 1; - websocket.send( - JSON.stringify({ - type: "pong", - pingCount: ctx.state.pingCount, - timestamp: Date.now() - }) - ); - } - }); - }, +var defaultTimeoutActor = actor2({ + state: { value: 0 }, actions: { - getPingCount(c) { - return c.state.pingCount; - }, - resetPingCount(c) { - c.state.pingCount = 0; - return c.state.pingCount; + normalAction: async (_c) => { + await new Promise((resolve2) => setTimeout(resolve2, 50)); + return "normal response"; } } }); - -// src/actors/actions/action-inputs.ts -import { actor as actor6 } from "rivetkit"; -var inputActor = actor6({ - createState: (c, input) => { - return { - initialInput: input, - onCreateInput: void 0 - }; - }, - onCreate: (c, input) => { - c.state.onCreateInput = input; +var syncTimeoutActor = actor2({ + state: { value: 0 }, + options: { + actionTimeout: 50 + // 50ms timeout }, actions: { - getInputs: (c) => { - return { - initialInput: c.state.initialInput, - onCreateInput: c.state.onCreateInput - }; + syncAction: (_c) => { + return "sync response"; } } }); // src/actors/actions/action-types.ts -import { actor as actor7, UserError } from "rivetkit"; -var syncActionActor = actor7({ +import { actor as actor3, UserError } from "rivetkit"; +var syncActionActor = actor3({ state: { value: 0 }, actions: { // Simple synchronous action that returns a value directly @@ -253,7 +113,7 @@ var syncActionActor = actor7({ } } }); -var asyncActionActor = actor7({ +var asyncActionActor = actor3({ state: { value: 0, data: null }, actions: { // Async action with a delay @@ -270,7 +130,7 @@ var asyncActionActor = actor7({ return data; }, // Async action with error handling - asyncWithError: async (c, shouldError) => { + asyncWithError: async (_c, shouldError) => { await Promise.resolve(); if (shouldError) { throw new UserError("Intentional error"); @@ -279,22 +139,22 @@ var asyncActionActor = actor7({ } } }); -var promiseActor = actor7({ +var promiseActor = actor3({ state: { results: [] }, actions: { // Action that returns a resolved promise - resolvedPromise: (c) => { + resolvedPromise: (_c) => { return Promise.resolve("resolved value"); }, // Action that returns a promise that resolves after a delay delayedPromise: (c) => { - return new Promise((resolve) => { + return new Promise((resolve2) => { c.state.results.push("delayed"); - resolve("delayed value"); + resolve2("delayed value"); }); }, // Action that returns a rejected promise - rejectedPromise: (c) => { + rejectedPromise: (_c) => { return Promise.reject(new UserError("promised rejection")); }, // Action to check the collected results @@ -304,62 +164,9 @@ var promiseActor = actor7({ } }); -// src/actors/actions/action-timeout.ts -import { actor as actor8 } from "rivetkit"; -var shortTimeoutActor = actor8({ - state: { value: 0 }, - options: { - actionTimeout: 50 - // 50ms timeout - }, - actions: { - quickAction: async (c) => { - return "quick response"; - }, - slowAction: async (c) => { - await new Promise((resolve) => setTimeout(resolve, 100)); - return "slow response"; - } - } -}); -var longTimeoutActor = actor8({ - state: { value: 0 }, - options: { - actionTimeout: 200 - // 200ms timeout - }, - actions: { - delayedAction: async (c) => { - await new Promise((resolve) => setTimeout(resolve, 100)); - return "delayed response"; - } - } -}); -var defaultTimeoutActor = actor8({ - state: { value: 0 }, - actions: { - normalAction: async (c) => { - await new Promise((resolve) => setTimeout(resolve, 50)); - return "normal response"; - } - } -}); -var syncTimeoutActor = actor8({ - state: { value: 0 }, - options: { - actionTimeout: 50 - // 50ms timeout - }, - actions: { - syncAction: (c) => { - return "sync response"; - } - } -}); - // src/actors/actions/error-handling.ts -import { actor as actor9, UserError as UserError2 } from "rivetkit"; -var errorHandlingActor = actor9({ +import { actor as actor4, UserError as UserError2 } from "rivetkit"; +var errorHandlingActor = actor4({ state: { errorLog: [] }, @@ -387,18 +194,18 @@ var errorHandlingActor = actor9({ return "success"; }, // Action that times out (simulated with a long delay) - timeoutAction: async (c) => { - return new Promise((resolve) => { + timeoutAction: async (_c) => { + return new Promise((resolve2) => { setTimeout(() => { - resolve("This should not be reached if timeout works"); + resolve2("This should not be reached if timeout works"); }, 1e4); }); }, // Action with configurable delay to test timeout edge cases - delayedAction: async (c, delayMs) => { - return new Promise((resolve) => { + delayedAction: async (_c, delayMs) => { + return new Promise((resolve2) => { setTimeout(() => { - resolve(`Completed after ${delayMs}ms`); + resolve2(`Completed after ${delayMs}ms`); }, delayMs); }); }, @@ -422,15 +229,15 @@ var errorHandlingActor = actor9({ // 500ms timeout for actions } }); -var customTimeoutActor = actor9({ +var customTimeoutActor = actor4({ state: {}, actions: { quickAction: async () => { - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve2) => setTimeout(resolve2, 50)); return "Quick action completed"; }, slowAction: async () => { - await new Promise((resolve) => setTimeout(resolve, 300)); + await new Promise((resolve2) => setTimeout(resolve2, 300)); return "Slow action completed"; } }, @@ -440,707 +247,526 @@ var customTimeoutActor = actor9({ } }); -// src/actors/state/actor-onstatechange.ts -import { actor as actor10 } from "rivetkit"; -var onStateChangeActor = actor10({ - state: { - value: 0 - }, - vars: { - changeCount: 0 +// src/actors/ai/ai-agent.ts +import { openai } from "@ai-sdk/openai"; +import { generateText, tool } from "ai"; +import { actor as actor5, event } from "rivetkit"; +import { z } from "zod"; + +// src/actors/ai/my-tools.ts +async function getWeather(location) { + return { + location, + temperature: Math.floor(Math.random() * 30) + 10, + condition: ["sunny", "cloudy", "rainy", "snowy"][Math.floor(Math.random() * 4)], + humidity: Math.floor(Math.random() * 50) + 30 + }; +} + +// src/actors/ai/ai-agent.ts +var aiAgent = actor5({ + // Persistent state that survives restarts: https://rivet.dev/docs/actors/state + state: { + messages: [] + }, + events: { + messageReceived: event() }, actions: { - // Action that modifies state - should trigger onStateChange - setValue: (c, newValue) => { - c.state.value = newValue; - return c.state.value; - }, - // Action that modifies state multiple times - should trigger onStateChange for each change - incrementMultiple: (c, times) => { - for (let i = 0; i < times; i++) { - c.state.value++; - } - return c.state.value; - }, - // Action that doesn't modify state - should NOT trigger onStateChange - getValue: (c) => { - return c.state.value; - }, - // Action that reads and returns without modifying - should NOT trigger onStateChange - getDoubled: (c) => { - const doubled = c.state.value * 2; - return doubled; - }, - // Get the count of how many times onStateChange was called - getChangeCount: (c) => { - return c.vars.changeCount; - }, - // Reset change counter for testing - resetChangeCount: (c) => { - c.vars.changeCount = 0; + // Callable functions from clients: https://rivet.dev/docs/actors/actions + getMessages: (c) => c.state.messages, + sendMessage: async (c, userMessage) => { + const userMsg = { + role: "user", + content: userMessage, + timestamp: Date.now() + }; + c.state.messages.push(userMsg); + const { text: text2 } = await generateText({ + model: openai("gpt-4o-mini"), + prompt: userMessage, + messages: c.state.messages, + tools: { + weather: tool({ + description: "Get the weather in a location", + parameters: z.object({ + location: z.string().describe( + "The location to get the weather for" + ) + }), + execute: async ({ location }) => { + return await getWeather(location); + } + }) + } + }); + const assistantMsg = { + role: "assistant", + content: text2, + timestamp: Date.now() + }; + c.state.messages.push(assistantMsg); + c.broadcast("messageReceived", assistantMsg); + return assistantMsg; } - }, - // Track onStateChange calls - onStateChange: (c) => { - c.vars.changeCount++; } }); -// src/actors/state/metadata.ts -import { actor as actor11 } from "rivetkit"; -var metadataActor = actor11({ +// src/actors/connections/conn-state.ts +import { actor as actor6, event as event2 } from "rivetkit"; +var connStateActor = actor6({ state: { - lastMetadata: null, - actorName: "", - // Store tags and region in state for testing since they may not be - // available in the context in all environments - storedTags: {}, - storedRegion: null + sharedCounter: 0, + disconnectionCount: 0 }, - onWake: (c) => { - c.state.actorName = c.name; + events: { + userConnected: event2(), + userDisconnected: event2(), + directMessage: event2() + }, + // Define connection state + createConnState: (_c, params) => { + return { + username: params?.username || "anonymous", + role: params?.role || "user", + counter: 0, + createdAt: Date.now(), + noCount: params?.noCount ?? false + }; + }, + // Lifecycle hook when a connection is established + onConnect: (c, conn) => { + c.broadcast("userConnected", { + id: conn.id, + username: "anonymous", + role: "user" + }); + }, + // Lifecycle hook when a connection is closed + onDisconnect: (c, conn) => { + if (!conn.state?.noCount) { + c.state.disconnectionCount += 1; + c.broadcast("userDisconnected", { + id: conn.id + }); + } }, actions: { - // Set up test tags - this will be called by tests to simulate tags - setupTestTags: (c, tags) => { - c.state.storedTags = tags; - return tags; + // Action to increment the connection's counter + incrementConnCounter: (c, amount = 1) => { + c.conn.state.counter += amount; }, - // Set up test region - this will be called by tests to simulate region - setupTestRegion: (c, region) => { - c.state.storedRegion = region; - return region; + // Action to increment the shared counter + incrementSharedCounter: (c, amount = 1) => { + c.state.sharedCounter += amount; + return c.state.sharedCounter; }, - // Get all available metadata - getMetadata: (c) => { - const metadata = { - name: c.name, - tags: c.state.storedTags, - region: c.state.storedRegion - }; - c.state.lastMetadata = metadata; - return metadata; + // Get the connection state + getConnectionState: (c) => { + return { id: c.conn.id, ...c.conn.state }; }, - // Get the actor name - getActorName: (c) => { - return c.name; + // Check all active connections + getConnectionIds: (c) => { + return c.conns.entries().filter((c2) => !c2[1].state?.noCount).map((x) => x[0]).toArray(); }, - // Get a specific tag by key - getTag: (c, key) => { - return c.state.storedTags[key] || null; + // Get disconnection count + getDisconnectionCount: (c) => { + return c.state.disconnectionCount; }, - // Get all tags - getTags: (c) => { - return c.state.storedTags; + // Get all active connection states + getAllConnectionStates: (c) => { + return c.conns.entries().map(([id, conn]) => ({ id, ...conn.state })).toArray(); }, - // Get the region - getRegion: (c) => { - return c.state.storedRegion; + // Send message to a specific connection with matching ID + sendToConnection: (c, targetId, message) => { + if (c.conns.has(targetId)) { + c.conns.get(targetId)?.send("directMessage", { from: c.conn.id, message }); + return true; + } else { + return false; + } }, - // Get the stored actor name (from onWake) - getStoredActorName: (c) => { - return c.state.actorName; + // Update connection state (simulated for tests) + updateConnection: (c, updates) => { + if (updates.username) c.conn.state.username = updates.username; + if (updates.role) c.conn.state.role = updates.role; + return c.conn.state; }, - // Get last retrieved metadata - getLastMetadata: (c) => { - return c.state.lastMetadata; + disconnectSelf: (c, reason) => { + c.conn.disconnect(reason ?? "test.disconnect"); + return true; } } }); -// src/actors/state/vars.ts -import { actor as actor12 } from "rivetkit"; -var staticVarActor = actor12({ - state: { value: 0 }, - connState: { hello: "world" }, - vars: { counter: 42, name: "test-actor" }, - actions: { - getVars: (c) => { - return c.vars; - }, - getName: (c) => { - return c.vars.name; - } - } -}); -var nestedVarActor = actor12({ - state: { value: 0 }, - connState: { hello: "world" }, - vars: { - counter: 42, - nested: { - value: "original", - array: [1, 2, 3], - obj: { key: "value" } - } - }, - actions: { - getVars: (c) => { - return c.vars; - }, - modifyNested: (c) => { - c.vars.nested.value = "modified"; - c.vars.nested.array.push(4); - c.vars.nested.obj.key = "new-value"; - return c.vars; +// src/actors/connections/reject-connection.ts +import { actor as actor7, UserError as UserError3 } from "rivetkit"; +var rejectConnectionActor = actor7({ + onBeforeConnect: async (_c, params) => { + if (params?.reject) { + await new Promise((resolve2) => setTimeout(resolve2, 500)); + throw new UserError3("Rejected connection", { + code: "rejected" + }); } - } -}); -var dynamicVarActor = actor12({ - state: { value: 0 }, - connState: { hello: "world" }, - createVars: () => { - return { - random: Math.random(), - computed: `Actor-${Math.floor(Math.random() * 1e3)}` - }; }, actions: { - getVars: (c) => { - return c.vars; - } + ping: () => "pong" } }); -var uniqueVarActor = actor12({ - state: { value: 0 }, - connState: { hello: "world" }, - createVars: () => { - return { - id: Math.floor(Math.random() * 1e6) - }; - }, - actions: { - getVars: (c) => { - return c.vars; + +// src/actors/connections/request-access.ts +import { actor as actor8 } from "rivetkit"; +var requestAccessActor = actor8({ + state: { + // Track request info from different hooks + onBeforeConnectRequest: { + hasRequest: false, + requestUrl: null, + requestMethod: null, + requestHeaders: {} + }, + createConnStateRequest: { + hasRequest: false, + requestUrl: null, + requestMethod: null, + requestHeaders: {} + }, + onRequestRequest: { + hasRequest: false, + requestUrl: null, + requestMethod: null, + requestHeaders: {} + }, + onWebSocketRequest: { + hasRequest: false, + requestUrl: null, + requestMethod: null, + requestHeaders: {} + } + }, + createConnState: (c, params) => { + let requestInfo = null; + if (params?.trackRequest && c.request) { + const headers = {}; + c.request.headers.forEach((value, key) => { + headers[key] = value; + }); + requestInfo = { + hasRequest: true, + requestUrl: c.request.url, + requestMethod: c.request.method, + requestHeaders: headers + }; + } + return { + trackRequest: params?.trackRequest || false, + requestInfo + }; + }, + onConnect: (c, conn) => { + if (conn.state.requestInfo) { + c.state.createConnStateRequest = conn.state.requestInfo; + } + }, + onBeforeConnect: (c, params) => { + if (params?.trackRequest) { + if (c.request) { + c.state.onBeforeConnectRequest.hasRequest = true; + c.state.onBeforeConnectRequest.requestUrl = c.request.url; + c.state.onBeforeConnectRequest.requestMethod = c.request.method; + const headers = {}; + c.request.headers.forEach((value, key) => { + headers[key] = value; + }); + c.state.onBeforeConnectRequest.requestHeaders = headers; + } else { + c.state.onBeforeConnectRequest.hasRequest = false; + } + } + }, + onRequest: (c, request) => { + c.state.onRequestRequest.hasRequest = true; + c.state.onRequestRequest.requestUrl = request.url; + c.state.onRequestRequest.requestMethod = request.method; + const headers = {}; + request.headers.forEach((value, key) => { + headers[key] = value; + }); + c.state.onRequestRequest.requestHeaders = headers; + return new Response( + JSON.stringify({ + hasRequest: true, + requestUrl: request.url, + requestMethod: request.method, + requestHeaders: headers + }), + { + status: 200, + headers: { "Content-Type": "application/json" } + } + ); + }, + onWebSocket: (c, websocket) => { + if (!c.request) throw "Missing request"; + c.state.onWebSocketRequest.hasRequest = true; + c.state.onWebSocketRequest.requestUrl = c.request.url; + c.state.onWebSocketRequest.requestMethod = c.request.method; + const headers = {}; + c.request.headers.forEach((value, key) => { + headers[key] = value; + }); + c.state.onWebSocketRequest.requestHeaders = headers; + websocket.send( + JSON.stringify({ + hasRequest: true, + requestUrl: c.request.url, + requestMethod: c.request.method, + requestHeaders: headers + }) + ); + websocket.addEventListener("message", (event19) => { + websocket.send(event19.data); + }); + }, + actions: { + getRequestInfo: (c) => { + return { + onBeforeConnect: c.state.onBeforeConnectRequest, + createConnState: c.state.createConnStateRequest, + onRequest: c.state.onRequestRequest, + onWebSocket: c.state.onWebSocketRequest + }; } } }); -var driverCtxActor = actor12({ - state: { value: 0 }, - connState: { hello: "world" }, - createVars: (c, driverCtx) => { + +// src/actors/counter/conn-params.ts +import { actor as actor9, event as event3 } from "rivetkit"; +var counterWithParams = actor9({ + state: { count: 0, initializers: [] }, + events: { + newCount: event3() + }, + createConnState: (_c, params) => { return { - hasDriverCtx: Boolean(driverCtx?.isTest) + name: params.name || "anonymous" }; }, + onConnect: (c, conn) => { + c.state.initializers.push(conn.state.name); + }, actions: { - getVars: (c) => { - return c.vars; + increment: (c, x) => { + c.state.count += x; + c.broadcast("newCount", { + count: c.state.count, + by: c.conn.state.name + }); + return c.state.count; + }, + getInitializers: (c) => { + return c.state.initializers; } } }); -// src/actors/state/kv.ts -import { actor as actor13 } from "rivetkit"; -var kvActor = actor13({ +// src/actors/counter/counter.ts +import { + actor as actor10, + event as event4 +} from "rivetkit"; +var counter = actor10({ + options: { + canHibernateWebSocket: false, + sleepGracePeriod: 5e3 + }, + state: { count: 0 }, + events: { + newCount: event4() + }, + onWebSocket(_c, websocket) { + websocket.addEventListener("message", (event19) => { + if (websocket.readyState !== 1) return; + websocket.send(event19.data); + }); + }, actions: { - putText: async (c, key, value) => { - await c.kv.put(key, value); - return true; + increment: (c, x) => { + c.state.count += x; + c.broadcast("newCount", c.state.count); + return c.state.count; }, - getText: async (c, key) => { - return await c.kv.get(key); + setCount: (c, x) => { + c.state.count = x; + c.broadcast("newCount", x); + return c.state.count; }, - listText: async (c, prefix) => { - const results = await c.kv.list(prefix, { keyType: "text" }); - return results.map(([key, value]) => ({ - key, - value - })); + getCount: (c) => { + return c.state.count; }, - roundtripArrayBuffer: async (c, key, values) => { - const buffer = new Uint8Array(values).buffer; - await c.kv.put(key, buffer, { type: "arrayBuffer" }); - const result = await c.kv.get(key, { type: "arrayBuffer" }); - if (!result) { - return null; - } - return Array.from(new Uint8Array(result)); + noop: (_c) => { + return { ok: true }; + }, + goToSleep: (c) => { + c.sleep(); + return { ok: true }; } } }); -// src/actors/state/large-payloads.ts -import { actor as actor14 } from "rivetkit"; -var largePayloadActor = actor14({ - state: {}, +// src/actors/counter/counter-conn.ts +import { actor as actor11, event as event5 } from "rivetkit"; +var counterConn = actor11({ + state: { + connectionCount: 0 + }, + events: { + newCount: event5() + }, + connState: { count: 0 }, + onConnect: (c, _conn) => { + c.state.connectionCount += 1; + }, + onDisconnect: (c, _conn) => { + c.state.connectionCount -= 1; + }, actions: { - /** - * Accepts a large request payload and returns its size - */ - processLargeRequest: (c, data) => { - return { - itemCount: data.items.length, - firstItem: data.items[0], - lastItem: data.items[data.items.length - 1] - }; + increment: (c, x) => { + c.conn.state.count += x; + c.broadcast("newCount", c.conn.state.count); }, - /** - * Returns a large response payload - */ - getLargeResponse: (c, itemCount) => { - const items = []; - for (let i = 0; i < itemCount; i++) { - items.push(`Item ${i} with some additional text to increase size`); - } - return { items }; + setCount: (c, x) => { + c.conn.state.count = x; + c.broadcast("newCount", x); }, - /** - * Echo back the request data - */ - echo: (c, data) => { - return data; + getCount: (c) => { + return c.conn.state.count; + }, + getConnectionCount: (c) => { + return c.state.connectionCount; } } }); -var largePayloadConnActor = actor14({ - state: {}, - connState: { - lastRequestSize: 0 + +// src/actors/counter/lifecycle.ts +import { actor as actor12 } from "rivetkit"; +var counterWithLifecycle = actor12({ + state: { + count: 0, + events: [] }, - actions: { - /** - * Accepts a large request payload and returns its size - */ - processLargeRequest: (c, data) => { - c.conn.state.lastRequestSize = data.items.length; - return { - itemCount: data.items.length, - firstItem: data.items[0], - lastItem: data.items[data.items.length - 1] - }; - }, - /** - * Returns a large response payload - */ - getLargeResponse: (c, itemCount) => { - const items = []; - for (let i = 0; i < itemCount; i++) { - items.push(`Item ${i} with some additional text to increase size`); - } - return { items }; - }, - /** - * Echo back the request data - */ - echo: (c, data) => { - return data; - }, - /** - * Get the last request size - */ - getLastRequestSize: (c) => { - return c.conn.state.lastRequestSize; - } - } -}); - -// src/actors/state/sqlite-raw.ts -import { actor as actor15 } from "rivetkit"; -import { db } from "rivetkit/db"; -var sqliteRawActor = actor15({ - db: db({ - onMigrate: async (db16) => { - await db16.execute(` - CREATE TABLE IF NOT EXISTS todos ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - completed INTEGER DEFAULT 0, - created_at INTEGER NOT NULL - ) - `); - } + createConnState: (_c, _params) => ({ + joinTime: Date.now() }), + onWake: (c) => { + c.state.events.push("onWake"); + }, + onSleep: async (c) => { + c.state.events.push("onSleep:start"); + await new Promise((resolve2) => setTimeout(resolve2, 1e3)); + c.state.events.push("onSleep:end"); + }, + onBeforeConnect: (c, params) => { + if (params?.trackLifecycle) c.state.events.push("onBeforeConnect"); + }, + onConnect: (c, conn) => { + if (conn.params?.trackLifecycle) c.state.events.push("onConnect"); + }, + onDisconnect: (c, conn) => { + if (conn.params?.trackLifecycle) c.state.events.push("onDisconnect"); + }, actions: { - addTodo: async (c, title) => { - const createdAt = Date.now(); - await c.db.execute( - "INSERT INTO todos (title, created_at) VALUES (?, ?)", - title, - createdAt - ); - return { title, createdAt }; - }, - getTodos: async (c) => { - return await c.db.execute("SELECT * FROM todos ORDER BY created_at DESC"); - }, - toggleTodo: async (c, id) => { - await c.db.execute( - "UPDATE todos SET completed = NOT completed WHERE id = ?", - id - ); - const rows = await c.db.execute("SELECT * FROM todos WHERE id = ?", id); - return rows[0]; + getEvents: (c) => { + return c.state.events; }, - deleteTodo: async (c, id) => { - await c.db.execute("DELETE FROM todos WHERE id = ?", id); - return { deleted: id }; + increment: (c, x) => { + c.state.count += x; + return c.state.count; } } }); -// src/actors/state/sqlite-drizzle/mod.ts -import { actor as actor16 } from "rivetkit"; -import { db as db2 } from "rivetkit/db/drizzle"; -import { eq } from "drizzle-orm"; - -// src/actors/state/sqlite-drizzle/schema.ts -var schema_exports = {}; -__export(schema_exports, { - todos: () => todos -}); -import { sqliteTable, text, integer } from "rivetkit/db/drizzle"; -var todos = sqliteTable("todos", { - id: integer("id").primaryKey({ autoIncrement: true }), - title: text("title").notNull(), - completed: integer("completed").default(0), - createdAt: integer("created_at").notNull() -}); - -// src/actors/state/sqlite-drizzle/drizzle/meta/_journal.json -var journal_default = { - version: "7", - dialect: "sqlite", - entries: [ - { - idx: 0, - version: "6", - when: 1770921282251, - tag: "0000_left_wrecking_crew", - breakpoints: true - } - ] -}; - -// src/actors/state/sqlite-drizzle/drizzle/0000_left_wrecking_crew.sql -var left_wrecking_crew_default = "CREATE TABLE `todos` (\n `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n `title` text NOT NULL,\n `completed` integer DEFAULT 0,\n `created_at` integer NOT NULL\n);\n"; - -// src/actors/state/sqlite-drizzle/drizzle/migrations.js -var migrations_default = { - journal: journal_default, - migrations: { - m0000: left_wrecking_crew_default - } -}; - -// src/actors/state/sqlite-drizzle/mod.ts -var { todos: todos2 } = schema_exports; -var sqliteDrizzleActor = actor16({ - db: db2({ schema: schema_exports, migrations: migrations_default }), +// src/actors/counter/ping-pong-counter.ts +import { actor as actor13 } from "rivetkit"; +var pingPongCounter = actor13({ + state: { + pingCount: 0 + }, + onWebSocket(ctx, websocket) { + websocket.addEventListener("message", (event19) => { + const data = event19.data; + if (typeof data !== "string") return; + let parsed; + try { + parsed = JSON.parse(data); + } catch { + return; + } + if (parsed?.type === "ping") { + ctx.state.pingCount = ctx.state.pingCount + 1; + websocket.send( + JSON.stringify({ + type: "pong", + pingCount: ctx.state.pingCount, + timestamp: Date.now() + }) + ); + } + }); + }, actions: { - addTodo: async (c, title) => { - const result = await c.db.insert(todos2).values({ - title, - createdAt: Date.now() - }).returning(); - return result[0]; - }, - getTodos: async (c) => { - return await c.db.select().from(todos2).orderBy(todos2.createdAt); - }, - toggleTodo: async (c, id) => { - const existing = await c.db.select().from(todos2).where(eq(todos2.id, id)); - if (!existing[0]) return null; - const newCompleted = existing[0].completed ? 0 : 1; - const result = await c.db.update(todos2).set({ completed: newCompleted }).where(eq(todos2.id, id)).returning(); - return result[0]; + getPingCount(c) { + return c.state.pingCount; }, - deleteTodo: async (c, id) => { - await c.db.delete(todos2).where(eq(todos2.id, id)); - return { deleted: id }; + resetPingCount(c) { + c.state.pingCount = 0; + return c.state.pingCount; } } }); -// src/actors/state/parallelism-test.ts -import { actor as actor17, event as event4 } from "rivetkit"; -import { db as db3 } from "rivetkit/db"; -var parallelismTest = actor17({ +// src/actors/http/raw-fetch-counter.ts +import { Hono } from "hono"; +import { actor as actor14 } from "rivetkit"; +var rawFetchCounter = actor14({ state: { - stateCount: 0 + count: 0 }, - db: db3({ - onMigrate: async (db16) => { - await db16.execute(` - CREATE TABLE IF NOT EXISTS counter ( - id INTEGER PRIMARY KEY CHECK (id = 1), - count INTEGER NOT NULL DEFAULT 0 - ) - `); - await db16.execute(` - INSERT OR IGNORE INTO counter (id, count) VALUES (1, 0) - `); - } - }), - events: { - stateCountChanged: event4(), - sqliteCountChanged: event4() + createVars: () => { + return { router: createCounterRouter() }; }, - actions: { - incrementState: (c) => { - c.state.stateCount += 1; - c.broadcast("stateCountChanged", { count: c.state.stateCount }); - return { count: c.state.stateCount }; - }, - getStateCount: (c) => { - return { count: c.state.stateCount }; - }, - incrementSqlite: async (c) => { - await c.db.execute(`UPDATE counter SET count = count + 1 WHERE id = 1`); - const results = await c.db.execute( - `SELECT count FROM counter WHERE id = 1` - ); - const count = results[0].count; - c.broadcast("sqliteCountChanged", { count }); - return { count }; - }, - getSqliteCount: async (c) => { - const results = await c.db.execute( - `SELECT count FROM counter WHERE id = 1` - ); - return { count: results[0].count }; - } + onRequest: (c, request) => { + return c.vars.router.fetch(request, { actor: c }); }, - options: { - sleepTimeout: 3e4 + actions: { + // ...actions... } }); +function createCounterRouter() { + const app2 = new Hono(); + app2.get("/count", (c) => { + const { actor: actor62 } = c.env; + return c.json({ + count: actor62.state.count + }); + }); + app2.post("/increment", (c) => { + const { actor: actor62 } = c.env; + actor62.state.count++; + return c.json({ + count: actor62.state.count + }); + }); + return app2; +} -// src/actors/connections/conn-state.ts -import { actor as actor18, event as event5 } from "rivetkit"; -var connStateActor = actor18({ +// src/actors/http/raw-http.ts +import { Hono as Hono2 } from "hono"; +import { actor as actor15 } from "rivetkit"; +var rawHttpActor = actor15({ state: { - sharedCounter: 0, - disconnectionCount: 0 - }, - events: { - userConnected: event5(), - userDisconnected: event5(), - directMessage: event5() - }, - // Define connection state - createConnState: (c, params) => { - return { - username: params?.username || "anonymous", - role: params?.role || "user", - counter: 0, - createdAt: Date.now(), - noCount: params?.noCount ?? false - }; - }, - // Lifecycle hook when a connection is established - onConnect: (c, conn) => { - c.broadcast("userConnected", { - id: conn.id, - username: "anonymous", - role: "user" - }); - }, - // Lifecycle hook when a connection is closed - onDisconnect: (c, conn) => { - if (!conn.state?.noCount) { - c.state.disconnectionCount += 1; - c.broadcast("userDisconnected", { - id: conn.id - }); - } - }, - actions: { - // Action to increment the connection's counter - incrementConnCounter: (c, amount = 1) => { - c.conn.state.counter += amount; - }, - // Action to increment the shared counter - incrementSharedCounter: (c, amount = 1) => { - c.state.sharedCounter += amount; - return c.state.sharedCounter; - }, - // Get the connection state - getConnectionState: (c) => { - return { id: c.conn.id, ...c.conn.state }; - }, - // Check all active connections - getConnectionIds: (c) => { - return c.conns.entries().filter((c2) => !c2[1].state?.noCount).map((x) => x[0]).toArray(); - }, - // Get disconnection count - getDisconnectionCount: (c) => { - return c.state.disconnectionCount; - }, - // Get all active connection states - getAllConnectionStates: (c) => { - return c.conns.entries().map(([id, conn]) => ({ id, ...conn.state })).toArray(); - }, - // Send message to a specific connection with matching ID - sendToConnection: (c, targetId, message) => { - if (c.conns.has(targetId)) { - c.conns.get(targetId).send("directMessage", { from: c.conn.id, message }); - return true; - } else { - return false; - } - }, - // Update connection state (simulated for tests) - updateConnection: (c, updates) => { - if (updates.username) c.conn.state.username = updates.username; - if (updates.role) c.conn.state.role = updates.role; - return c.conn.state; - }, - disconnectSelf: (c, reason) => { - c.conn.disconnect(reason ?? "test.disconnect"); - return true; - } - } -}); - -// src/actors/connections/reject-connection.ts -import { actor as actor19, UserError as UserError3 } from "rivetkit"; -var rejectConnectionActor = actor19({ - onBeforeConnect: async (_c, params) => { - if (params?.reject) { - await new Promise((resolve) => setTimeout(resolve, 500)); - throw new UserError3("Rejected connection", { - code: "rejected" - }); - } - }, - actions: { - ping: () => "pong" - } -}); - -// src/actors/connections/request-access.ts -import { actor as actor20 } from "rivetkit"; -var requestAccessActor = actor20({ - state: { - // Track request info from different hooks - onBeforeConnectRequest: { - hasRequest: false, - requestUrl: null, - requestMethod: null, - requestHeaders: {} - }, - createConnStateRequest: { - hasRequest: false, - requestUrl: null, - requestMethod: null, - requestHeaders: {} - }, - onRequestRequest: { - hasRequest: false, - requestUrl: null, - requestMethod: null, - requestHeaders: {} - }, - onWebSocketRequest: { - hasRequest: false, - requestUrl: null, - requestMethod: null, - requestHeaders: {} - } - }, - createConnState: (c, params) => { - let requestInfo = null; - if (params?.trackRequest && c.request) { - const headers = {}; - c.request.headers.forEach((value, key) => { - headers[key] = value; - }); - requestInfo = { - hasRequest: true, - requestUrl: c.request.url, - requestMethod: c.request.method, - requestHeaders: headers - }; - } - return { - trackRequest: params?.trackRequest || false, - requestInfo - }; - }, - onConnect: (c, conn) => { - if (conn.state.requestInfo) { - c.state.createConnStateRequest = conn.state.requestInfo; - } - }, - onBeforeConnect: (c, params) => { - if (params?.trackRequest) { - if (c.request) { - c.state.onBeforeConnectRequest.hasRequest = true; - c.state.onBeforeConnectRequest.requestUrl = c.request.url; - c.state.onBeforeConnectRequest.requestMethod = c.request.method; - const headers = {}; - c.request.headers.forEach((value, key) => { - headers[key] = value; - }); - c.state.onBeforeConnectRequest.requestHeaders = headers; - } else { - c.state.onBeforeConnectRequest.hasRequest = false; - } - } - }, - onRequest: (c, request) => { - c.state.onRequestRequest.hasRequest = true; - c.state.onRequestRequest.requestUrl = request.url; - c.state.onRequestRequest.requestMethod = request.method; - const headers = {}; - request.headers.forEach((value, key) => { - headers[key] = value; - }); - c.state.onRequestRequest.requestHeaders = headers; - return new Response( - JSON.stringify({ - hasRequest: true, - requestUrl: request.url, - requestMethod: request.method, - requestHeaders: headers - }), - { - status: 200, - headers: { "Content-Type": "application/json" } - } - ); - }, - onWebSocket: (c, websocket) => { - if (!c.request) throw "Missing request"; - c.state.onWebSocketRequest.hasRequest = true; - c.state.onWebSocketRequest.requestUrl = c.request.url; - c.state.onWebSocketRequest.requestMethod = c.request.method; - const headers = {}; - c.request.headers.forEach((value, key) => { - headers[key] = value; - }); - c.state.onWebSocketRequest.requestHeaders = headers; - websocket.send( - JSON.stringify({ - hasRequest: true, - requestUrl: c.request.url, - requestMethod: c.request.method, - requestHeaders: headers - }) - ); - websocket.addEventListener("message", (event21) => { - websocket.send(event21.data); - }); - }, - actions: { - getRequestInfo: (c) => { - return { - onBeforeConnect: c.state.onBeforeConnectRequest, - createConnState: c.state.createConnStateRequest, - onRequest: c.state.onRequestRequest, - onWebSocket: c.state.onWebSocketRequest - }; - } - } -}); - -// src/actors/http/raw-http.ts -import { Hono } from "hono"; -import { actor as actor21 } from "rivetkit"; -var rawHttpActor = actor21({ - state: { - requestCount: 0 + requestCount: 0 }, onRequest(ctx, request) { const url = new URL(request.url); @@ -1182,18 +808,18 @@ var rawHttpActor = actor21({ }, actions: {} }); -var rawHttpNoHandlerActor = actor21({ +var rawHttpNoHandlerActor = actor15({ actions: {} }); -var rawHttpVoidReturnActor = actor21({ - onRequest(ctx, request) { +var rawHttpVoidReturnActor = actor15({ + onRequest(_ctx, _request) { return void 0; }, actions: {} }); -var rawHttpHonoActor = actor21({ +var rawHttpHonoActor = actor15({ createVars() { - const router = new Hono(); + const router = new Hono2(); router.get( "/", (c) => c.json({ message: "Welcome to Hono actor!" }) @@ -1208,7 +834,7 @@ var rawHttpHonoActor = actor21({ router.get("/users/:id", (c) => { const id = c.req.param("id"); return c.json({ - id: parseInt(id), + id: parseInt(id, 10), name: id === "1" ? "Alice" : "Bob" }); }); @@ -1219,7 +845,7 @@ var rawHttpHonoActor = actor21({ router.put("/users/:id", async (c) => { const id = c.req.param("id"); const body = await c.req.json(); - return c.json({ id: parseInt(id), ...body }); + return c.json({ id: parseInt(id, 10), ...body }); }); router.delete("/users/:id", (c) => { const id = c.req.param("id"); @@ -1234,10 +860,10 @@ var rawHttpHonoActor = actor21({ }); // src/actors/http/raw-http-request-properties.ts -import { actor as actor22 } from "rivetkit"; -var rawHttpRequestPropertiesActor = actor22({ +import { actor as actor16 } from "rivetkit"; +var rawHttpRequestPropertiesActor = actor16({ actions: {}, - onRequest(ctx, request) { + onRequest(_ctx, request) { const url = new URL(request.url); const method = request.method; const headers = {}; @@ -1257,7 +883,7 @@ var rawHttpRequestPropertiesActor = actor22({ const text2 = await request.text(); return text2 || null; } - } catch (error) { + } catch (_error) { return null; } }; @@ -1303,8 +929,8 @@ var rawHttpRequestPropertiesActor = actor22({ }); // src/actors/http/raw-websocket.ts -import { actor as actor23 } from "rivetkit"; -var rawWebSocketActor = actor23({ +import { actor as actor17 } from "rivetkit"; +var rawWebSocketActor = actor17({ state: { connectionCount: 0, messageCount: 0 @@ -1321,13 +947,13 @@ var rawWebSocketActor = actor23({ }) ); console.log("[ACTOR] Sent welcome message"); - websocket.addEventListener("message", (event21) => { + websocket.addEventListener("message", (event19) => { ctx.state.messageCount = ctx.state.messageCount + 1; console.log( `[ACTOR] Message received, total count: ${ctx.state.messageCount}, data:`, - event21.data + event19.data ); - const data = event21.data; + const data = event19.data; if (typeof data === "string") { try { const parsed = JSON.parse(data); @@ -1386,10 +1012,10 @@ var rawWebSocketActor = actor23({ } } }); -var rawWebSocketBinaryActor = actor23({ - onWebSocket(ctx, websocket) { - websocket.addEventListener("message", (event21) => { - const data = event21.data; +var rawWebSocketBinaryActor = actor17({ + onWebSocket(_ctx, websocket) { + websocket.addEventListener("message", (event19) => { + const data = event19.data; if (data instanceof ArrayBuffer || data instanceof Uint8Array) { const bytes = new Uint8Array(data); const reversed = new Uint8Array(bytes.length); @@ -1403,44 +1029,9 @@ var rawWebSocketBinaryActor = actor23({ actions: {} }); -// src/actors/http/raw-fetch-counter.ts -import { Hono as Hono2 } from "hono"; -import { actor as actor24 } from "rivetkit"; -var rawFetchCounter = actor24({ - state: { - count: 0 - }, - createVars: () => { - return { router: createCounterRouter() }; - }, - onRequest: (c, request) => { - return c.vars.router.fetch(request, { actor: c }); - }, - actions: { - // ...actions... - } -}); -function createCounterRouter() { - const app2 = new Hono2(); - app2.get("/count", (c) => { - const { actor: actor61 } = c.env; - return c.json({ - count: actor61.state.count - }); - }); - app2.post("/increment", (c) => { - const { actor: actor61 } = c.env; - actor61.state.count++; - return c.json({ - count: actor61.state.count - }); - }); - return app2; -} - // src/actors/http/raw-websocket-chat-room.ts -import { actor as actor25 } from "rivetkit"; -var rawWebSocketChatRoom = actor25({ +import { actor as actor18 } from "rivetkit"; +var rawWebSocketChatRoom = actor18({ state: { messages: [] }, @@ -1457,9 +1048,9 @@ var rawWebSocketChatRoom = actor25({ messages: ctx.state.messages }) ); - socket.addEventListener("message", (event21) => { + socket.addEventListener("message", (event19) => { try { - const data = JSON.parse(event21.data); + const data = JSON.parse(event19.data); if (data.type === "message" && data.text) { const message = { id: crypto.randomUUID(), @@ -1493,11 +1084,13 @@ var rawWebSocketChatRoom = actor25({ }); // src/actors/http/raw-websocket-serverless-smoke.ts -import { actor as actor26 } from "rivetkit"; +import { + actor as actor19 +} from "rivetkit"; function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve2) => setTimeout(resolve2, ms)); } -var rawWebSocketServerlessSmoke = actor26({ +var rawWebSocketServerlessSmoke = actor19({ options: { canHibernateWebSocket: false, sleepGracePeriod: 5e3 @@ -1544,7 +1137,7 @@ var rawWebSocketServerlessSmoke = actor26({ }); sendTick(); const interval = setInterval(sendTick, 1e3); - websocket.addEventListener("message", (event21) => { + websocket.addEventListener("message", (event19) => { c.state.totalMessageCount += 1; c.log.info({ msg: "raw websocket serverless smoke received message", @@ -1557,7 +1150,7 @@ var rawWebSocketServerlessSmoke = actor26({ connectionId, index, timestamp: Date.now(), - received: event21.data + received: event19.data }) ); }); @@ -1584,8 +1177,10 @@ var rawWebSocketServerlessSmoke = actor26({ }); // src/actors/http/tunnel-stress.ts -import { actor as actor27 } from "rivetkit"; -var tunnelStress = actor27({ +import { + actor as actor20 +} from "rivetkit"; +var tunnelStress = actor20({ options: { canHibernateWebSocket: false, sleepGracePeriod: 5e3 @@ -1612,41 +1207,44 @@ var tunnelStress = actor27({ }; const heartbeat = setInterval(sendHeartbeat, 1e3); sendHeartbeat(); - websocket.addEventListener("message", async (event21) => { - if (typeof event21.data === "string") { - let parsed; - try { - parsed = JSON.parse(event21.data); - } catch { - parsed = void 0; - } - if (parsed && typeof parsed === "object" && parsed.type === "ping") { - const id = parsed.id; - if (websocket.readyState === 1) { - websocket.send( - JSON.stringify({ - type: "pong", - connectionId, - id, - timestamp: Date.now() - }) - ); + websocket.addEventListener( + "message", + async (event19) => { + if (typeof event19.data === "string") { + let parsed; + try { + parsed = JSON.parse(event19.data); + } catch { + parsed = void 0; + } + if (parsed && typeof parsed === "object" && parsed.type === "ping") { + const id = parsed.id; + if (websocket.readyState === 1) { + websocket.send( + JSON.stringify({ + type: "pong", + connectionId, + id, + timestamp: Date.now() + }) + ); + } + return; } - return; } + c.state.messageCount += 1; + await c.kv.put("counter", String(c.state.messageCount)); + websocket.send( + JSON.stringify({ + type: "reply", + connectionId, + messageCount: c.state.messageCount, + timestamp: Date.now(), + received: event19.data + }) + ); } - c.state.messageCount += 1; - await c.kv.put("counter", String(c.state.messageCount)); - websocket.send( - JSON.stringify({ - type: "reply", - connectionId, - messageCount: c.state.messageCount, - timestamp: Date.now(), - received: event21.data - }) - ); - }); + ); websocket.addEventListener("close", () => { clearInterval(heartbeat); c.state.connectionCount -= 1; @@ -1663,10 +1261,232 @@ var tunnelStress = actor27({ } }); +// src/actors/inter-actor/cross-actor-actions.ts +import { actor as actor21 } from "rivetkit"; +var inventory = actor21({ + // Each item has its own inventory actor instance + createState: (_c, input) => ({ + itemName: input?.itemName ?? "Widget", + stock: input?.initialStock ?? 100, + reservations: [] + }), + actions: { + // Check current stock + getStock: (c) => ({ + itemName: c.state.itemName, + stock: c.state.stock + }), + // Reserve items for checkout (called by checkout actor) + reserveItems: (c, checkoutId, quantity) => { + if (c.state.stock < quantity) { + return { + success: false, + message: `Insufficient stock. Available: ${c.state.stock}, Requested: ${quantity}`, + availableStock: c.state.stock + }; + } + c.state.stock -= quantity; + c.state.reservations.push(checkoutId); + return { + success: true, + message: `Reserved ${quantity} items for checkout ${checkoutId}`, + remainingStock: c.state.stock + }; + }, + // Release reserved items if checkout is cancelled + releaseItems: (c, checkoutId, quantity) => { + const index = c.state.reservations.indexOf(checkoutId); + if (index > -1) { + c.state.reservations.splice(index, 1); + c.state.stock += quantity; + } + return { + success: true, + remainingStock: c.state.stock + }; + } + } +}); +var checkout = actor21({ + createState: (_c, input) => ({ + customerName: input?.customerName ?? "Guest", + items: [], + completed: false + }), + actions: { + // Add item to checkout and reserve from inventory + addItem: async (c, itemId, quantity) => { + const inventoryActor = c.client().inventory.getOrCreate([itemId]); + const itemInfo = await inventoryActor.getStock(); + const reservation = await inventoryActor.reserveItems( + c.actorId, + // Use checkout ID as reservation ID + quantity + ); + if (!reservation.success) { + return { + success: false, + message: reservation.message + }; + } + c.state.items.push({ + itemId, + itemName: itemInfo.itemName, + quantity + }); + return { + success: true, + message: `Added ${quantity} ${itemInfo.itemName} to checkout`, + remainingStock: reservation.remainingStock + }; + }, + // Get checkout summary + getSummary: (c) => ({ + customerName: c.state.customerName, + items: c.state.items, + completed: c.state.completed, + totalItems: c.state.items.reduce( + (sum, item) => sum + item.quantity, + 0 + ) + }), + // Complete the checkout + completeCheckout: (c) => { + c.state.completed = true; + return { + success: true, + message: "Checkout completed successfully" + }; + }, + // Cancel checkout and release all reservations + cancelCheckout: async (c) => { + for (const item of c.state.items) { + const inventoryActor = c.client().inventory.getOrCreate([item.itemId]); + await inventoryActor.releaseItems(c.actorId, item.quantity); + } + c.state.items = []; + return { + success: true, + message: "Checkout cancelled, items returned to inventory" + }; + } + } +}); + +// src/actors/lifecycle/destroy.ts +import { actor as actor22 } from "rivetkit"; +var destroyObserver = actor22({ + state: { destroyedActors: [] }, + actions: { + notifyDestroyed: (c, actorKey) => { + c.state.destroyedActors.push(actorKey); + }, + wasDestroyed: (c, actorKey) => { + return c.state.destroyedActors.includes(actorKey); + }, + reset: (c) => { + c.state.destroyedActors = []; + } + } +}); +var destroyActor = actor22({ + state: { value: 0, key: "" }, + onWake: (c) => { + c.state.key = c.key.join("/"); + }, + onDestroy: async (c) => { + const client = c.client(); + const observer = client.destroyObserver.getOrCreate(["observer"]); + await observer.notifyDestroyed(c.state.key); + }, + actions: { + setValue: async (c, newValue) => { + c.state.value = newValue; + await c.saveState({ immediate: true }); + return c.state.value; + }, + getValue: (c) => { + return c.state.value; + }, + destroy: (c) => { + c.destroy(); + } + } +}); + +// src/actors/lifecycle/hibernation.ts +import { actor as actor23 } from "rivetkit"; +var HIBERNATION_SLEEP_TIMEOUT = 500; +var hibernationActor = actor23({ + state: { + sleepCount: 0, + wakeCount: 0 + }, + createConnState: (_c) => { + return { + count: 0, + connectCount: 0, + disconnectCount: 0 + }; + }, + onWake: (c) => { + c.state.wakeCount += 1; + }, + onSleep: (c) => { + c.state.sleepCount += 1; + }, + onConnect: (_c, conn) => { + conn.state.connectCount += 1; + }, + onDisconnect: (_c, conn) => { + conn.state.disconnectCount += 1; + }, + actions: { + // Basic RPC that returns a simple value + ping: (_c) => { + return "pong"; + }, + // Increment the connection's count + connIncrement: (c) => { + c.conn.state.count += 1; + return c.conn.state.count; + }, + // Get the connection's count + getConnCount: (c) => { + return c.conn.state.count; + }, + // Get the connection's lifecycle counts + getConnLifecycleCounts: (c) => { + return { + connectCount: c.conn.state.connectCount, + disconnectCount: c.conn.state.disconnectCount + }; + }, + // Get all connection IDs + getConnectionIds: (c) => { + return c.conns.entries().map((x) => x[0]).toArray(); + }, + // Get actor sleep/wake counts + getActorCounts: (c) => { + return { + sleepCount: c.state.sleepCount, + wakeCount: c.state.wakeCount + }; + }, + // Trigger sleep + triggerSleep: (c) => { + c.sleep(); + } + }, + options: { + sleepTimeout: HIBERNATION_SLEEP_TIMEOUT + } +}); + // src/actors/lifecycle/run.ts -import { actor as actor28, queue } from "rivetkit"; +import { actor as actor24, queue } from "rivetkit"; var RUN_SLEEP_TIMEOUT = 500; -var runWithTicks = actor28({ +var runWithTicks = actor24({ state: { tickCount: 0, lastTickAt: 0, @@ -1680,13 +1500,13 @@ var runWithTicks = actor28({ c.state.tickCount += 1; c.state.lastTickAt = Date.now(); c.log.info({ msg: "tick", tickCount: c.state.tickCount }); - await new Promise((resolve) => { - const timeout = setTimeout(resolve, 50); + await new Promise((resolve2) => { + const timeout = setTimeout(resolve2, 50); c.abortSignal.addEventListener( "abort", () => { clearTimeout(timeout); - resolve(); + resolve2(); }, { once: true } ); @@ -1707,7 +1527,7 @@ var runWithTicks = actor28({ sleepTimeout: RUN_SLEEP_TIMEOUT } }); -var runWithQueueConsumer = actor28({ +var runWithQueueConsumer = actor24({ state: { messagesReceived: [], runStarted: false @@ -1743,7 +1563,7 @@ var runWithQueueConsumer = actor28({ sleepTimeout: RUN_SLEEP_TIMEOUT } }); -var runWithEarlyExit = actor28({ +var runWithEarlyExit = actor24({ state: { runStarted: false, destroyCalled: false @@ -1751,7 +1571,7 @@ var runWithEarlyExit = actor28({ run: async (c) => { c.state.runStarted = true; c.log.info("run handler started, will exit after delay"); - await new Promise((resolve) => setTimeout(resolve, 200)); + await new Promise((resolve2) => setTimeout(resolve2, 200)); c.log.info("run handler exiting early"); }, onDestroy: (c) => { @@ -1767,7 +1587,7 @@ var runWithEarlyExit = actor28({ sleepTimeout: RUN_SLEEP_TIMEOUT } }); -var runWithError = actor28({ +var runWithError = actor24({ state: { runStarted: false, destroyCalled: false @@ -1775,7 +1595,7 @@ var runWithError = actor28({ run: async (c) => { c.state.runStarted = true; c.log.info("run handler started, will throw error"); - await new Promise((resolve) => setTimeout(resolve, 50)); + await new Promise((resolve2) => setTimeout(resolve2, 50)); throw new Error("intentional error in run handler"); }, onDestroy: (c) => { @@ -1791,7 +1611,7 @@ var runWithError = actor28({ sleepTimeout: RUN_SLEEP_TIMEOUT } }); -var runWithoutHandler = actor28({ +var runWithoutHandler = actor24({ state: { wakeCount: 0 }, @@ -1808,26 +1628,98 @@ var runWithoutHandler = actor28({ } }); -// src/actors/lifecycle/sleep.ts -import { actor as actor29, event as event7 } from "rivetkit"; -import { promiseWithResolvers } from "rivetkit/utils"; -var SLEEP_TIMEOUT = 1e3; -var sleep2 = actor29({ - state: { startCount: 0, sleepCount: 0 }, - onWake: (c) => { - c.state.startCount += 1; +// src/actors/lifecycle/scheduled.ts +import { actor as actor25, event as event6 } from "rivetkit"; +var scheduled = actor25({ + state: { + lastRun: 0, + scheduledCount: 0, + taskHistory: [] }, - onSleep: (c) => { - c.state.sleepCount += 1; + events: { + scheduled: event6(), + scheduledWithId: event6() }, actions: { - triggerSleep: (c) => { - c.sleep(); + // Schedule using 'at' with specific timestamp + scheduleTaskAt: (c, timestamp) => { + c.schedule.at(timestamp, "onScheduledTask"); + return timestamp; }, - getCounts: (c) => { - return { - startCount: c.state.startCount, - sleepCount: c.state.sleepCount + // Schedule using 'after' with delay + scheduleTaskAfter: (c, delayMs) => { + c.schedule.after(delayMs, "onScheduledTask"); + return Date.now() + delayMs; + }, + // Schedule with a task ID for ordering tests + scheduleTaskAfterWithId: (c, taskId, delayMs) => { + c.schedule.after(delayMs, "onScheduledTaskWithId", taskId); + return { taskId, scheduledFor: Date.now() + delayMs }; + }, + // Original method for backward compatibility + scheduleTask: (c, delayMs) => { + const timestamp = Date.now() + delayMs; + c.schedule.at(timestamp, "onScheduledTask"); + return timestamp; + }, + // Getters for state + getLastRun: (c) => { + return c.state.lastRun; + }, + getScheduledCount: (c) => { + return c.state.scheduledCount; + }, + getTaskHistory: (c) => { + return c.state.taskHistory; + }, + clearHistory: (c) => { + c.state.taskHistory = []; + c.state.scheduledCount = 0; + c.state.lastRun = 0; + return true; + }, + // Scheduled task handlers + onScheduledTask: (c) => { + c.state.lastRun = Date.now(); + c.state.scheduledCount++; + c.broadcast("scheduled", { + time: c.state.lastRun, + count: c.state.scheduledCount + }); + }, + onScheduledTaskWithId: (c, taskId) => { + c.state.lastRun = Date.now(); + c.state.scheduledCount++; + c.state.taskHistory.push(taskId); + c.broadcast("scheduledWithId", { + taskId, + time: c.state.lastRun, + count: c.state.scheduledCount + }); + } + } +}); + +// src/actors/lifecycle/sleep.ts +import { actor as actor26, event as event7 } from "rivetkit"; +import { promiseWithResolvers } from "rivetkit/utils"; +var SLEEP_TIMEOUT = 1e3; +var sleep2 = actor26({ + state: { startCount: 0, sleepCount: 0 }, + onWake: (c) => { + c.state.startCount += 1; + }, + onSleep: (c) => { + c.state.sleepCount += 1; + }, + actions: { + triggerSleep: (c) => { + c.sleep(); + }, + getCounts: (c) => { + return { + startCount: c.state.startCount, + sleepCount: c.state.sleepCount }; }, setAlarm: async (c, duration) => { @@ -1841,7 +1733,7 @@ var sleep2 = actor29({ sleepTimeout: SLEEP_TIMEOUT } }); -var sleepWithLongRpc = actor29({ +var sleepWithLongRpc = actor26({ state: { startCount: 0, sleepCount: 0 }, createVars: () => ({}), events: { @@ -1874,7 +1766,7 @@ var sleepWithLongRpc = actor29({ sleepTimeout: SLEEP_TIMEOUT } }); -var sleepWithRawHttp = actor29({ +var sleepWithRawHttp = actor26({ state: { startCount: 0, sleepCount: 0, requestCount: 0 }, onWake: (c) => { c.state.startCount += 1; @@ -1887,10 +1779,11 @@ var sleepWithRawHttp = actor29({ const url = new URL(request.url); if (url.pathname === "/long-request") { const duration = parseInt( - url.searchParams.get("duration") || "1000" + url.searchParams.get("duration") || "1000", + 10 ); c.log.info({ msg: "starting long fetch request", duration }); - await new Promise((resolve) => setTimeout(resolve, duration)); + await new Promise((resolve2) => setTimeout(resolve2, duration)); c.log.info("finished long fetch request"); return new Response(JSON.stringify({ completed: true }), { headers: { "Content-Type": "application/json" } @@ -1911,7 +1804,7 @@ var sleepWithRawHttp = actor29({ sleepTimeout: SLEEP_TIMEOUT } }); -var sleepWithRawWebSocket = actor29({ +var sleepWithRawWebSocket = actor26({ state: { startCount: 0, sleepCount: 0, connectionCount: 0 }, onWake: (c) => { c.state.startCount += 1; @@ -1931,8 +1824,8 @@ var sleepWithRawWebSocket = actor29({ connectionCount: c.state.connectionCount }) ); - websocket.addEventListener("message", (event21) => { - const data = event21.data; + websocket.addEventListener("message", (event19) => { + const data = event19.data; if (typeof data === "string") { try { const parsed = JSON.parse(data); @@ -1974,7 +1867,7 @@ var sleepWithRawWebSocket = actor29({ sleepTimeout: SLEEP_TIMEOUT } }); -var sleepWithNoSleepOption = actor29({ +var sleepWithNoSleepOption = actor26({ state: { startCount: 0, sleepCount: 0 }, onWake: (c) => { c.state.startCount += 1; @@ -1996,199 +1889,17 @@ var sleepWithNoSleepOption = actor29({ } }); -// src/actors/lifecycle/scheduled.ts -import { actor as actor30, event as event8 } from "rivetkit"; -var scheduled = actor30({ - state: { - lastRun: 0, - scheduledCount: 0, - taskHistory: [] - }, - events: { - scheduled: event8(), - scheduledWithId: event8() - }, - actions: { - // Schedule using 'at' with specific timestamp - scheduleTaskAt: (c, timestamp) => { - c.schedule.at(timestamp, "onScheduledTask"); - return timestamp; - }, - // Schedule using 'after' with delay - scheduleTaskAfter: (c, delayMs) => { - c.schedule.after(delayMs, "onScheduledTask"); - return Date.now() + delayMs; - }, - // Schedule with a task ID for ordering tests - scheduleTaskAfterWithId: (c, taskId, delayMs) => { - c.schedule.after(delayMs, "onScheduledTaskWithId", taskId); - return { taskId, scheduledFor: Date.now() + delayMs }; - }, - // Original method for backward compatibility - scheduleTask: (c, delayMs) => { - const timestamp = Date.now() + delayMs; - c.schedule.at(timestamp, "onScheduledTask"); - return timestamp; - }, - // Getters for state - getLastRun: (c) => { - return c.state.lastRun; - }, - getScheduledCount: (c) => { - return c.state.scheduledCount; - }, - getTaskHistory: (c) => { - return c.state.taskHistory; - }, - clearHistory: (c) => { - c.state.taskHistory = []; - c.state.scheduledCount = 0; - c.state.lastRun = 0; - return true; - }, - // Scheduled task handlers - onScheduledTask: (c) => { - c.state.lastRun = Date.now(); - c.state.scheduledCount++; - c.broadcast("scheduled", { - time: c.state.lastRun, - count: c.state.scheduledCount - }); - }, - onScheduledTaskWithId: (c, taskId) => { - c.state.lastRun = Date.now(); - c.state.scheduledCount++; - c.state.taskHistory.push(taskId); - c.broadcast("scheduledWithId", { - taskId, - time: c.state.lastRun, - count: c.state.scheduledCount - }); - } - } -}); - -// src/actors/lifecycle/destroy.ts -import { actor as actor31 } from "rivetkit"; -var destroyObserver = actor31({ - state: { destroyedActors: [] }, - actions: { - notifyDestroyed: (c, actorKey) => { - c.state.destroyedActors.push(actorKey); - }, - wasDestroyed: (c, actorKey) => { - return c.state.destroyedActors.includes(actorKey); - }, - reset: (c) => { - c.state.destroyedActors = []; - } - } -}); -var destroyActor = actor31({ - state: { value: 0, key: "" }, - onWake: (c) => { - c.state.key = c.key.join("/"); - }, - onDestroy: async (c) => { - const client = c.client(); - const observer = client.destroyObserver.getOrCreate(["observer"]); - await observer.notifyDestroyed(c.state.key); - }, - actions: { - setValue: async (c, newValue) => { - c.state.value = newValue; - await c.saveState({ immediate: true }); - return c.state.value; - }, - getValue: (c) => { - return c.state.value; - }, - destroy: (c) => { - c.destroy(); - } - } -}); - -// src/actors/lifecycle/hibernation.ts -import { actor as actor32 } from "rivetkit"; -var HIBERNATION_SLEEP_TIMEOUT = 500; -var hibernationActor = actor32({ - state: { - sleepCount: 0, - wakeCount: 0 - }, - createConnState: (c) => { - return { - count: 0, - connectCount: 0, - disconnectCount: 0 - }; - }, - onWake: (c) => { - c.state.wakeCount += 1; - }, - onSleep: (c) => { - c.state.sleepCount += 1; - }, - onConnect: (c, conn) => { - conn.state.connectCount += 1; - }, - onDisconnect: (c, conn) => { - conn.state.disconnectCount += 1; - }, - actions: { - // Basic RPC that returns a simple value - ping: (c) => { - return "pong"; - }, - // Increment the connection's count - connIncrement: (c) => { - c.conn.state.count += 1; - return c.conn.state.count; - }, - // Get the connection's count - getConnCount: (c) => { - return c.conn.state.count; - }, - // Get the connection's lifecycle counts - getConnLifecycleCounts: (c) => { - return { - connectCount: c.conn.state.connectCount, - disconnectCount: c.conn.state.disconnectCount - }; - }, - // Get all connection IDs - getConnectionIds: (c) => { - return c.conns.entries().map((x) => x[0]).toArray(); - }, - // Get actor sleep/wake counts - getActorCounts: (c) => { - return { - sleepCount: c.state.sleepCount, - wakeCount: c.state.wakeCount - }; - }, - // Trigger sleep - triggerSleep: (c) => { - c.sleep(); - } - }, - options: { - sleepTimeout: HIBERNATION_SLEEP_TIMEOUT - } -}); - // src/actors/queue/worker.ts -import { actor as actor33, event as event9, queue as queue2 } from "rivetkit"; -var worker = actor33({ +import { actor as actor27, event as event8, queue as queue2 } from "rivetkit"; +var worker = actor27({ state: { status: "idle", processed: 0, lastJob: null }, events: { - statusChanged: event9(), - jobProcessed: event9() + statusChanged: event8(), + jobProcessed: event8() }, queues: { jobs: queue2() @@ -2221,9 +1932,9 @@ var worker = actor33({ }); // src/actors/queue/worker-timeout.ts -import { actor as actor34, event as event10, queue as queue3 } from "rivetkit"; +import { actor as actor28, event as event9, queue as queue3 } from "rivetkit"; var DEFAULT_TIMEOUT_MS = 2e3; -var workerTimeout = actor34({ +var workerTimeout = actor28({ state: { status: "idle", processed: 0, @@ -2233,8 +1944,8 @@ var workerTimeout = actor34({ timeoutMs: DEFAULT_TIMEOUT_MS }, events: { - tick: event10(), - jobProcessed: event10() + tick: event9(), + jobProcessed: event9() }, queues: { jobs: queue3() @@ -2282,7799 +1993,5661 @@ var workerTimeout = actor34({ } }); -// src/actors/workflow/workflow-fixtures.ts -import { actor as actor35, event as event11, queue as queue4 } from "rivetkit"; -import { Loop, workflow } from "rivetkit/workflow"; -var WORKFLOW_GUARD_KV_KEY = "__rivet_actor_workflow_guard_triggered"; -var WORKFLOW_QUEUE_NAME = "workflow-default"; -var WORKFLOW_TIMEOUT_QUEUE_NAME = "workflow-timeout"; -var workflowCounterActor = actor35({ +// src/actors/state/actor-onstatechange.ts +import { actor as actor29 } from "rivetkit"; +var onStateChangeActor = actor29({ state: { - runCount: 0, - guardTriggered: false, - history: [] + value: 0 + }, + vars: { + changeCount: 0 }, - run: workflow(async (ctx) => { - await ctx.loop("counter", async (loopCtx) => { - try { - loopCtx.state; - } catch { - } - await loopCtx.step("increment", async () => { - incrementWorkflowCounter(loopCtx); - }); - await loopCtx.sleep("idle", 25); - return Loop.continue(void 0); - }); - }), actions: { - getState: async (c) => { - const guardFlag = await c.kv.get(WORKFLOW_GUARD_KV_KEY); - if (guardFlag === "true") { - c.state.guardTriggered = true; + // Action that modifies state - should trigger onStateChange + setValue: (c, newValue) => { + c.state.value = newValue; + return c.state.value; + }, + // Action that modifies state multiple times - should trigger onStateChange for each change + incrementMultiple: (c, times) => { + for (let i = 0; i < times; i++) { + c.state.value++; } - return c.state; + return c.state.value; + }, + // Action that doesn't modify state - should NOT trigger onStateChange + getValue: (c) => { + return c.state.value; + }, + // Action that reads and returns without modifying - should NOT trigger onStateChange + getDoubled: (c) => { + const doubled = c.state.value * 2; + return doubled; + }, + // Get the count of how many times onStateChange was called + getChangeCount: (c) => { + return c.vars.changeCount; + }, + // Reset change counter for testing + resetChangeCount: (c) => { + c.vars.changeCount = 0; } }, - options: { - sleepTimeout: 50 + // Track onStateChange calls + onStateChange: (c) => { + c.vars.changeCount++; } }); -var workflowQueueActor = actor35({ - state: { - received: [] - }, - queues: { - [WORKFLOW_QUEUE_NAME]: queue4() - }, - run: workflow(async (ctx) => { - await ctx.loop("queue", async (loopCtx) => { - const message = await loopCtx.queue.next("queue-wait", { - names: [WORKFLOW_QUEUE_NAME], - completable: true - }); - if (!message.complete) { - return Loop.continue(void 0); + +// src/actors/state/kv.ts +import { actor as actor30 } from "rivetkit"; +var kvActor = actor30({ + actions: { + putText: async (c, key, value) => { + await c.kv.put(key, value); + return true; + }, + getText: async (c, key) => { + return await c.kv.get(key); + }, + listText: async (c, prefix) => { + const results = await c.kv.list(prefix, { keyType: "text" }); + return results.map(([key, value]) => ({ + key, + value + })); + }, + roundtripArrayBuffer: async (c, key, values) => { + const buffer = new Uint8Array(values).buffer; + await c.kv.put(key, buffer, { type: "arrayBuffer" }); + const result = await c.kv.get(key, { type: "arrayBuffer" }); + if (!result) { + return null; } - const complete = message.complete; - await loopCtx.step("store-message", async () => { - await storeWorkflowQueueMessage(loopCtx, message.body, complete); - }); - return Loop.continue(void 0); - }); - }), + return Array.from(new Uint8Array(result)); + } + } +}); + +// src/actors/state/large-payloads.ts +import { actor as actor31 } from "rivetkit"; +var largePayloadActor = actor31({ + state: {}, actions: { - getMessages: (c) => c.state.received + /** + * Accepts a large request payload and returns its size + */ + processLargeRequest: (_c, data) => { + return { + itemCount: data.items.length, + firstItem: data.items[0], + lastItem: data.items[data.items.length - 1] + }; + }, + /** + * Returns a large response payload + */ + getLargeResponse: (_c, itemCount) => { + const items = []; + for (let i = 0; i < itemCount; i++) { + items.push( + `Item ${i} with some additional text to increase size` + ); + } + return { items }; + }, + /** + * Echo back the request data + */ + echo: (_c, data) => { + return data; + } } }); -var workflowSleepActor = actor35({ - state: { - ticks: 0 +var largePayloadConnActor = actor31({ + state: {}, + connState: { + lastRequestSize: 0 }, - run: workflow(async (ctx) => { - await ctx.loop("sleep", async (loopCtx) => { - await loopCtx.step("tick", async () => { - incrementWorkflowSleepTick(loopCtx); - }); - await loopCtx.sleep("delay", 40); - return Loop.continue(void 0); - }); - }), actions: { - getState: (c) => c.state - }, - options: { - sleepTimeout: 50 + /** + * Accepts a large request payload and returns its size + */ + processLargeRequest: (c, data) => { + c.conn.state.lastRequestSize = data.items.length; + return { + itemCount: data.items.length, + firstItem: data.items[0], + lastItem: data.items[data.items.length - 1] + }; + }, + /** + * Returns a large response payload + */ + getLargeResponse: (_c, itemCount) => { + const items = []; + for (let i = 0; i < itemCount; i++) { + items.push( + `Item ${i} with some additional text to increase size` + ); + } + return { items }; + }, + /** + * Echo back the request data + */ + echo: (_c, data) => { + return data; + }, + /** + * Get the last request size + */ + getLastRequestSize: (c) => { + return c.conn.state.lastRequestSize; + } } }); -var workflowQueueTimeoutActor = actor35({ + +// src/actors/state/metadata.ts +import { actor as actor32 } from "rivetkit"; +var metadataActor = actor32({ state: { - processed: 0, - ticks: 0, - lastTickAt: null, - lastJob: null, - timeoutMs: 2e3 - }, - events: { - tick: event11(), - jobProcessed: event11() + lastMetadata: null, + actorName: "", + // Store tags and region in state for testing since they may not be + // available in the context in all environments + storedTags: {}, + storedRegion: null }, - queues: { - [WORKFLOW_TIMEOUT_QUEUE_NAME]: queue4() + onWake: (c) => { + c.state.actorName = c.name; }, - run: workflow(async (ctx) => { - await ctx.loop("queue-timeout-loop", async (loopCtx) => { - const timeoutMs = await loopCtx.step("read-timeout", async () => { - return readWorkflowTimeoutMs(loopCtx); - }); - const [message] = await loopCtx.queue.nextBatch("wait-job-or-timeout", { - names: [WORKFLOW_TIMEOUT_QUEUE_NAME], - timeout: timeoutMs - }); - if (!message) { - await loopCtx.step("tick", async () => { - recordWorkflowTimeoutTick(loopCtx); - }); - return Loop.continue(void 0); - } - await loopCtx.step("process-job", async () => { - processWorkflowTimeoutJob(loopCtx, message.body); - }); - return Loop.continue(void 0); - }); - }), actions: { - enqueueJob: async (c, payload2) => { - const job = { id: crypto.randomUUID(), payload: payload2 }; - await c.queue.send(WORKFLOW_TIMEOUT_QUEUE_NAME, job); - return job; + // Set up test tags - this will be called by tests to simulate tags + setupTestTags: (c, tags) => { + c.state.storedTags = tags; + return tags; }, - setTimeoutMs: (c, timeoutMs) => { - c.state.timeoutMs = Math.max(100, Math.floor(timeoutMs)); - return c.state.timeoutMs; + // Set up test region - this will be called by tests to simulate region + setupTestRegion: (c, region) => { + c.state.storedRegion = region; + return region; }, - getState: (c) => c.state + // Get all available metadata + getMetadata: (c) => { + const metadata = { + name: c.name, + tags: c.state.storedTags, + region: c.state.storedRegion + }; + c.state.lastMetadata = metadata; + return metadata; + }, + // Get the actor name + getActorName: (c) => { + return c.name; + }, + // Get a specific tag by key + getTag: (c, key) => { + return c.state.storedTags[key] || null; + }, + // Get all tags + getTags: (c) => { + return c.state.storedTags; + }, + // Get the region + getRegion: (c) => { + return c.state.storedRegion; + }, + // Get the stored actor name (from onWake) + getStoredActorName: (c) => { + return c.state.actorName; + }, + // Get last retrieved metadata + getLastMetadata: (c) => { + return c.state.lastMetadata; + } } }); -function incrementWorkflowCounter(ctx) { - ctx.state.runCount += 1; - ctx.state.history.push(ctx.state.runCount); -} -async function storeWorkflowQueueMessage(ctx, body, complete) { - ctx.state.received.push(body); - await complete({ echo: body }); -} -function incrementWorkflowSleepTick(ctx) { - ctx.state.ticks += 1; -} -function readWorkflowTimeoutMs(ctx) { - return ctx.state.timeoutMs; -} -function recordWorkflowTimeoutTick(ctx) { - const at = Date.now(); - ctx.state.ticks += 1; - ctx.state.lastTickAt = at; - ctx.broadcast("tick", { - ticks: ctx.state.ticks, - at - }); -} -function processWorkflowTimeoutJob(ctx, job) { - ctx.state.processed += 1; - ctx.state.lastJob = job; - ctx.broadcast("jobProcessed", { - processed: ctx.state.processed, - job - }); -} - -// src/actors/workflow/timer.ts -import { actor as actor36, event as event12 } from "rivetkit"; -import { Loop as Loop2, workflow as workflow2 } from "rivetkit/workflow"; -// src/actors/workflow/_helpers.ts -function actorCtx(ctx) { - return ctx; -} - -// src/actors/workflow/timer.ts -var timer = actor36({ - createState: (c, input) => ({ - id: c.key[0], - name: input?.name ?? "Timer", - durationMs: input?.durationMs ?? 1e4, - startedAt: Date.now() +// src/actors/state/parallelism-test.ts +import { actor as actor33, event as event10 } from "rivetkit"; +import { db } from "rivetkit/db"; +var parallelismTest = actor33({ + state: { + stateCount: 0 + }, + db: db({ + onMigrate: async (db17) => { + await db17.execute(` + CREATE TABLE IF NOT EXISTS counter ( + id INTEGER PRIMARY KEY CHECK (id = 1), + count INTEGER NOT NULL DEFAULT 0 + ) + `); + await db17.execute(` + INSERT OR IGNORE INTO counter (id, count) VALUES (1, 0) + `); + } }), events: { - timerStarted: event12(), - timerCompleted: event12() + stateCountChanged: event10(), + sqliteCountChanged: event10() }, actions: { - getTimer: (c) => c.state + incrementState: (c) => { + c.state.stateCount += 1; + c.broadcast("stateCountChanged", { count: c.state.stateCount }); + return { count: c.state.stateCount }; + }, + getStateCount: (c) => { + return { count: c.state.stateCount }; + }, + incrementSqlite: async (c) => { + await c.db.execute( + `UPDATE counter SET count = count + 1 WHERE id = 1` + ); + const results = await c.db.execute( + `SELECT count FROM counter WHERE id = 1` + ); + const count = results[0].count; + c.broadcast("sqliteCountChanged", { count }); + return { count }; + }, + getSqliteCount: async (c) => { + const results = await c.db.execute( + `SELECT count FROM counter WHERE id = 1` + ); + return { count: results[0].count }; + } }, - run: workflow2(async (ctx) => { - await ctx.loop("timer-loop", async (loopCtx) => { - const c = actorCtx(loopCtx); - const durationMs = await loopCtx.step("start-timer", async () => { - ctx.log.info({ - msg: "starting timer", - timerId: c.state.id, - durationMs: c.state.durationMs - }); - c.broadcast("timerStarted", c.state); - return c.state.durationMs; - }); - await loopCtx.sleep("countdown", durationMs); - await loopCtx.step("complete-timer", async () => { - c.state.completedAt = Date.now(); - c.broadcast("timerCompleted", c.state); - ctx.log.info({ msg: "timer completed", timerId: c.state.id }); - }); - return Loop2.break(void 0); - }); - }), options: { - sleepTimeout: 1e3 + sleepTimeout: 3e4 } }); -// src/actors/workflow/order.ts -import { actor as actor37, event as event13 } from "rivetkit"; -import { Loop as Loop3, workflow as workflow3 } from "rivetkit/workflow"; -async function simulateWork(name, failChance = 0.1) { - await new Promise( - (resolve) => setTimeout(resolve, 500 + Math.random() * 1e3) - ); - if (Math.random() < failChance) { - throw new Error(`${name} failed (simulated)`); +// src/actors/state/sqlite-drizzle/mod.ts +import { eq } from "drizzle-orm"; +import { actor as actor34 } from "rivetkit"; +import { db as db2 } from "rivetkit/db/drizzle"; + +// src/actors/state/sqlite-drizzle/drizzle/meta/_journal.json +var journal_default = { + version: "7", + dialect: "sqlite", + entries: [ + { + idx: 0, + version: "6", + when: 1770921282251, + tag: "0000_left_wrecking_crew", + breakpoints: true + } + ] +}; + +// src/actors/state/sqlite-drizzle/drizzle/0000_left_wrecking_crew.sql +var left_wrecking_crew_default = "CREATE TABLE `todos` (\n `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n `title` text NOT NULL,\n `completed` integer DEFAULT 0,\n `created_at` integer NOT NULL\n);\n"; + +// src/actors/state/sqlite-drizzle/drizzle/migrations.js +var migrations_default = { + journal: journal_default, + migrations: { + m0000: left_wrecking_crew_default } -} -var order = actor37({ - createState: (c) => ({ - id: c.key[0], - status: "pending", - step: 0, - createdAt: Date.now() - }), - events: { - orderUpdated: event13() - }, - actions: { - getOrder: (c) => c.state - }, - run: workflow3(async (ctx) => { - await ctx.loop("process-order", async (loopCtx) => { - const c = actorCtx(loopCtx); - await loopCtx.step("validate", async () => { - ctx.log.info({ msg: "processing order", orderId: c.state.id }); - c.state.status = "validating"; - c.state.step = 1; - c.broadcast("orderUpdated", c.state); - await simulateWork("validation", 0.05); - }); - await loopCtx.step("charge", async () => { - c.state.status = "charging"; - c.state.step = 2; - c.broadcast("orderUpdated", c.state); - await simulateWork("payment", 0.1); - }); - await loopCtx.step("fulfill", async () => { - c.state.status = "fulfilling"; - c.state.step = 3; - c.broadcast("orderUpdated", c.state); - await simulateWork("fulfillment", 0.05); - }); - await loopCtx.step("complete", async () => { - c.state.status = "completed"; - c.state.step = 4; - c.state.completedAt = Date.now(); - c.broadcast("orderUpdated", c.state); - ctx.log.info({ msg: "order completed", orderId: c.state.id }); - }); - return Loop3.break(void 0); - }); - }) +}; + +// src/actors/state/sqlite-drizzle/schema.ts +var schema_exports = {}; +__export(schema_exports, { + todos: () => todos +}); +import { integer, sqliteTable, text } from "rivetkit/db/drizzle"; +var todos = sqliteTable("todos", { + id: integer("id").primaryKey({ autoIncrement: true }), + title: text("title").notNull(), + completed: integer("completed").default(0), + createdAt: integer("created_at").notNull() }); -// src/actors/workflow/batch.ts -import { actor as actor38, event as event14 } from "rivetkit"; -import { Loop as Loop4, workflow as workflow4 } from "rivetkit/workflow"; -function fetchBatch(cursor, batchSize, totalItems) { - const start = cursor * batchSize; - const end = Math.min(start + batchSize, totalItems); - const items = []; - for (let i = start; i < end; i++) { - items.push(i); +// src/actors/state/sqlite-drizzle/mod.ts +var { todos: todos2 } = schema_exports; +var sqliteDrizzleActor = actor34({ + db: db2({ schema: schema_exports, migrations: migrations_default }), + actions: { + addTodo: async (c, title) => { + const result = await c.db.insert(todos2).values({ + title, + createdAt: Date.now() + }).returning(); + return result[0]; + }, + getTodos: async (c) => { + return await c.db.select().from(todos2).orderBy(todos2.createdAt); + }, + toggleTodo: async (c, id) => { + const existing = await c.db.select().from(todos2).where(eq(todos2.id, id)); + if (!existing[0]) return null; + const newCompleted = existing[0].completed ? 0 : 1; + const result = await c.db.update(todos2).set({ completed: newCompleted }).where(eq(todos2.id, id)).returning(); + return result[0]; + }, + deleteTodo: async (c, id) => { + await c.db.delete(todos2).where(eq(todos2.id, id)); + return { deleted: id }; + } } - return { - items, - hasMore: end < totalItems - }; -} -var batch = actor38({ - createState: (c, input) => ({ - id: c.key[0], - totalItems: input?.totalItems ?? 50, - batchSize: input?.batchSize ?? 5, - status: "running", - processedTotal: 0, - currentBatch: 0, - batches: [], - startedAt: Date.now() +}); + +// src/actors/state/sqlite-raw.ts +import { actor as actor35 } from "rivetkit"; +import { db as db3 } from "rivetkit/db"; +var sqliteRawActor = actor35({ + db: db3({ + onMigrate: async (db17) => { + await db17.execute(` + CREATE TABLE IF NOT EXISTS todos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + completed INTEGER DEFAULT 0, + created_at INTEGER NOT NULL + ) + `); + } }), - events: { - batchProcessed: event14(), - stateChanged: event14(), - processingComplete: event14() - }, actions: { - getJob: (c) => c.state - }, - run: workflow4(async (ctx) => { - await ctx.loop({ - name: "batch-loop", - state: { cursor: 0 }, - run: async (batchCtx, loopState) => { - const c = actorCtx(batchCtx); - const batch2 = await batchCtx.step("fetch-batch", async () => { - ctx.log.info({ - msg: "processing batch", - jobId: c.state.id, - cursor: loopState.cursor - }); - await new Promise((r) => setTimeout(r, 200 + Math.random() * 300)); - return fetchBatch(loopState.cursor, c.state.batchSize, c.state.totalItems); - }); - await batchCtx.step("process-batch", async () => { - await new Promise((r) => setTimeout(r, 300 + Math.random() * 500)); - const batchInfo = { - id: loopState.cursor, - count: batch2.items.length, - processedAt: Date.now() - }; - c.state.currentBatch = loopState.cursor; - c.state.processedTotal += batch2.items.length; - c.state.batches.push(batchInfo); - c.broadcast("batchProcessed", batchInfo); - c.broadcast("stateChanged", c.state); - ctx.log.info({ - msg: "batch processed", - jobId: c.state.id, - cursor: loopState.cursor, - count: batch2.items.length - }); - }); - if (!batch2.hasMore) { - await batchCtx.step("mark-complete", async () => { - c.state.status = "completed"; - c.state.completedAt = Date.now(); - c.broadcast("stateChanged", c.state); - c.broadcast("processingComplete", { - totalBatches: loopState.cursor + 1, - totalItems: c.state.processedTotal - }); - }); - return Loop4.break(loopState.cursor + 1); - } - return Loop4.continue({ cursor: loopState.cursor + 1 }); - } - }); - }) + addTodo: async (c, title) => { + const createdAt = Date.now(); + await c.db.execute( + "INSERT INTO todos (title, created_at) VALUES (?, ?)", + title, + createdAt + ); + return { title, createdAt }; + }, + getTodos: async (c) => { + return await c.db.execute( + "SELECT * FROM todos ORDER BY created_at DESC" + ); + }, + toggleTodo: async (c, id) => { + await c.db.execute( + "UPDATE todos SET completed = NOT completed WHERE id = ?", + id + ); + const rows = await c.db.execute( + "SELECT * FROM todos WHERE id = ?", + id + ); + return rows[0]; + }, + deleteTodo: async (c, id) => { + await c.db.execute("DELETE FROM todos WHERE id = ?", id); + return { deleted: id }; + } + } }); -// src/actors/workflow/approval.ts -import { actor as actor39, event as event15, queue as queue5 } from "rivetkit"; -import { Loop as Loop5, workflow as workflow5 } from "rivetkit/workflow"; -var QUEUE_DECISION = "decision"; -var APPROVAL_TIMEOUT_MS = 3e4; -var approval = actor39({ - createState: (c, input) => ({ - id: c.key[0], - title: input?.title ?? "Untitled Request", - description: input?.description ?? "", - status: "pending", - createdAt: Date.now() - }), - queues: { - decision: queue5() - }, - events: { - requestUpdated: event15(), - requestCreated: event15() +// src/actors/state/vars.ts +import { actor as actor36 } from "rivetkit"; +var staticVarActor = actor36({ + state: { value: 0 }, + connState: { hello: "world" }, + vars: { counter: 42, name: "test-actor" }, + actions: { + getVars: (c) => { + return c.vars; + }, + getName: (c) => { + return c.vars.name; + } + } +}); +var nestedVarActor = actor36({ + state: { value: 0 }, + connState: { hello: "world" }, + vars: { + counter: 42, + nested: { + value: "original", + array: [1, 2, 3], + obj: { key: "value" } + } }, actions: { - getRequest: (c) => c.state, - approve: async (c, approver) => { - if (c.state.status !== "pending") return; - c.state.deciding = true; - c.broadcast("requestUpdated", c.state); - await c.queue.send(QUEUE_DECISION, { approved: true, approver }); + getVars: (c) => { + return c.vars; }, - reject: async (c, approver) => { - if (c.state.status !== "pending") return; - c.state.deciding = true; - c.broadcast("requestUpdated", c.state); - await c.queue.send(QUEUE_DECISION, { approved: false, approver }); + modifyNested: (c) => { + c.vars.nested.value = "modified"; + c.vars.nested.array.push(4); + c.vars.nested.obj.key = "new-value"; + return c.vars; } + } +}); +var dynamicVarActor = actor36({ + state: { value: 0 }, + connState: { hello: "world" }, + createVars: () => { + return { + random: Math.random(), + computed: `Actor-${Math.floor(Math.random() * 1e3)}` + }; }, - run: workflow5(async (ctx) => { - await ctx.loop("approval-loop", async (loopCtx) => { - const c = actorCtx(loopCtx); - await loopCtx.step("init-request", async () => { - ctx.log.info({ - msg: "waiting for approval decision", - requestId: c.state.id, - title: c.state.title - }); - c.broadcast("requestCreated", c.state); - }); - const [decisionMessage] = await loopCtx.queue.nextBatch( - "wait-decision", - { - names: [QUEUE_DECISION], - timeout: APPROVAL_TIMEOUT_MS - } - ); - const decision = decisionMessage?.body ?? null; - await loopCtx.step("update-status", async () => { - c.state.deciding = false; - if (decision === null) { - c.state.status = "timeout"; - ctx.log.info({ msg: "request timed out", requestId: c.state.id }); - } else if (decision.approved) { - c.state.status = "approved"; - c.state.decidedBy = decision.approver; - ctx.log.info({ - msg: "request approved", - requestId: c.state.id, - approver: decision.approver - }); - } else { - c.state.status = "rejected"; - c.state.decidedBy = decision.approver; - ctx.log.info({ - msg: "request rejected", - requestId: c.state.id, - approver: decision.approver - }); - } - c.state.decidedAt = Date.now(); - c.broadcast("requestUpdated", c.state); - }); - return Loop5.break(void 0); - }); - }) + actions: { + getVars: (c) => { + return c.vars; + } + } +}); +var uniqueVarActor = actor36({ + state: { value: 0 }, + connState: { hello: "world" }, + createVars: () => { + return { + id: Math.floor(Math.random() * 1e6) + }; + }, + actions: { + getVars: (c) => { + return c.vars; + } + } +}); +var driverCtxActor = actor36({ + state: { value: 0 }, + connState: { hello: "world" }, + createVars: (_c, driverCtx) => { + return { + hasDriverCtx: Boolean(driverCtx?.isTest) + }; + }, + actions: { + getVars: (c) => { + return c.vars; + } + } }); -// src/actors/workflow/dashboard.ts -import { actor as actor40, event as event16, queue as queue6 } from "rivetkit"; -import { Loop as Loop6, workflow as workflow6 } from "rivetkit/workflow"; -var QUEUE_REFRESH = "refresh"; -async function fetchUserStats() { - await new Promise((r) => setTimeout(r, 800 + Math.random() * 1200)); - return { - count: Math.floor(1e3 + Math.random() * 500), - activeToday: Math.floor(100 + Math.random() * 200), - newThisWeek: Math.floor(20 + Math.random() * 80) - }; +// src/actors/testing/grow-db.ts +import { actor as actor37 } from "rivetkit"; +import { db as db4 } from "rivetkit/db"; +var DEFAULT_TARGET_MB = 64; +var DEFAULT_BATCH_ROWS = 128; +var DEFAULT_ROW_BYTES = 8 * 1024; +var DEFAULT_BUDGET_MS = 12e4; +var MEBIBYTE = 1024 * 1024; +function finiteInt(value, fallback) { + if (value === void 0) return fallback; + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`expected a positive finite number, got ${value}`); + } + return Math.floor(value); } -async function fetchOrderStats() { - await new Promise((r) => setTimeout(r, 600 + Math.random() * 1e3)); - const count = Math.floor(50 + Math.random() * 150); - const revenue = Math.floor(5e3 + Math.random() * 15e3); - return { - count, - revenue, - avgOrderValue: Math.round(revenue / count) - }; +async function queryOne(database, sql) { + const rows = await database.execute(sql); + if (!rows[0]) throw new Error(`query returned no rows: ${sql}`); + return rows[0]; } -async function fetchMetricsStats() { - await new Promise((r) => setTimeout(r, 400 + Math.random() * 800)); +async function storageStats(database) { + const [pageCount, pageSize, freelistCount] = await Promise.all([ + queryOne(database, "PRAGMA page_count"), + queryOne(database, "PRAGMA page_size"), + queryOne(database, "PRAGMA freelist_count") + ]); return { - pageViews: Math.floor(1e4 + Math.random() * 5e4), - sessions: Math.floor(2e3 + Math.random() * 8e3), - bounceRate: Math.round(30 + Math.random() * 40) + pageCount: pageCount.page_count, + pageSize: pageSize.page_size, + freelistCount: freelistCount.freelist_count, + sizeBytes: pageCount.page_count * pageSize.page_size }; } -var dashboard = actor40({ - state: { - data: null, - loading: false, - branches: { - users: "pending", - orders: "pending", - metrics: "pending" - }, - lastRefresh: null - }, - queues: { - [QUEUE_REFRESH]: queue6() - }, - events: { - stateChanged: event16(), - refreshComplete: event16() +var growDb = actor37({ + options: { + // Generous per-action timeout. grow() self-limits with budgetMs and + // returns done=false before this fires. + actionTimeout: 3e5 }, + db: db4({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS grow_rows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch INTEGER NOT NULL, + payload BLOB NOT NULL, + created_at INTEGER NOT NULL + ) + `); + } + }), actions: { - refresh: async (c) => { - if (!c.state.loading) { - c.state.loading = true; - c.state.branches = { - users: "pending", - orders: "pending", - metrics: "pending" - }; - c.broadcast("stateChanged", c.state); - await c.queue.send(QUEUE_REFRESH, {}); - } - }, - getState: (c) => c.state - }, - run: workflow6(async (ctx) => { - await ctx.loop("refresh-loop", async (loopCtx) => { - const c = actorCtx(loopCtx); - await loopCtx.queue.next("wait-refresh", { - names: [QUEUE_REFRESH] - }); - ctx.log.info({ msg: "starting dashboard refresh" }); - const results = await loopCtx.join("fetch-all", { - users: { - run: async (branchCtx) => { - const bc = actorCtx(branchCtx); - await branchCtx.step("mark-running", async () => { - bc.state.branches.users = "running"; - bc.broadcast("stateChanged", bc.state); - }); - const data = await branchCtx.step("fetch-users", async () => { - return await fetchUserStats(); - }); - await branchCtx.step("mark-complete", async () => { - bc.state.branches.users = "completed"; - bc.broadcast("stateChanged", bc.state); - }); - return data; - } - }, - orders: { - run: async (branchCtx) => { - const bc = actorCtx(branchCtx); - await branchCtx.step("mark-running", async () => { - bc.state.branches.orders = "running"; - bc.broadcast("stateChanged", bc.state); - }); - const data = await branchCtx.step("fetch-orders", async () => { - return await fetchOrderStats(); - }); - await branchCtx.step("mark-complete", async () => { - bc.state.branches.orders = "completed"; - bc.broadcast("stateChanged", bc.state); - }); - return data; - } - }, - metrics: { - run: async (branchCtx) => { - const bc = actorCtx(branchCtx); - await branchCtx.step("mark-running", async () => { - bc.state.branches.metrics = "running"; - bc.broadcast("stateChanged", bc.state); - }); - const data = await branchCtx.step("fetch-metrics", async () => { - return await fetchMetricsStats(); - }); - await branchCtx.step("mark-complete", async () => { - bc.state.branches.metrics = "completed"; - bc.broadcast("stateChanged", bc.state); - }); - return data; - } - } - }); - await loopCtx.step("save-data", async () => { - c.state.data = { - users: results.users, - orders: results.orders, - metrics: results.metrics, - fetchedAt: Date.now() - }; - c.state.loading = false; - c.state.lastRefresh = Date.now(); - c.broadcast("stateChanged", c.state); - c.broadcast("refreshComplete", c.state.data); - }); - ctx.log.info({ msg: "dashboard refresh complete" }); - return Loop6.continue(void 0); - }); - }) -}); - -// src/actors/workflow/race.ts -import { actor as actor41, event as event17 } from "rivetkit"; -import { Loop as Loop7, workflow as workflow7 } from "rivetkit/workflow"; -var race = actor41({ - createState: (c, input) => ({ - id: c.key[0], - workDurationMs: input?.workDurationMs ?? 2e3, - timeoutMs: input?.timeoutMs ?? 3e3, - status: "running", - startedAt: Date.now() - }), - events: { - raceStarted: event17(), - raceCompleted: event17() - }, - actions: { - getTask: (c) => c.state - }, - run: workflow7(async (ctx) => { - await ctx.loop("race-loop", async (loopCtx) => { - const c = actorCtx(loopCtx); - const { workDurationMs, timeoutMs, taskId } = await loopCtx.step( - "start-race", - async () => { - ctx.log.info({ - msg: "starting race", - taskId: c.state.id, - workDurationMs: c.state.workDurationMs, - timeoutMs: c.state.timeoutMs - }); - c.broadcast("raceStarted", c.state); + grow: async (c, input = {}) => { + const startedAt = performance.now(); + const targetMb = finiteInt(input.targetMb, DEFAULT_TARGET_MB); + const batchRows = finiteInt(input.batchRows, DEFAULT_BATCH_ROWS); + const rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES); + const budgetMs = finiteInt(input.budgetMs, DEFAULT_BUDGET_MS); + const targetBytes = targetMb * MEBIBYTE; + const placeholders = new Array(batchRows).fill("(?, randomblob(?), ?)").join(", "); + let batchesThisCall = 0; + let rowsThisCall = 0; + let stats = await storageStats(c.db); + while (stats.sizeBytes < targetBytes) { + if (performance.now() - startedAt >= budgetMs) { return { - workDurationMs: c.state.workDurationMs, - timeoutMs: c.state.timeoutMs, - taskId: c.state.id + ...stats, + done: false, + targetBytes, + batchesThisCall, + rowsThisCall, + elapsedMs: Math.round(performance.now() - startedAt) }; } - ); - const { winner, value } = await loopCtx.race("work-vs-timeout", [ - { - name: "work", - run: async (branchCtx) => { - await branchCtx.sleep("simulate-work", workDurationMs); - return await branchCtx.step("complete-work", async () => { - return `Result for task ${taskId}`; - }); - } - }, - { - name: "timeout", - run: async (branchCtx) => { - await branchCtx.sleep("timeout-wait", timeoutMs); - return null; - } - } - ]); - await loopCtx.step("save-result", async () => { - c.state.completedAt = Date.now(); - c.state.actualDurationMs = c.state.completedAt - c.state.startedAt; - if (winner === "work") { - c.state.status = "work_won"; - c.state.result = value; - ctx.log.info({ - msg: "work completed before timeout", - taskId: c.state.id, - durationMs: c.state.actualDurationMs - }); - } else { - c.state.status = "timeout_won"; - ctx.log.info({ - msg: "timeout won the race", - taskId: c.state.id, - durationMs: c.state.actualDurationMs - }); - } - c.broadcast("raceCompleted", c.state); - }); - return Loop7.break(void 0); - }); - }) -}); - -// src/actors/workflow/payment.ts -import { actor as actor42, event as event18 } from "rivetkit"; -import { Loop as Loop8, workflow as workflow8 } from "rivetkit/workflow"; -var payment = actor42({ - createState: (c, input) => ({ - id: c.key[0], - amount: input?.amount ?? 100, - shouldFail: input?.shouldFail ?? false, - status: "pending", - steps: [ - { name: "reserve-inventory", status: "pending" }, - { name: "charge-card", status: "pending" }, - { name: "complete-order", status: "pending" } - ], - startedAt: Date.now() - }), - events: { - transactionStarted: event18(), - transactionUpdated: event18(), - transactionCompleted: event18() - }, - actions: { - getTransaction: (c) => c.state - }, - run: workflow8(async (ctx) => { - await ctx.loop("payment-loop", async (loopCtx) => { - const c = actorCtx(loopCtx); - await loopCtx.step("init-payment", async () => { - ctx.log.info({ - msg: "starting payment processing", - txId: c.state.id, - amount: c.state.amount, - shouldFail: c.state.shouldFail - }); - c.broadcast("transactionStarted", c.state); - }); - await loopCtx.rollbackCheckpoint("payment-checkpoint"); - await loopCtx.step({ - name: "reserve-inventory", - run: async () => { - c.state.status = "reserving"; - const step = c.state.steps.find( - (s) => s.name === "reserve-inventory" - ); - if (step) { - step.status = "completed"; - step.completedAt = Date.now(); - } - c.broadcast("transactionUpdated", c.state); - await new Promise( - (r) => setTimeout(r, 500 + Math.random() * 500) - ); - ctx.log.info({ msg: "inventory reserved", txId: c.state.id }); - return { reserved: true }; - }, - rollback: async () => { - c.state.status = "rolling_back"; - const step = c.state.steps.find( - (s) => s.name === "reserve-inventory" - ); - if (step) { - step.status = "rolled_back"; - step.rolledBackAt = Date.now(); - } - ctx.log.info({ msg: "inventory released", txId: c.state.id }); - c.broadcast("transactionUpdated", c.state); - await new Promise((r) => setTimeout(r, 400)); + const now = Date.now(); + const args = []; + for (let i = 0; i < batchRows; i += 1) { + args.push(batchesThisCall, rowBytes, now); } - }); - await loopCtx.step({ - name: "charge-card", - run: async () => { - c.state.status = "charging"; - const step = c.state.steps.find((s) => s.name === "charge-card"); - if (step) { - step.status = "completed"; - step.completedAt = Date.now(); - } - c.broadcast("transactionUpdated", c.state); - await new Promise( - (r) => setTimeout(r, 500 + Math.random() * 500) + await c.db.execute("BEGIN"); + try { + await c.db.execute( + `INSERT INTO grow_rows (batch, payload, created_at) VALUES ${placeholders}`, + ...args ); - if (c.state.shouldFail) { - throw new Error("Payment declined (simulated)"); - } - ctx.log.info({ msg: "card charged", txId: c.state.id }); - return { chargeId: `ch_${c.state.id}` }; - }, - rollback: async () => { - c.state.status = "rolling_back"; - const step = c.state.steps.find((s) => s.name === "charge-card"); - if (step) { - step.status = "rolled_back"; - step.rolledBackAt = Date.now(); - } - ctx.log.info({ msg: "charge refunded", txId: c.state.id }); - c.broadcast("transactionUpdated", c.state); - await new Promise((r) => setTimeout(r, 400)); + await c.db.execute("COMMIT"); + } catch (err) { + await c.db.execute("ROLLBACK").catch(() => void 0); + throw err; } - }); - await loopCtx.step("complete-order", async () => { - c.state.status = "completing"; - const step = c.state.steps.find((s) => s.name === "complete-order"); - if (step) step.status = "completed"; - c.broadcast("transactionUpdated", c.state); - await new Promise( - (r) => setTimeout(r, 300 + Math.random() * 300) - ); - c.state.status = "completed"; - c.state.completedAt = Date.now(); - ctx.log.info({ msg: "order completed", txId: c.state.id }); - c.broadcast("transactionCompleted", c.state); - }); - return Loop8.break(void 0); - }); - }) + batchesThisCall += 1; + rowsThisCall += batchRows; + stats = await storageStats(c.db); + } + return { + ...stats, + done: true, + targetBytes, + batchesThisCall, + rowsThisCall, + elapsedMs: Math.round(performance.now() - startedAt) + }; + }, + stats: async (c) => storageStats(c.db) + } }); -// src/actors/workflow/history-examples.ts -import { actor as actor43, queue as queue7 } from "rivetkit"; -import { Loop as Loop9, workflow as workflow9 } from "rivetkit/workflow"; -function delay(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); +// src/actors/testing/inline-client.ts +import { actor as actor38 } from "rivetkit"; +function isDynamicSandboxRuntime() { + return false; } -var workflowHistorySimple = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "pending" - }), - actions: { - getState: (c) => c.state - }, - run: workflow9(async (ctx) => { - await ctx.step("start", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - c.state.lastStep = "start"; - c.state.startedAt = Date.now(); - return { initialized: true }; - }); - await delay(700); - await ctx.step("process", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "process"; - return { processed: true, items: 5 }; +async function waitForConnectionOpen(connection) { + if (connection.connStatus === "connected") { + return; + } + await new Promise((resolve2, reject) => { + const unsubscribeOpen = connection.onOpen(() => { + unsubscribeOpen(); + unsubscribeError(); + resolve2(); }); - await delay(2200); - await ctx.step("validate", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "validate"; - return { valid: true }; - }); - await delay(600); - await ctx.step("complete", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "complete"; - c.state.status = "completed"; - c.state.completedAt = Date.now(); - c.state.output = { success: true, processedItems: 3 }; - return { success: true }; + const unsubscribeError = connection.onError((error) => { + unsubscribeOpen(); + unsubscribeError(); + reject(error); }); - }) -}); -var LOOP_ITEMS = ["A", "B", "C"]; -var workflowHistoryLoop = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "running", - processed: 0, - batches: [] - }), + }); +} +var inlineClientActor = actor38({ + state: { messages: [] }, actions: { - getState: (c) => c.state - }, - run: workflow9(async (ctx) => { - await ctx.step("init", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - return { batchSize: LOOP_ITEMS.length }; - }); - await ctx.loop({ - name: "batch-loop", - state: { index: 0 }, - commitInterval: 1, - historyEvery: 1, - historyKeep: LOOP_ITEMS.length, - run: async (loopCtx, loopState) => { - const item = LOOP_ITEMS[loopState.index]; - await loopCtx.step(`process-${loopState.index}`, async () => { - const c = actorCtx(loopCtx); - c.state.processed += 1; - c.state.batches.push({ index: loopState.index, item }); - return { item, status: "done" }; - }); - if (loopState.index >= LOOP_ITEMS.length - 1) { - return Loop9.break({ processed: LOOP_ITEMS.length }); - } - return Loop9.continue({ index: loopState.index + 1 }); + // Action that uses client to call another actor (stateless) + callCounterIncrement: async (c, amount) => { + const client = c.client(); + const result = await client.counter.getOrCreate(["inline-test"]).increment(amount); + c.state.messages.push( + `Called counter.increment(${amount}), result: ${result}` + ); + return result; + }, + // Action that uses client to get counter state (stateless) + getCounterState: async (c) => { + const client = c.client(); + const count = await client.counter.getOrCreate(["inline-test"]).getCount(); + c.state.messages.push(`Got counter state: ${count}`); + return count; + }, + // Action that uses client with .connect() for stateful communication + connectToCounterAndIncrement: async (c, amount) => { + const client = c.client(); + const handle = client.counter.getOrCreate(["inline-test-stateful"]); + if (isDynamicSandboxRuntime()) { + const events2 = []; + const result12 = await handle.increment(amount); + events2.push(result12); + const result22 = await handle.increment(amount * 2); + events2.push(result22); + c.state.messages.push( + `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result12}, ${result22}, events: ${JSON.stringify(events2)}` + ); + return { result1: result12, result2: result22, events: events2 }; } - }); - await ctx.step("finalize", async () => { - const c = actorCtx(ctx); - c.state.status = "completed"; - c.state.completedAt = Date.now(); - return { allProcessed: true }; - }); - }) + await handle.getCount(); + const connection = handle.connect(); + await waitForConnectionOpen(connection); + const events = []; + connection.on("newCount", (count) => { + events.push(count); + }); + const result1 = await connection.increment(amount); + const result2 = await connection.increment(amount * 2); + await connection.dispose(); + c.state.messages.push( + `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}` + ); + return { result1, result2, events }; + }, + // Get all messages from this actor's state + getMessages: (c) => { + return c.state.messages; + }, + // Clear messages + clearMessages: (c) => { + c.state.messages = []; + } + } }); -var workflowHistoryJoin = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "pending" + +// src/actors/testing/load-test-agent.ts +import { + actor as actor39 +} from "rivetkit"; +import { db as db5 } from "rivetkit/db"; +var DEFAULT_TOKENS_PER_SECOND = 20; +var DEFAULT_DURATION_MS = 5e3; +function send(websocket, payload2) { + if (websocket.readyState !== 1) return; + websocket.send(JSON.stringify(payload2)); +} +function parsePositiveNumber(value, name, fallback) { + if (value === void 0 || value === null) return fallback; + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`${name} must be a positive number`); + } + return parsed; +} +function sleep3(ms, signal) { + if (signal.aborted) return Promise.resolve(); + return new Promise((resolve2) => { + const timeout = setTimeout(resolve2, ms); + signal.addEventListener( + "abort", + () => { + clearTimeout(timeout); + resolve2(); + }, + { once: true } + ); + }); +} +var loadTestAgent = actor39({ + options: { + canHibernateWebSocket: false, + sleepGracePeriod: 5e3 + }, + db: db5({ + onMigrate: async (db17) => { + await db17.execute(` + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + connection_id TEXT NOT NULL, + request_id TEXT NOT NULL, + token_index INTEGER NOT NULL, + token TEXT NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await db17.execute(` + CREATE INDEX IF NOT EXISTS messages_request_idx + ON messages (request_id, token_index) + `); + } }), - actions: { - getState: (c) => c.state + state: { + connectionCount: 0, + inferenceCount: 0, + tokenCount: 0 }, - run: workflow9(async (ctx) => { - await ctx.step("start", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - return { ready: true }; + onWebSocket(c, websocket) { + c.state.connectionCount += 1; + const connectionId = crypto.randomUUID(); + send(websocket, { + type: "connected", + connectionId, + connectionCount: c.state.connectionCount, + timestamp: Date.now() }); - const results = await ctx.join("parallel-tasks", { - "fetch-api": { - run: async (branchCtx) => { - await branchCtx.step("task-a", async () => { - await delay(120); - return { fetched: true }; - }); - return { data: "api-response" }; - } - }, - "query-db": { - run: async (branchCtx) => { - await branchCtx.step("task-b", async () => { - await delay(200); - return { queried: true }; - }); - return { rows: 42 }; - } - }, - "check-cache": { - run: async (branchCtx) => { - await branchCtx.step("task-c", async () => { - await delay(60); - return { checked: true }; + websocket.addEventListener( + "message", + async (event19) => { + try { + const message = typeof event19.data === "string" ? JSON.parse(event19.data) : void 0; + if (message && message.type === "ping") { + send(websocket, { + type: "pong", + connectionId, + id: message.id, + timestamp: Date.now() + }); + return; + } + if (!message || message.type !== "inference") { + throw new Error("expected inference message"); + } + const requestId = typeof message.requestId === "string" && message.requestId ? message.requestId : crypto.randomUUID(); + const tokensPerSecond = parsePositiveNumber( + message.tokensPerSecond, + "tokensPerSecond", + DEFAULT_TOKENS_PER_SECOND + ); + const durationMs = parsePositiveNumber( + message.durationMs, + "durationMs", + DEFAULT_DURATION_MS + ); + const intervalMs = 1e3 / tokensPerSecond; + const targetTokens = Math.max( + 1, + Math.floor(durationMs / 1e3 * tokensPerSecond) + ); + const inference = (async () => { + c.state.inferenceCount += 1; + send(websocket, { + type: "inference-start", + connectionId, + requestId, + tokensPerSecond, + durationMs, + targetTokens, + timestamp: Date.now() + }); + const startedAt = performance.now(); + for (let i = 0; i < targetTokens; i++) { + if (c.abortSignal.aborted || websocket.readyState !== 1) { + break; + } + const tokenIndex = i + 1; + const token = `token-${tokenIndex}`; + const createdAt = Date.now(); + await c.db.execute( + "INSERT INTO messages (connection_id, request_id, token_index, token, created_at) VALUES (?, ?, ?, ?, ?)", + connectionId, + requestId, + tokenIndex, + token, + createdAt + ); + c.state.tokenCount += 1; + send(websocket, { + type: "token", + connectionId, + requestId, + tokenIndex, + token, + timestamp: createdAt + }); + const nextAt = startedAt + tokenIndex * intervalMs; + const delayMs = Math.max( + 0, + nextAt - performance.now() + ); + if (delayMs > 0) { + await sleep3(delayMs, c.abortSignal); + } + } + send(websocket, { + type: "inference-complete", + connectionId, + requestId, + tokenCount: targetTokens, + timestamp: Date.now() + }); + })(); + await c.keepAwake(inference); + } catch (error) { + send(websocket, { + type: "error", + message: error instanceof Error ? error.message : "unknown websocket error", + timestamp: Date.now() }); - return { hit: true }; } } + ); + websocket.addEventListener("close", () => { + c.state.connectionCount -= 1; }); - await ctx.step("merge-results", async () => { - const c = actorCtx(ctx); - c.state.status = "completed"; - c.state.result = { - api: results["fetch-api"].data, - rows: results["query-db"].rows, - cacheHit: results["check-cache"].hit + }, + actions: { + getStats(c) { + return { + connectionCount: c.state.connectionCount, + inferenceCount: c.state.inferenceCount, + tokenCount: c.state.tokenCount }; - return { merged: true }; - }); - }) + } + } }); -var workflowHistoryRace = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "running" - }), - actions: { - getState: (c) => c.state - }, - run: workflow9(async (ctx) => { - await ctx.step("begin", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - return { started: true }; - }); - const { winner, value } = await ctx.race("race-providers", [ - { - name: "provider-fast", - run: async (branchCtx) => { - await branchCtx.sleep("provider-fast-step", 50); - return { provider: "cdn-edge", latency: 12 }; - } - }, + +// src/actors/testing/load-test-agent-2.ts +import { + actor as actor40 +} from "rivetkit"; +import { db as db6 } from "rivetkit/db"; +var AsyncMutex = class { + locked = false; + waiters = []; + async acquire() { + if (!this.locked) { + this.locked = true; + return; + } + await new Promise((resolve2) => this.waiters.push(resolve2)); + this.locked = true; + } + release() { + const next = this.waiters.shift(); + if (next) { + next(); + return; + } + this.locked = false; + } +}; +function createSerializedDb(execute) { + const mutex = new AsyncMutex(); + let activeTransaction = null; + const createTransactionDb = () => { + const tx = Object.assign( + (query, ...values) => execute(query, ...values), { - name: "provider-slow", - run: async (branchCtx) => { - await branchCtx.sleep("provider-slow-step", 200); - return { provider: "origin", latency: 120 }; + withTransaction: async (_stats, fn) => fn(tx) + } + ); + return tx; + }; + const queryWithMutex = async (query, ...values) => { + await mutex.acquire(); + try { + return await execute(query, ...values); + } finally { + mutex.release(); + } + }; + return Object.assign(queryWithMutex, { + withTransaction: async (stats, fn) => { + if (activeTransaction) { + return fn(activeTransaction); + } + await mutex.acquire(); + const tx = createTransactionDb(); + try { + await executeTrackedQuery( + execute, + stats, + "transaction-begin", + "BEGIN" + ); + activeTransaction = tx; + try { + const result = await fn(tx); + activeTransaction = null; + await executeTrackedQuery( + execute, + stats, + "transaction-commit", + "COMMIT" + ); + return result; + } catch (error) { + activeTransaction = null; + await executeTrackedQuery( + execute, + stats, + "transaction-rollback", + "ROLLBACK" + ); + throw error; } + } finally { + activeTransaction = null; + mutex.release(); } - ]); - await ctx.step("use-result", async () => { - const c = actorCtx(ctx); - c.state.status = "completed"; - c.state.winner = winner; - c.state.result = value.provider; - return { used: value.provider }; - }); - }) -}); -var QUEUE_ORDER_CREATED = "order:created"; -var QUEUE_ORDER_UPDATED = "order:updated"; -var QUEUE_ORDER_ITEM = "order:item"; -var QUEUE_ORDER_ARTIFACT = "order:artifact"; -var QUEUE_ORDER_READY = "order:ready"; -var QUEUE_ORDER_OPTIONAL = "order:optional"; -var FULL_WORKFLOW_MESSAGE_SEEDS = [ - { name: QUEUE_ORDER_CREATED, payload: { id: "order-1" } }, - { name: QUEUE_ORDER_UPDATED, payload: { id: "order-1", status: "paid" } }, - { name: QUEUE_ORDER_ITEM, payload: { sku: "sku-0", qty: 1 } }, - { name: QUEUE_ORDER_ITEM, payload: { sku: "sku-4", qty: 1 } }, - { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-0" } }, - { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-1" } }, - { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-2" } }, - { name: QUEUE_ORDER_READY, payload: { batch: 3 } }, - { name: QUEUE_ORDER_READY, payload: { batch: 0 } }, - { name: QUEUE_ORDER_READY, payload: { batch: 2 } } -]; -var FULL_WORKFLOW_ITEMS = [ - { id: "item-1", basePrice: 100, tax: 8 }, - { id: "item-2", basePrice: 115, tax: 9 }, - { id: "item-3", basePrice: 130, tax: 10 }, - { id: "item-4", basePrice: 145, tax: 12 } -]; -var workflowHistoryFull = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "pending", - seededMessages: false - }), - queues: { - [QUEUE_ORDER_CREATED]: queue7(), - [QUEUE_ORDER_UPDATED]: queue7(), - [QUEUE_ORDER_ITEM]: queue7(), - [QUEUE_ORDER_ARTIFACT]: queue7(), - [QUEUE_ORDER_READY]: queue7(), - [QUEUE_ORDER_OPTIONAL]: queue7() + } + }); +} +var MESSAGE_COUNT = 84; +var MESSAGE_TOOL_REF_COUNT = 122; +var TOOL_CALL_COUNT = 61; +var EXECUTOR_TOOL_COUNT = 42; +var THREAD_EVENT_COUNT = 233; +var MESSAGE_CONTENT_BYTES = 2600; +var THREAD_EVENT_PAYLOAD_BYTES = 1e3; +var TOOL_CALL_RESULT_BYTES = 2700; +var EXECUTOR_TOOL_SCHEMA_BYTES = 550; +var SLOW_QUERY_MS = 1e3; +function send2(websocket, message) { + if (websocket.readyState === 1) { + websocket.send(JSON.stringify(message)); + } +} +var loadTestAgent2 = actor40({ + options: { + canHibernateWebSocket: false, + sleepGracePeriod: 5e3 }, - actions: { - getState: (c) => c.state, - seedMessages: async (c) => { - if (c.state.seededMessages) return; - for (const seed of FULL_WORKFLOW_MESSAGE_SEEDS) { - await c.queue.send(seed.name, seed.payload); - } - c.state.seededMessages = true; + state: { + runCount: 0, + wakeCount: 0, + queryStats: createAgentConcurrent2QueryStats() + }, + db: db6({ + onMigrate: async (database) => { + await createAgentConcurrent2Schema(database); + await seedAgentConcurrent2Data(database); } + }), + vars: { + sql: null, + wakeStats: null, + wakeStartedAt: null, + wakeIteration: 0 }, - run: workflow9(async (ctx) => { - await ctx.step("bootstrap", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - c.state.lastStep = "bootstrap"; - c.state.startedAt = Date.now(); - return { - requestId: `req-${c.state.id}`, - startedAt: Date.now() - }; + onWebSocket: (c, websocket) => { + send2(websocket, { + type: "connected", + timestamp: Date.now() }); - await ctx.step("validate-input", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "validate-input"; - return true; + websocket.addEventListener("message", (event19) => { + const promise = handleAgentConcurrent2Message( + c, + websocket, + event19.data + ); + void c.keepAwake(promise); }); - await ctx.rollbackCheckpoint("checkpoint-after-validation"); - await ctx.step("load-user-profile", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "load-user-profile"; + }, + actions: { + run: async (c, clientId) => { + const runtime = ensureAgentConcurrent2Runtime(c); + c.state.runCount++; + runtime.vars.wakeIteration++; + const cycleStats = createAgentConcurrent2QueryStats(); + const stats = createAgentConcurrent2StatsSet( + cycleStats, + runtime.wakeStats, + c.state.queryStats + ); + const result = await runAgentConcurrent2Workload( + runtime.sql, + clientId ?? `agent2-action-${c.state.runCount}`, + 0, + stats + ); return { - id: "user-123", - tier: "standard", - flags: ["email-verified", "promo-eligible"] - }; - }); - await ctx.step("compute-discount", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "compute-discount"; - return { percent: 5, reason: "tier-discount" }; - }); - await ctx.step("ephemeral-cache-check", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "ephemeral-cache-check"; - return { cacheHit: false, tier: "standard" }; - }); - await ctx.rollbackCheckpoint("checkpoint-before-reserve"); - await ctx.loop({ - name: "process-items-loop", - state: { index: 0 }, - commitInterval: 1, - historyEvery: 1, - historyKeep: 2, - run: async (loopCtx, loopState) => { - const item = FULL_WORKFLOW_ITEMS[loopState.index]; - if (!item) { - return Loop9.break({ count: FULL_WORKFLOW_ITEMS.length }); - } - await loopCtx.step(`fetch-item-${loopState.index}`, async () => { - return { itemId: item.id, basePrice: item.basePrice }; - }); - await loopCtx.step(`compute-tax-${loopState.index}`, async () => { - return item.tax; - }); - await loopCtx.step( - `reserve-inventory-${loopState.index}`, - async () => ({ - reservationId: `res-${loopState.index}`, - itemId: item.id - }) - ); - if (loopState.index >= FULL_WORKFLOW_ITEMS.length - 1) { - return Loop9.break({ - count: FULL_WORKFLOW_ITEMS.length, - total: 504 - }); - } - return Loop9.continue({ index: loopState.index + 1 }); - } - }); - await ctx.sleep("short-cooldown", 40); - await ctx.sleep("cooldown-sleep", 60); - await ctx.sleep("wait-until-deadline", 45); - await ctx.step("compute-deadlines", async () => { - const readyBy = Date.now() + 800; - const readyBatchBy = Date.now() + 1100; - return { readyBy, readyBatchBy }; - }); - await ctx.queue.next("listen-order-created", { - names: [QUEUE_ORDER_CREATED] - }); - await ctx.queue.nextBatch("listen-order-updated-timeout", { - names: [QUEUE_ORDER_UPDATED], - timeout: 250 - }); - await ctx.queue.nextBatch("listen-batch-two", { - names: [QUEUE_ORDER_ITEM], - count: 2 - }); - await ctx.queue.nextBatch("listen-artifacts-timeout", { - names: [QUEUE_ORDER_ARTIFACT], - count: 3, - timeout: 300 - }); - await ctx.queue.nextBatch("listen-optional", { - names: [QUEUE_ORDER_OPTIONAL], - timeout: 200 - }); - await ctx.queue.nextBatch("listen-until", { - names: [QUEUE_ORDER_READY], - timeout: 300 - }); - await ctx.queue.nextBatch("listen-batch-until", { - names: [QUEUE_ORDER_READY], - count: 2, - timeout: 400 - }); - await ctx.join("join-dependencies", { - inventory: { - run: async (branchCtx) => { - const reserved = await branchCtx.step( - "inventory-audit", - async () => 4 - ); - await branchCtx.sleep("join-inventory-sleep", 35); - return { - reserved, - checked: 4, - notes: ["inventory-ok", "items=4"] - }; - } - }, - pricing: { - run: async (branchCtx) => { - const method = await branchCtx.step( - "pricing-method", - async () => "promo" - ); - return { - subtotal: 504, - discount: 25, - total: 479, - method - }; - } - }, - shipping: { - run: async (branchCtx) => { - const zone = await branchCtx.step( - "shipping-zone", - async () => "us-east" - ); - await branchCtx.sleep("join-shipping-sleep", 35); - return { method: "ground", etaDays: 4, zone }; - } - } - }); - await ctx.race("race-fulfillment", [ - { - name: "race-fast", - run: async (branchCtx) => { - await branchCtx.sleep("race-fast-sleep", 70); - return { method: "express", cost: 18, etaDays: 1 }; - } - }, - { - name: "race-slow", - run: async (branchCtx) => { - await branchCtx.sleep("race-slow-sleep", 250); - return { method: "ground", cost: 8, etaDays: 4 }; - } - } - ]); - await ctx.removed("legacy-step-placeholder", "step"); - await ctx.step("finalize", async () => { - const c = actorCtx(ctx); - c.state.lastStep = "finalize"; - c.state.status = "completed"; - c.state.completedAt = Date.now(); - return true; - }); - }) -}); -var workflowHistoryInProgress = actor43({ - createState: (c, input) => ({ - id: c.key[0], - status: "running", - processingDurationMs: input?.processingDurationMs ?? 3e4, - progress: 0 - }), - actions: { - getState: (c) => c.state - }, - run: workflow9(async (ctx) => { - await ctx.step("init", async () => { - const c = actorCtx(ctx); - c.state.startedAt = Date.now(); - c.state.progress = 10; - return { initialized: true }; - }); - await ctx.step("fetch-data", async () => { - const c = actorCtx(ctx); - c.state.progress = 25; - return { fetched: true, records: 100 }; - }); - await ctx.step("process", async () => { - const c = actorCtx(ctx); - c.state.progress = 42; - await delay(c.state.processingDurationMs); - c.state.status = "completed"; - c.state.completedAt = Date.now(); - return { processed: true }; - }); - }) -}); -var RETRY_MAX_RETRIES = 20; -var workflowHistoryRetrying = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "running", - attempts: 0, - succeedAfter: 999 - }), - actions: { - getState: (c) => c.state, - allowSuccess: (c, afterAttempt) => { - c.state.succeedAfter = afterAttempt ?? c.state.attempts + 1; - } - }, - run: workflow9(async (ctx) => { - await ctx.step("start", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - return { ready: true }; - }); - await ctx.step({ - name: "api-call", - maxRetries: RETRY_MAX_RETRIES, - retryBackoffBase: 250, - retryBackoffMax: 1500, - run: async () => { - const c = actorCtx(ctx); - c.state.attempts += 1; - if (c.state.attempts < c.state.succeedAfter) { - const error = new Error("Connection timeout after 5000ms"); - c.state.lastError = error.message; - throw error; - } - c.state.status = "completed"; - c.state.lastError = void 0; - return { success: true }; - } - }); - }) -}); -var FAILED_MAX_RETRIES = 3; -var workflowHistoryFailed = actor43({ - createState: (c) => ({ - id: c.key[0], - status: "running", - attempts: 0 - }), - actions: { - getState: (c) => c.state - }, - run: workflow9(async (ctx) => { - await ctx.step("init", async () => { - const c = actorCtx(ctx); - c.state.status = "running"; - return { initialized: true }; - }); - await ctx.step("validate", async () => { - return { valid: true }; - }); - await ctx.step({ - name: "process", - maxRetries: FAILED_MAX_RETRIES, - retryBackoffBase: 200, - retryBackoffMax: 800, - run: async () => { - const c = actorCtx(ctx); - c.state.attempts += 1; - const error = new Error( - "Database connection failed: ECONNREFUSED" - ); - c.state.lastError = error.message; - throw error; - } - }); - }) -}); - -// src/actors/inter-actor/cross-actor-actions.ts -import { actor as actor44 } from "rivetkit"; -var inventory = actor44({ - // Each item has its own inventory actor instance - createState: (_c, input) => ({ - itemName: input?.itemName ?? "Widget", - stock: input?.initialStock ?? 100, - reservations: [] - }), - actions: { - // Check current stock - getStock: (c) => ({ - itemName: c.state.itemName, - stock: c.state.stock - }), - // Reserve items for checkout (called by checkout actor) - reserveItems: (c, checkoutId, quantity) => { - if (c.state.stock < quantity) { - return { - success: false, - message: `Insufficient stock. Available: ${c.state.stock}, Requested: ${quantity}`, - availableStock: c.state.stock - }; - } - c.state.stock -= quantity; - c.state.reservations.push(checkoutId); - return { - success: true, - message: `Reserved ${quantity} items for checkout ${checkoutId}`, - remainingStock: c.state.stock + ...result, + stats: snapshotAgentConcurrent2Stats(c, cycleStats) }; }, - // Release reserved items if checkout is cancelled - releaseItems: (c, checkoutId, quantity) => { - const index = c.state.reservations.indexOf(checkoutId); - if (index > -1) { - c.state.reservations.splice(index, 1); - c.state.stock += quantity; - } - return { - success: true, - remainingStock: c.state.stock - }; + getRunCount: (c) => c.state.runCount, + sleep: (c) => { + c.sleep(); + return true; } } }); -var checkout = actor44({ - createState: (_c, input) => ({ - customerName: input?.customerName ?? "Guest", - items: [], - completed: false - }), - actions: { - // Add item to checkout and reserve from inventory - addItem: async (c, itemId, quantity) => { - const inventoryActor = c.client().inventory.getOrCreate([itemId]); - const itemInfo = await inventoryActor.getStock(); - const reservation = await inventoryActor.reserveItems( - c.actorId, - // Use checkout ID as reservation ID - quantity - ); - if (!reservation.success) { - return { - success: false, - message: reservation.message - }; - } - c.state.items.push({ - itemId, - itemName: itemInfo.itemName, - quantity +async function handleAgentConcurrent2Message(c, websocket, data) { + let trigger = "unknown"; + let cycleStats = null; + try { + const request = parseAgentConcurrent2Request(data); + trigger = request.type; + if (request.type === "ping") { + send2(websocket, { + type: "pong", + id: request.id, + timestamp: Date.now() }); - return { - success: true, - message: `Added ${quantity} ${itemInfo.itemName} to checkout`, - remainingStock: reservation.remainingStock - }; - }, - // Get checkout summary - getSummary: (c) => ({ - customerName: c.state.customerName, - items: c.state.items, - completed: c.state.completed, - totalItems: c.state.items.reduce( - (sum, item) => sum + item.quantity, - 0 - ) - }), - // Complete the checkout - completeCheckout: (c) => { - c.state.completed = true; - return { - success: true, - message: "Checkout completed successfully" - }; - }, - // Cancel checkout and release all reservations - cancelCheckout: async (c) => { - for (const item of c.state.items) { - const inventoryActor = c.client().inventory.getOrCreate([item.itemId]); - await inventoryActor.releaseItems(c.actorId, item.quantity); - } - c.state.items = []; - return { - success: true, - message: "Checkout cancelled, items returned to inventory" - }; + return; + } + if (request.type === "force_sleep") { + send2(websocket, { type: "sleeping", timestamp: Date.now() }); + c.sleep(); + return; + } + const runtime = ensureAgentConcurrent2Runtime(c); + c.state.runCount++; + runtime.vars.wakeIteration++; + cycleStats = createAgentConcurrent2QueryStats(); + const stats = createAgentConcurrent2StatsSet( + cycleStats, + runtime.wakeStats, + c.state.queryStats + ); + if (request.type === "agent2_resume") { + const startedAt = performance.now(); + const result2 = await runCatchupSnapshot( + runtime.sql, + request.version, + stats + ); + send2(websocket, { + type: "agent2_result", + trigger: request.type, + totalMs: Math.round(performance.now() - startedAt), + results: [result2], + stats: snapshotAgentConcurrent2Stats(c, cycleStats) + }); + return; } + const result = await runAgentConcurrent2Workload( + runtime.sql, + request.clientId, + request.staggerHandleMs ?? 0, + stats + ); + send2(websocket, { + type: "agent2_result", + trigger: request.type, + ...result, + stats: snapshotAgentConcurrent2Stats(c, cycleStats) + }); + } catch (error) { + send2(websocket, { + type: "agent2_error", + trigger, + error: error instanceof Error ? error.message : String(error), + ...cycleStats ? { stats: snapshotAgentConcurrent2Stats(c, cycleStats) } : {} + }); } -}); - -// src/actors/testing/inline-client.ts -import { actor as actor45 } from "rivetkit"; -function isDynamicSandboxRuntime() { - return false; } -async function waitForConnectionOpen(connection) { - if (connection.connStatus === "connected") { - return; +function parseAgentConcurrent2Request(data) { + if (typeof data !== "string") { + throw new Error("agent concurrent 2 request must be a string"); } - await new Promise((resolve, reject) => { - const unsubscribeOpen = connection.onOpen(() => { - unsubscribeOpen(); - unsubscribeError(); - resolve(); - }); - const unsubscribeError = connection.onError((error) => { - unsubscribeOpen(); - unsubscribeError(); - reject(error); - }); - }); + const parsed = JSON.parse(data); + if (!parsed || typeof parsed !== "object") { + throw new Error("agent concurrent 2 request must be an object"); + } + const request = parsed; + if (request.type === "ping") { + return { + type: "ping", + ...typeof request.id === "number" ? { id: request.id } : {} + }; + } + if (request.type === "force_sleep") { + return { type: "force_sleep" }; + } + if (request.type === "agent2_resume") { + return { + type: "agent2_resume", + version: numberField(request, "version") + }; + } + if (request.type === "agent2_connect") { + return { + type: "agent2_connect", + clientId: stringField(request, "clientId"), + ...typeof request.staggerHandleMs === "number" ? { staggerHandleMs: request.staggerHandleMs } : {} + }; + } + throw new Error( + `unknown agent concurrent 2 request type: ${String(request.type)}` + ); } -var inlineClientActor = actor45({ - state: { messages: [] }, - actions: { - // Action that uses client to call another actor (stateless) - callCounterIncrement: async (c, amount) => { - const client = c.client(); - const result = await client.counter.getOrCreate(["inline-test"]).increment(amount); - c.state.messages.push( - `Called counter.increment(${amount}), result: ${result}` - ); - return result; - }, - // Action that uses client to get counter state (stateless) - getCounterState: async (c) => { - const client = c.client(); - const count = await client.counter.getOrCreate(["inline-test"]).getCount(); - c.state.messages.push(`Got counter state: ${count}`); - return count; - }, - // Action that uses client with .connect() for stateful communication - connectToCounterAndIncrement: async (c, amount) => { - const client = c.client(); - const handle = client.counter.getOrCreate(["inline-test-stateful"]); - if (isDynamicSandboxRuntime()) { - const events2 = []; - const result12 = await handle.increment(amount); - events2.push(result12); - const result22 = await handle.increment(amount * 2); - events2.push(result22); - c.state.messages.push( - `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result12}, ${result22}, events: ${JSON.stringify(events2)}` - ); - return { result1: result12, result2: result22, events: events2 }; - } - await handle.getCount(); - const connection = handle.connect(); - await waitForConnectionOpen(connection); - const events = []; - connection.on("newCount", (count) => { - events.push(count); - }); - const result1 = await connection.increment(amount); - const result2 = await connection.increment(amount * 2); - await connection.dispose(); - c.state.messages.push( - `Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}` - ); - return { result1, result2, events }; - }, - // Get all messages from this actor's state - getMessages: (c) => { - return c.state.messages; - }, - // Clear messages - clearMessages: (c) => { - c.state.messages = []; - } +function stringField(record, field) { + const value = record[field]; + if (typeof value !== "string" || value.length === 0) { + throw new Error(`agent concurrent 2 request ${field} must be a string`); } -}); - -// src/actors/testing/test-counter.ts -import { actor as actor46 } from "rivetkit"; -var testCounter = actor46({ - state: { count: 0 }, - actions: { - increment: (c, amount = 1) => { - c.state.count += amount; - return c.state.count; - }, - getCount: (c) => { - return c.state.count; - }, - reset: (c) => { - c.state.count = 0; - return c.state.count; - } + return value; +} +function numberField(record, field) { + const value = record[field]; + if (typeof value !== "number" || !Number.isFinite(value)) { + throw new Error( + `agent concurrent 2 request ${field} must be a finite number` + ); } -}); - -// src/actors/testing/test-counter-sqlite.ts -import { actor as actor47 } from "rivetkit"; -import { db as db4 } from "rivetkit/db"; -var testCounterSqlite = actor47({ - db: db4({ - onMigrate: async (db16) => { - await db16.execute(` - CREATE TABLE IF NOT EXISTS counter ( - id INTEGER PRIMARY KEY CHECK (id = 1), - value INTEGER NOT NULL DEFAULT 0 - ) - `); - await db16.execute( - "INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)" - ); - } - }), - actions: { - increment: async (c, amount = 1) => { - await c.db.execute( - "UPDATE counter SET value = value + ? WHERE id = 1", - amount - ); - const rows = await c.db.execute( - "SELECT value FROM counter WHERE id = 1" - ); - return rows[0].value; - }, - getCount: async (c) => { - const rows = await c.db.execute( - "SELECT value FROM counter WHERE id = 1" + return value; +} +function createAgentConcurrent2Db(db17) { + return createSerializedDb( + async (query, ...values) => { + const converted = values.map( + (value) => typeof value === "boolean" ? value ? 1 : 0 : value ); - return rows[0].value; - }, - reset: async (c) => { - await c.db.execute("UPDATE counter SET value = 0 WHERE id = 1"); - return 0; + return await db17.execute(query, ...converted); } + ); +} +function ensureAgentConcurrent2Runtime(c) { + c.vars.sql ??= createAgentConcurrent2Db(c.db); + c.state.queryStats ??= createAgentConcurrent2QueryStats(); + c.state.wakeCount ??= 0; + if (!c.vars.wakeStats) { + c.vars.wakeStats = createAgentConcurrent2QueryStats(); + c.vars.wakeStartedAt = Date.now(); + c.vars.wakeIteration = 0; + c.state.wakeCount++; } -}); - -// src/actors/testing/test-sqlite-load.ts -import { actor as actor48 } from "rivetkit"; -import { db as db5 } from "rivetkit/db"; -var testSqliteLoad = actor48({ - db: db5({ - onMigrate: async (db16) => { - await db16.execute(` - CREATE TABLE IF NOT EXISTS schema_version ( - id INTEGER PRIMARY KEY CHECK (id = 1), - version INTEGER NOT NULL DEFAULT 50 - ) - `); - await db16.execute( - "INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, 50)" + return { + sql: c.vars.sql, + wakeStats: c.vars.wakeStats, + vars: c.vars + }; +} +function createAgentConcurrent2QueryStats() { + return { + total: 0, + reads: 0, + mutations: 0, + tx: 0, + other: 0, + rows: 0, + errors: 0, + slow: 0, + maxMs: 0, + maxStep: "", + byOperation: {}, + byTable: {} + }; +} +function createAgentConcurrent2StatsSet(cycle, wake, actor62) { + return { cycle, wake, actor: actor62 }; +} +function snapshotAgentConcurrent2Stats(c, cycle) { + return { + wakeIndex: c.state.wakeCount, + actorIteration: c.state.runCount, + wakeIteration: c.vars.wakeIteration, + cycle: cloneAgentConcurrent2QueryStats(cycle), + wake: cloneAgentConcurrent2QueryStats( + c.vars.wakeStats ?? createAgentConcurrent2QueryStats() + ), + actor: cloneAgentConcurrent2QueryStats(c.state.queryStats) + }; +} +function cloneAgentConcurrent2QueryStats(stats) { + return { + total: stats.total, + reads: stats.reads, + mutations: stats.mutations, + tx: stats.tx, + other: stats.other, + rows: stats.rows, + errors: stats.errors, + slow: stats.slow, + maxMs: stats.maxMs, + maxStep: stats.maxStep, + byOperation: { ...stats.byOperation }, + byTable: { ...stats.byTable } + }; +} +async function runAgentConcurrent2Workload(sql, clientId, staggerHandleMs, stats) { + const startedAt = performance.now(); + const buildToolPlanContext = runBuildToolPlanContext(sql, stats); + const catchupSnapshot = runCatchupSnapshot(sql, 0, stats); + const recoverToolCalls = runRecoverToolCalls(sql, stats); + const mutationMix = runMutationMix(sql, clientId, stats); + const handleExecutorConnect = delay(staggerHandleMs).then( + () => runHandleClientConnect(sql, clientId, stats) + ); + const results = await Promise.all([ + handleExecutorConnect, + buildToolPlanContext, + catchupSnapshot, + recoverToolCalls, + mutationMix + ]); + return { + totalMs: Math.round(performance.now() - startedAt), + results + }; +} +async function runHandleClientConnect(sql, clientId, stats) { + const startedAt = performance.now(); + const steps = []; + const nextSeq = await sql.withTransaction(stats, async (tx) => { + const latestExecutor = await timedQuery( + tx, + stats, + steps, + "load-latest-executor-id", + `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` + ); + const latestExecutorId = String( + latestExecutor[0]?.executor_id ?? "seed-executor" + ); + await timedQuery( + tx, + stats, + steps, + "select-cached-executor-tools", + `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, + latestExecutorId + ); + const executorType = await timedQuery( + tx, + stats, + steps, + "select-executor-type", + `SELECT value FROM thread_meta_kv WHERE key = 'executor_type'` + ); + if (!executorType[0]?.value) { + await timedQuery( + tx, + stats, + steps, + "set-executor-type", + `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`, + "local-client", + (/* @__PURE__ */ new Date()).toISOString() ); - await db16.execute(` - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - email TEXT, - created_at INTEGER NOT NULL - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS products ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - price REAL NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS orders ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - total REAL NOT NULL DEFAULT 0, - status TEXT NOT NULL DEFAULT 'pending', - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS order_items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - quantity INTEGER NOT NULL DEFAULT 1, - price REAL NOT NULL, - FOREIGN KEY (order_id) REFERENCES orders(id), - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS categories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - description TEXT + } + const sandboxIntent = await timedQuery( + tx, + stats, + steps, + "select-workspace-intent", + `SELECT value FROM thread_meta_kv WHERE key = 'workspace_intent'` + ); + if (hasPendingLaunch(sandboxIntent[0]?.value)) { + await timedQuery( + tx, + stats, + steps, + "clear-pending-launch", + `UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'workspace_intent'`, + JSON.stringify({ spec: null, pendingLaunch: null }), + (/* @__PURE__ */ new Date()).toISOString() + ); + } + const seqRows = await timedQuery( + tx, + stats, + steps, + "select-next-thread-event-seq", + `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` + ); + const seq = Number(seqRows[0]?.seq ?? 1); + await timedQuery( + tx, + stats, + steps, + "insert-client-connected-event", + `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, + seq, + "client_connected", + JSON.stringify({ type: "client_connected", clientId }), + (/* @__PURE__ */ new Date()).toISOString() + ); + return seq; + }); + steps.push({ + name: "transaction-total", + durationMs: Math.round(performance.now() - startedAt), + rowCount: nextSeq + }); + return { + name: "handle-client-connect", + totalMs: Math.round(performance.now() - startedAt), + steps + }; +} +async function runBuildToolPlanContext(sql, stats) { + const startedAt = performance.now(); + const steps = []; + const latestExecutor = await timedQuery( + sql, + stats, + steps, + "load-latest-executor-id", + `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` + ); + const latestExecutorId = String( + latestExecutor[0]?.executor_id ?? "seed-executor" + ); + await timedQuery( + sql, + stats, + steps, + "select-executor-tools", + `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, + latestExecutorId + ); + await timedQuery( + sql, + stats, + steps, + "count-uncancelled-top-level", + `SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL` + ); + const unresolvedRows = await timedQuery( + sql, + stats, + steps, + "find-unresolved-assistant-message", + `SELECT m.* + FROM message_tool_refs AS tool_use + JOIN messages AS m + ON m.message_id = tool_use.assistant_message_id + WHERE tool_use.block_type = 'tool_use' + AND tool_use.cancelled = 0 + AND m.cancelled = 0 + AND m.role = 'assistant' + AND m.parent_tool_use_id IS NULL + AND NOT EXISTS ( + SELECT 1 + FROM message_tool_refs AS tool_result + JOIN messages AS tool_result_message + ON tool_result_message.message_id = tool_result.source_message_id + WHERE tool_result.assistant_message_id = tool_use.assistant_message_id + AND tool_result.block_type = 'tool_result' + AND tool_result.cancelled = 0 + AND tool_result.tool_use_id = tool_use.tool_use_id + AND tool_result_message.parent_tool_use_id IS NULL ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS product_categories ( - product_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - PRIMARY KEY (product_id, category_id), - FOREIGN KEY (product_id) REFERENCES products(id), - FOREIGN KEY (category_id) REFERENCES categories(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS reviews ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5), - comment TEXT, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id), - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS addresses ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - street TEXT NOT NULL, - city TEXT NOT NULL, - state TEXT, - zip TEXT, - country TEXT NOT NULL DEFAULT 'US', - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS payments ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - amount REAL NOT NULL, - method TEXT NOT NULL DEFAULT 'card', - status TEXT NOT NULL DEFAULT 'pending', - processed_at INTEGER, - FOREIGN KEY (order_id) REFERENCES orders(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS inventory ( - product_id INTEGER PRIMARY KEY, - quantity INTEGER NOT NULL DEFAULT 0, - reserved INTEGER NOT NULL DEFAULT 0, - last_restocked_at INTEGER, - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS coupons ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - code TEXT NOT NULL UNIQUE, - discount_percent REAL NOT NULL, - max_uses INTEGER, - used_count INTEGER NOT NULL DEFAULT 0, - expires_at INTEGER - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS shipping ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - address_id INTEGER NOT NULL, - carrier TEXT, - tracking_number TEXT, - shipped_at INTEGER, - delivered_at INTEGER, - FOREIGN KEY (order_id) REFERENCES orders(id), - FOREIGN KEY (address_id) REFERENCES addresses(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS tags ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS product_tags ( - product_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - PRIMARY KEY (product_id, tag_id), - FOREIGN KEY (product_id) REFERENCES products(id), - FOREIGN KEY (tag_id) REFERENCES tags(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS wishlists ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - name TEXT NOT NULL DEFAULT 'Default', - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS wishlist_items ( - wishlist_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - added_at INTEGER NOT NULL, - PRIMARY KEY (wishlist_id, product_id), - FOREIGN KEY (wishlist_id) REFERENCES wishlists(id), - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS notifications ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - type TEXT NOT NULL, - message TEXT NOT NULL, - read INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS audit_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - entity_type TEXT NOT NULL, - entity_id INTEGER NOT NULL, - action TEXT NOT NULL, - details TEXT, - performed_at INTEGER NOT NULL - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - user_id INTEGER NOT NULL, - token TEXT NOT NULL UNIQUE, - expires_at INTEGER NOT NULL, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_reviews_product ON reviews(product_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_reviews_user ON reviews(user_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_inventory_quantity ON inventory(quantity)" - ); - await db16.execute(` - CREATE TABLE IF NOT EXISTS returns ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - reason TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'requested', - requested_at INTEGER NOT NULL, - processed_at INTEGER, - FOREIGN KEY (order_id) REFERENCES orders(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS return_items ( - return_id INTEGER NOT NULL, - order_item_id INTEGER NOT NULL, - quantity INTEGER NOT NULL, - PRIMARY KEY (return_id, order_item_id), - FOREIGN KEY (return_id) REFERENCES returns(id), - FOREIGN KEY (order_item_id) REFERENCES order_items(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS suppliers ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - contact_email TEXT, - country TEXT - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS product_suppliers ( - product_id INTEGER NOT NULL, - supplier_id INTEGER NOT NULL, - cost REAL NOT NULL, - lead_time_days INTEGER, - PRIMARY KEY (product_id, supplier_id), - FOREIGN KEY (product_id) REFERENCES products(id), - FOREIGN KEY (supplier_id) REFERENCES suppliers(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS price_history ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - old_price REAL NOT NULL, - new_price REAL NOT NULL, - changed_at INTEGER NOT NULL, - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS user_preferences ( - user_id INTEGER PRIMARY KEY, - theme TEXT NOT NULL DEFAULT 'dark', - language TEXT NOT NULL DEFAULT 'en', - notifications_enabled INTEGER NOT NULL DEFAULT 1, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS cart ( - user_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - quantity INTEGER NOT NULL DEFAULT 1, - added_at INTEGER NOT NULL, - PRIMARY KEY (user_id, product_id), - FOREIGN KEY (user_id) REFERENCES users(id), - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS saved_searches ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - query TEXT NOT NULL, - filters TEXT, - created_at INTEGER NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS product_images ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - url TEXT NOT NULL, - alt_text TEXT, - sort_order INTEGER NOT NULL DEFAULT 0, - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute(` - CREATE TABLE IF NOT EXISTS discounts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER NOT NULL, - discount_percent REAL NOT NULL, - starts_at INTEGER NOT NULL, - ends_at INTEGER, - FOREIGN KEY (product_id) REFERENCES products(id) - ) - `); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_order_items_product ON order_items(product_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_shipping_order ON shipping(order_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_returns_order ON returns(order_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_price_history_product ON price_history(product_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_product_images_product ON product_images(product_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_discounts_product ON discounts(product_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id)" - ); - await db16.execute( - "CREATE INDEX IF NOT EXISTS idx_saved_searches_user ON saved_searches(user_id)" - ); - await db16.execute(` - CREATE TABLE IF NOT EXISTS counter ( - id INTEGER PRIMARY KEY CHECK (id = 1), - value INTEGER NOT NULL DEFAULT 0 - ) - `); - await db16.execute( - "INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)" - ); - } - }), - actions: { - increment: async (c, amount = 1) => { - await c.db.execute( - "UPDATE counter SET value = value + ? WHERE id = 1", - amount - ); - const rows = await c.db.execute( - "SELECT value FROM counter WHERE id = 1" - ); - return rows[0].value; - }, - getCount: async (c) => { - const rows = await c.db.execute( - "SELECT value FROM counter WHERE id = 1" - ); - return rows[0].value; - }, - reset: async (c) => { - await c.db.execute("UPDATE counter SET value = 0 WHERE id = 1"); - return 0; - }, - runLoadTest: async (c) => { - const now = Date.now(); - const results = []; - await c.db.execute( - "INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)", - "Load Test User", - `load-${now}@test.com`, - now - ); - results.push("inserted user"); - const users = await c.db.execute( - "SELECT * FROM users WHERE email = ?", - `load-${now}@test.com` - ); - results.push(`fetched user: ${users[0].name}`); - const userId = users[0].id; - await c.db.execute( - "INSERT INTO products (name, price, created_at) VALUES (?, ?, ?)", - "Test Widget", - 29.99, - now - ); - results.push("inserted product"); - const products = await c.db.execute("SELECT * FROM products LIMIT 10"); - results.push(`fetched ${products.length} products`); - const productId = products[0].id; - await c.db.execute( - "INSERT OR IGNORE INTO categories (name, description) VALUES (?, ?)", - `test-cat-${now}`, - "A test category" - ); - results.push("inserted category"); - const categories = await c.db.execute("SELECT * FROM categories"); - results.push(`fetched ${categories.length} categories`); - await c.db.execute( - "INSERT INTO orders (user_id, total, status, created_at) VALUES (?, ?, ?, ?)", - userId, - 29.99, - "pending", - now - ); - results.push("inserted order"); - const orders = await c.db.execute( - "SELECT * FROM orders WHERE user_id = ?", - userId - ); - results.push(`fetched ${orders.length} orders for user`); - const orderId = orders[0].id; - await c.db.execute( - "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)", - orderId, - productId, - 2, - 29.99 - ); - results.push("inserted order item"); - await c.db.execute( - "INSERT OR REPLACE INTO inventory (product_id, quantity, reserved, last_restocked_at) VALUES (?, ?, ?, ?)", - productId, - 100, - 2, - now - ); - results.push("inserted inventory"); - await c.db.execute( - "INSERT INTO reviews (user_id, product_id, rating, comment, created_at) VALUES (?, ?, ?, ?, ?)", - userId, - productId, - 5, - "Great product!", - now - ); - results.push("inserted review"); - const reviews = await c.db.execute( - "SELECT r.*, u.name as reviewer FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.product_id = ?", - productId - ); - results.push(`fetched ${reviews.length} reviews`); - await c.db.execute( - "INSERT INTO notifications (user_id, type, message, created_at) VALUES (?, ?, ?, ?)", - userId, - "order", - "Your order has been placed", - now - ); - results.push("inserted notification"); - await c.db.execute( - "INSERT INTO audit_log (entity_type, entity_id, action, details, performed_at) VALUES (?, ?, ?, ?, ?)", - "order", - orderId, - "created", - `Order created by user ${userId}`, - now - ); - results.push("inserted audit log"); - const orderStats = await c.db.execute( - "SELECT status, COUNT(*) as count, SUM(total) as total_value FROM orders GROUP BY status" - ); - results.push( - `order stats: ${orderStats.length} statuses` - ); - await c.db.execute( - "INSERT INTO addresses (user_id, street, city, state, zip, country) VALUES (?, ?, ?, ?, ?, ?)", - userId, - "123 Test St", - "Testville", - "CA", - "90210", - "US" - ); - results.push("inserted address"); - const orderDetails = await c.db.execute(` - SELECT o.id, o.status, o.total, u.name as customer, COUNT(oi.id) as item_count - FROM orders o - JOIN users u ON o.user_id = u.id - LEFT JOIN order_items oi ON oi.order_id = o.id - GROUP BY o.id - LIMIT 10 - `); - results.push( - `fetched ${orderDetails.length} order details` - ); - await c.db.execute( - "UPDATE orders SET status = ? WHERE id = ?", - "completed", - orderId - ); - results.push("updated order status"); - const version = await c.db.execute( - "SELECT version FROM schema_version WHERE id = 1" - ); - results.push( - `schema version: ${version[0].version}` - ); - const tableCounts = await c.db.execute(` - SELECT 'users' as tbl, COUNT(*) as cnt FROM users - UNION ALL SELECT 'products', COUNT(*) FROM products - UNION ALL SELECT 'orders', COUNT(*) FROM orders - UNION ALL SELECT 'reviews', COUNT(*) FROM reviews - UNION ALL SELECT 'categories', COUNT(*) FROM categories - `); - results.push( - `table counts: ${tableCounts.length} tables checked` - ); - return { - queriesRun: 20, - results - }; - } - } -}); - -// src/actors/testing/test-sqlite-bench.ts -import { actor as actor49 } from "rivetkit"; -import { db as db6 } from "rivetkit/db"; -var CHAT_LOG_CHUNK_BYTES = 4 * 1024; -var CHAT_LOG_INSERT_BATCH_SIZE = 50; -function buildChatLogMessage(seq, targetBytes) { - const prefix = `message-${seq}: `; - return prefix + "x".repeat(Math.max(0, targetBytes - prefix.length)); -} -async function seedChatLog(database, targetBytes) { - const threadId = `chat-${crypto.randomUUID()}`; - const createdAtBase = Date.now(); - let remainingBytes = targetBytes; - let rows = 0; - await database.execute("BEGIN"); - try { - while (remainingBytes > 0) { - const placeholders = []; - const args = []; - for (let batchIndex = 0; batchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0; batchIndex++) { - const contentBytes = Math.min(CHAT_LOG_CHUNK_BYTES, remainingBytes); - const seq = rows; - const role = seq % 2 === 0 ? "user" : "assistant"; - placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); - args.push( - threadId, - seq, - role, - buildChatLogMessage(seq, contentBytes), - contentBytes, - Math.ceil(contentBytes / 4), - createdAtBase + seq - ); - remainingBytes -= contentBytes; - rows++; - } - await database.execute( - `INSERT INTO chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - await database.execute("COMMIT"); - } catch (err) { - await database.execute("ROLLBACK"); - throw err; - } - return { threadId, rows, totalBytes: targetBytes }; -} -var testSqliteBench = actor49({ - options: { - actionTimeout: 3e5 - }, - db: db6({ - onMigrate: async (database) => { - await database.execute(`CREATE TABLE IF NOT EXISTS bench ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key TEXT NOT NULL, - value TEXT NOT NULL, - num INTEGER NOT NULL DEFAULT 0, - payload BLOB, - created_at INTEGER NOT NULL DEFAULT 0 - )`); - await database.execute("CREATE INDEX IF NOT EXISTS idx_bench_key ON bench(key)"); - await database.execute("CREATE INDEX IF NOT EXISTS idx_bench_num ON bench(num)"); - await database.execute(`CREATE TABLE IF NOT EXISTS bench_json ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - data TEXT NOT NULL DEFAULT '{}' - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS bench_secondary ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - bench_id INTEGER NOT NULL, - label TEXT NOT NULL, - score REAL NOT NULL DEFAULT 0, - FOREIGN KEY (bench_id) REFERENCES bench(id) - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS chat_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - thread_id TEXT NOT NULL, - seq INTEGER NOT NULL, - role TEXT NOT NULL, - content TEXT NOT NULL, - content_bytes INTEGER NOT NULL, - token_estimate INTEGER NOT NULL, - created_at INTEGER NOT NULL DEFAULT 0 - )`); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_chat_log_thread_seq ON chat_log(thread_id, seq DESC)" - ); - } - }), - actions: { - noop: (_c) => ({ ok: true }), - goToSleep: (c) => { - c.sleep(); - return { ok: true }; - }, - insertSingle: async (c, n) => { - const t0 = performance.now(); - for (let i = 0; i < n; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `k-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - return { ms: performance.now() - t0, ops: n }; - }, - insertTx: async (c, n) => { - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < n; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `k-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: n }; - }, - insertBatch: async (c, n) => { - const t0 = performance.now(); - const placeholders = Array.from({ length: n }, () => "(?, ?, ?, ?)").join(", "); - const args = []; - for (let i = 0; i < n; i++) { - args.push(`k-${i}`, `v-${i}`, i, Date.now()); - } - await c.db.execute(`INSERT INTO bench (key, value, num, created_at) VALUES ${placeholders}`, ...args); - return { ms: performance.now() - t0, ops: n }; - }, - pointRead: async (c, n) => { - await c.db.execute("INSERT INTO bench (key, value, num, created_at) VALUES ('pr', 'pr', 0, 0)"); - const rows = await c.db.execute("SELECT id FROM bench WHERE key = 'pr' LIMIT 1"); - const id = rows[0].id; - const t0 = performance.now(); - for (let i = 0; i < n; i++) { - await c.db.execute("SELECT * FROM bench WHERE id = ?", id); - } - return { ms: performance.now() - t0, ops: n }; - }, - fullScan: async (c, seedRows) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < seedRows; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `scan-${i}`, - `val-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute("SELECT * FROM bench"); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - rangeScanIndexed: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `rs-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute("SELECT * FROM bench WHERE num BETWEEN 100 AND 300"); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - rangeScanUnindexed: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `ru-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute("SELECT * FROM bench WHERE value BETWEEN 'v-100' AND 'v-300'"); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - bulkUpdate: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `bu-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - await c.db.execute("UPDATE bench SET value = 'updated', num = num + 1000 WHERE key LIKE 'bu-%'"); - return { ms: performance.now() - t0, seedMs, ops: 200 }; - }, - bulkDelete: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `bd-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - await c.db.execute("DELETE FROM bench WHERE key LIKE 'bd-%'"); - return { ms: performance.now() - t0, seedMs, ops: 200 }; - }, - hotRowUpdates: async (c, n) => { - await c.db.execute("INSERT INTO bench (key, value, num, created_at) VALUES ('hot', 'v', 0, 0)"); - const rows = await c.db.execute("SELECT id FROM bench WHERE key = 'hot' LIMIT 1"); - const id = rows[0].id; - const t0 = performance.now(); - for (let i = 0; i < n; i++) { - await c.db.execute("UPDATE bench SET num = ? WHERE id = ?", i, id); - } - return { ms: performance.now() - t0, ops: n }; - }, - vacuumAfterDelete: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `vac-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - await c.db.execute("DELETE FROM bench WHERE key LIKE 'vac-%'"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - await c.db.execute("VACUUM"); - return { ms: performance.now() - t0, seedMs }; - }, - largePayloadInsert: async (c, n) => { - const blob = "x".repeat(32 * 1024); - const t0 = performance.now(); - for (let i = 0; i < n; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, payload, created_at) VALUES (?, ?, ?, ?, ?)", - `lp-${i}`, - `v-${i}`, - i, - blob, - Date.now() - ); - } - return { ms: performance.now() - t0, ops: n }; - }, - mixedOltp: async (c) => { - const t0 = performance.now(); - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - "oltp", - "initial", - 0, - Date.now() - ); - const rows = await c.db.execute("SELECT * FROM bench WHERE key = 'oltp' LIMIT 1"); - const id = rows[0].id; - await c.db.execute("UPDATE bench SET value = 'updated', num = 1 WHERE id = ?", id); - await c.db.execute("SELECT * FROM bench WHERE id = ?", id); - return { ms: performance.now() - t0, ops: 4 }; - }, - jsonInsertAndQuery: async (c) => { - const t0 = performance.now(); - for (let i = 0; i < 50; i++) { - await c.db.execute( - "INSERT INTO bench_json (data) VALUES (?)", - JSON.stringify({ name: `item-${i}`, tags: ["a", "b"], score: Math.random() * 100 }) - ); - } - const rows = await c.db.execute( - "SELECT id, json_extract(data, '$.name') as name, json_extract(data, '$.score') as score FROM bench_json ORDER BY json_extract(data, '$.score') DESC LIMIT 10" - ); - return { ms: performance.now() - t0, ops: 51, rows: rows.length }; - }, - jsonEachAgg: async (c) => { - await c.db.execute( - "INSERT INTO bench_json (data) VALUES (?)", - JSON.stringify({ items: Array.from({ length: 100 }, (_, i) => ({ id: i, val: i * 10 })) }) - ); - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT SUM(json_extract(value, '$.val')) as total FROM bench_json, json_each(json_extract(data, '$.items')) LIMIT 1" - ); - return { ms: performance.now() - t0, total: rows[0].total }; - }, - complexAggregation: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `grp-${i % 10}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT key, COUNT(*) as cnt, AVG(num) as avg_num, MIN(num) as min_num, MAX(num) as max_num FROM bench WHERE key LIKE 'grp-%' GROUP BY key ORDER BY cnt DESC" - ); - return { ms: performance.now() - t0, seedMs, groups: rows.length }; - }, - complexSubquery: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `sq-${i}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT * FROM bench WHERE num > (SELECT AVG(num) FROM bench) ORDER BY num DESC LIMIT 50" - ); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - complexJoin: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `j-${i}`, - `v-${i}`, - i, - Date.now() - ); - await c.db.execute( - "INSERT INTO bench_secondary (bench_id, label, score) VALUES (?, ?, ?)", - i + 1, - `label-${i}`, - Math.random() * 100 - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT b.key, b.num, s.label, s.score FROM bench b INNER JOIN bench_secondary s ON s.bench_id = b.id WHERE b.key LIKE 'j-%' ORDER BY s.score DESC LIMIT 200" - ); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - complexCteWindow: async (c) => { - const t0Seed = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 200; i++) { - await c.db.execute( - "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", - `cte-${i % 10}`, - `v-${i}`, - i, - Date.now() - ); - } - await c.db.execute("COMMIT"); - const seedMs = performance.now() - t0Seed; - const t0 = performance.now(); - const rows = await c.db.execute(` - WITH ranked AS ( - SELECT key, num, ROW_NUMBER() OVER (PARTITION BY key ORDER BY num DESC) as rn, - AVG(num) OVER (PARTITION BY key) as avg_num - FROM bench - WHERE key LIKE 'cte-%' - ) - SELECT * FROM ranked WHERE rn <= 3 ORDER BY key, rn - `); - return { ms: performance.now() - t0, seedMs, rows: rows.length }; - }, - migrationTables: async (c, n) => { - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < n; i++) { - await c.db.execute(`CREATE TABLE IF NOT EXISTS mig_${i} ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - data TEXT NOT NULL DEFAULT '' - )`); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: n }; - }, - chatLogInsert: async (c, totalBytes) => { - const t0 = performance.now(); - const seeded = await seedChatLog(c.db, totalBytes); - return { ms: performance.now() - t0, ops: seeded.rows, bytes: seeded.totalBytes }; - }, - chatLogSelectLimit: async (c, totalBytes) => { - const seeded = await seedChatLog(c.db, totalBytes); - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT seq, role, substr(content, 1, 128) AS preview FROM chat_log ORDER BY created_at DESC LIMIT 100" - ); - return { - ms: performance.now() - t0, - ops: rows.length, - rows: rows.length, - bytes: seeded.totalBytes - }; - }, - chatLogSelectIndexed: async (c, totalBytes) => { - const seeded = await seedChatLog(c.db, totalBytes); - const lowerBound = Math.max(0, seeded.rows - 100); - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT seq, role, content_bytes FROM chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", - seeded.threadId, - lowerBound - ); - return { - ms: performance.now() - t0, - ops: rows.length, - rows: rows.length, - bytes: seeded.totalBytes - }; - }, - chatLogCount: async (c, totalBytes) => { - const seeded = await seedChatLog(c.db, totalBytes); - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT COUNT(*) AS count FROM chat_log WHERE thread_id = ?", - seeded.threadId - ); - return { - ms: performance.now() - t0, - ops: 1, - count: rows[0].count, - bytes: seeded.totalBytes - }; - }, - chatLogSum: async (c, totalBytes) => { - const seeded = await seedChatLog(c.db, totalBytes); - const t0 = performance.now(); - const rows = await c.db.execute( - "SELECT SUM(content_bytes) AS total_bytes FROM chat_log WHERE thread_id = ?", - seeded.threadId - ); - return { - ms: performance.now() - t0, - ops: 1, - totalBytes: rows[0].total_bytes ?? 0, - bytes: seeded.totalBytes - }; - }, - largeTxInsert500KB: async (c) => { - const targetBytes = 500 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - largeTxInsert1MB: async (c) => { - const targetBytes = 1024 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - // 1 MiB total, 4096 × 256 B rows. Max NAPI crossings. - largeTxInsert1MBTinyRows: async (c) => { - const targetBytes = 1024 * 1024; - const rowSize = 256; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - // 1 MiB total, 256 × 4 KiB rows. Same shape as largeTxInsert1MB; kept as a sanity duplicate. - largeTxInsert1MBMediumRows: async (c) => { - const targetBytes = 1024 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - // 1 MiB total, 1 × 1 MiB row. One NAPI crossing, exercises SQLite overflow-page chain. - largeTxInsert1MBOneRow: async (c) => { - const rowSize = 1024 * 1024; - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: 1, bytes: rowSize }; - }, - largeTxInsert5MB: async (c) => { - const targetBytes = 5 * 1024 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - largeTxInsert10MB: async (c) => { - const targetBytes = 10 * 1024 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - largeTxInsert50MB: async (c) => { - const targetBytes = 50 * 1024 * 1024; - const rowSize = 4 * 1024; - const rowCount = Math.ceil(targetBytes / rowSize); - await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < rowCount; i++) { - await c.db.execute( - "INSERT INTO large_tx (payload) VALUES (randomblob(?))", - rowSize - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize }; - }, - // Stress test: insert 1000 rows, delete them all, repeat 10 times. - // Tests freelist reuse and space reclamation patterns. - churnInsertDelete: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS churn ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - const t0 = performance.now(); - const cycles = 10; - const perCycle = 1e3; - for (let cycle = 0; cycle < cycles; cycle++) { - await c.db.execute("BEGIN"); - for (let i = 0; i < perCycle; i++) { - await c.db.execute( - "INSERT INTO churn (payload) VALUES (randomblob(1024))" - ); - } - await c.db.execute("DELETE FROM churn"); - await c.db.execute("COMMIT"); - } - return { - ms: performance.now() - t0, - ops: cycles * perCycle, - cycles - }; - }, - // Interleave inserts, updates, deletes in same transaction. Tests how - // the VFS handles mixed page dirtying patterns. - mixedOltpLarge: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS mixed_oltp ( - id INTEGER PRIMARY KEY, - value INTEGER NOT NULL, - data BLOB NOT NULL - )`); - await c.db.execute("DELETE FROM mixed_oltp"); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))", - i, - i * 2 - ); - } - await c.db.execute("COMMIT"); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))", - 500 + i, - i * 3 - ); - await c.db.execute( - "UPDATE mixed_oltp SET value = value + 1 WHERE id = ?", - i - ); - if (i % 5 === 0) { - await c.db.execute( - "DELETE FROM mixed_oltp WHERE id = ?", - i - 50 >= 0 ? i - 50 : i - ); - } - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: 500 * 2 + 100 }; - }, - // Growing aggregation: insert then SELECT SUM after each batch. - // Tests cache invalidation and read-after-write patterns. - growingAggregation: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS agg_test ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - value INTEGER NOT NULL - )`); - await c.db.execute("DELETE FROM agg_test"); - const t0 = performance.now(); - const batches = 20; - const perBatch = 100; - let lastSum = 0; - for (let batch2 = 0; batch2 < batches; batch2++) { - await c.db.execute("BEGIN"); - for (let i = 0; i < perBatch; i++) { - await c.db.execute( - "INSERT INTO agg_test (value) VALUES (?)", - batch2 * perBatch + i - ); - } - await c.db.execute("COMMIT"); - const rows = await c.db.execute( - "SELECT SUM(value) AS s FROM agg_test" - ); - lastSum = rows[0]?.s ?? 0; - } - return { - ms: performance.now() - t0, - ops: batches * perBatch, - batches, - lastSum - }; - }, - // Create index on already-populated table. Tests large rewrite patterns. - indexCreationOnLargeTable: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS idx_test ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key TEXT NOT NULL, - value INTEGER NOT NULL - )`); - await c.db.execute("DROP INDEX IF EXISTS idx_test_key"); - await c.db.execute("DELETE FROM idx_test"); - await c.db.execute("BEGIN"); - for (let i = 0; i < 1e4; i++) { - await c.db.execute( - "INSERT INTO idx_test (key, value) VALUES (?, ?)", - `key-${i % 1e3}-${i}`, - i - ); - } - await c.db.execute("COMMIT"); - const t0 = performance.now(); - await c.db.execute("CREATE INDEX idx_test_key ON idx_test(key)"); - return { ms: performance.now() - t0, ops: 1e4 }; - }, - // Update 1000 different rows in separate UPDATEs in one transaction. - // Stresses B-tree navigation and page dirtying. - bulkUpdate1000Rows: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS bulk_update ( - id INTEGER PRIMARY KEY, - value INTEGER NOT NULL - )`); - await c.db.execute("DELETE FROM bulk_update"); - await c.db.execute("BEGIN"); - for (let i = 0; i < 1e3; i++) { - await c.db.execute( - "INSERT INTO bulk_update (id, value) VALUES (?, ?)", - i, - i - ); - } - await c.db.execute("COMMIT"); - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 1e3; i++) { - await c.db.execute( - "UPDATE bulk_update SET value = value + 1 WHERE id = ?", - i - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: 1e3 }; - }, - // Delete everything then re-insert. Tests truncate+regrow cycle. - truncateAndRegrow: async (c) => { - await c.db.execute(`CREATE TABLE IF NOT EXISTS regrow ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload BLOB NOT NULL - )`); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO regrow (payload) VALUES (randomblob(1024))" - ); - } - await c.db.execute("COMMIT"); - const t0 = performance.now(); - await c.db.execute("DELETE FROM regrow"); - await c.db.execute("BEGIN"); - for (let i = 0; i < 500; i++) { - await c.db.execute( - "INSERT INTO regrow (payload) VALUES (randomblob(1024))" - ); - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: 500 }; - }, - // Many small tables vs one large. Tests schema page growth. - manySmallTables: async (c) => { - const t0 = performance.now(); - await c.db.execute("BEGIN"); - for (let i = 0; i < 50; i++) { - await c.db.execute( - `CREATE TABLE IF NOT EXISTS small_t_${i} (id INTEGER PRIMARY KEY, value INTEGER)` - ); - for (let j = 0; j < 10; j++) { - await c.db.execute( - `INSERT INTO small_t_${i} (id, value) VALUES (?, ?)`, - j, - i * j - ); - } - } - await c.db.execute("COMMIT"); - return { ms: performance.now() - t0, ops: 50 * 10, tables: 50 }; - } - } -}); - -// src/actors/testing/sqlite-cold-start-bench.ts -import { randomBytes } from "crypto"; -import { actor as actor50 } from "rivetkit"; -import { db as db7 } from "rivetkit/db"; -var DEFAULT_TARGET_BYTES = 50 * 1024 * 1024; -var DEFAULT_ROW_BYTES = 16 * 1024; -var DEFAULT_BATCH_ROWS = 8; -var DEFAULT_TRANSACTION_BYTES = 64 * 1024; -var READ_BATCH_ROWS = 64; -var REVERSE_PROBE_ROWS = 32 * 1024; -var PAYLOAD_TABLE = "cold_start_payload"; -var REVERSE_PROBE_TABLE = "cold_start_reverse_probe"; -function positiveInteger(value, fallback, name) { - const resolved = value ?? fallback; - if (!Number.isInteger(resolved) || resolved < 1) { - throw new Error(`${name} must be a positive integer`); - } - return resolved; -} -function randomAsciiString(bytes) { - return randomBytes(Math.ceil(bytes / 2)).toString("hex").slice(0, bytes); -} -async function readPayloads(database, direction = "forward") { - const t0 = performance.now(); - const [bounds] = await database.execute( - ` - SELECT - MIN(id) AS min_id, - MAX(id) AS max_id, - COUNT(*) AS rows, - 0 AS bytes, - 0 AS expected_bytes - FROM ${PAYLOAD_TABLE} - ` - ); - if (!bounds) throw new Error("read query returned no rows"); - let rows = 0; - let bytes = 0; - let expectedBytes = 0; - let chunks = 0; - const minId = bounds.min_id ?? 0; - const maxId = bounds.max_id ?? 0; - if (direction === "backward") { - const [probeBounds] = await database.execute( - ` - SELECT - MIN(id) AS min_id, - MAX(id) AS max_id, - COUNT(*) AS rows, - 0 AS bytes, - 0 AS expected_bytes - FROM ${REVERSE_PROBE_TABLE} - ` - ); - if (!probeBounds) throw new Error("reverse probe query returned no rows"); - const probeMinId = probeBounds.min_id ?? 0; - const probeMaxId = probeBounds.max_id ?? 0; - for (let upperId = probeMaxId; upperId >= probeMinId && upperId > 0; upperId -= READ_BATCH_ROWS) { - const lowerId = Math.max(probeMinId, upperId - READ_BATCH_ROWS + 1); - const chunkRows = await database.execute( - ` - SELECT - marker AS bytes, - marker AS expected_bytes - FROM ${REVERSE_PROBE_TABLE} - WHERE id BETWEEN ? AND ? - ORDER BY id DESC - `, - lowerId, - upperId - ); - for (const row of chunkRows) { - rows += 1; - bytes += row.bytes; - expectedBytes += row.expected_bytes; - } - chunks += 1; - } - return { - ms: performance.now() - t0, - ops: rows, - rows, - bytes, - expectedBytes, - chunks, - readBatchRows: READ_BATCH_ROWS, - direction - }; - } - for (let lowerId = minId; lowerId <= maxId; lowerId += READ_BATCH_ROWS) { - const upperId = lowerId + READ_BATCH_ROWS - 1; - const [chunk] = await database.execute( - ` - SELECT - COUNT(*) AS rows, - COALESCE(SUM(length(payload)), 0) AS bytes, - COALESCE(SUM(payload_bytes), 0) AS expected_bytes - FROM ${PAYLOAD_TABLE} - WHERE id BETWEEN ? AND ? - `, - lowerId, - upperId - ); - if (!chunk) throw new Error("chunked read query returned no rows"); - rows += chunk.rows; - bytes += chunk.bytes; - expectedBytes += chunk.expected_bytes; - chunks += 1; - } - return { - ms: performance.now() - t0, - ops: rows, - rows, - bytes, - expectedBytes, - chunks, - readBatchRows: READ_BATCH_ROWS - }; -} -var sqliteColdStartBench = actor50({ - options: { - actionTimeout: 6e5 - }, - db: db7({ - onMigrate: async (database) => { - await database.execute(` - CREATE TABLE IF NOT EXISTS cold_start_payload ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload TEXT NOT NULL, - payload_bytes INTEGER NOT NULL, - created_at INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS cold_start_reverse_probe ( - id INTEGER PRIMARY KEY, - marker INTEGER NOT NULL - ) - `); - } - }), - actions: { - reset: async (c) => { - await c.db.execute(`DELETE FROM ${PAYLOAD_TABLE}`); - await c.db.execute(`DELETE FROM ${REVERSE_PROBE_TABLE}`); - return { ok: true }; - }, - writeRandomStrings: async (c, input = {}) => { - const targetBytes = positiveInteger( - input.targetBytes, - DEFAULT_TARGET_BYTES, - "targetBytes" - ); - const rowBytes = positiveInteger(input.rowBytes, DEFAULT_ROW_BYTES, "rowBytes"); - const batchRows = positiveInteger( - input.batchRows, - DEFAULT_BATCH_ROWS, - "batchRows" - ); - const transactionBytes = positiveInteger( - input.transactionBytes, - DEFAULT_TRANSACTION_BYTES, - "transactionBytes" - ); - const createdAt = Date.now(); - let remainingBytes = targetBytes; - let rows = 0; - let transactions = 0; - let randomStringMs = 0; - let sqliteInsertMs = 0; - let commitMs = 0; - let inTransaction = false; - const wallT0 = performance.now(); - try { - while (remainingBytes > 0) { - let transactionRemainingBytes = Math.min( - transactionBytes, - remainingBytes - ); - await c.db.execute("BEGIN"); - inTransaction = true; - transactions += 1; - while (transactionRemainingBytes > 0) { - const placeholders = []; - const args = []; - const generateT0 = performance.now(); - for (let batchIndex = 0; batchIndex < batchRows && transactionRemainingBytes > 0 && remainingBytes > 0; batchIndex += 1) { - const payloadBytes = Math.min( - rowBytes, - transactionRemainingBytes, - remainingBytes - ); - placeholders.push("(?, ?, ?)"); - args.push( - randomAsciiString(payloadBytes), - payloadBytes, - createdAt + rows - ); - transactionRemainingBytes -= payloadBytes; - remainingBytes -= payloadBytes; - rows += 1; - } - randomStringMs += performance.now() - generateT0; - const insertT0 = performance.now(); - await c.db.execute( - `INSERT INTO ${PAYLOAD_TABLE} (payload, payload_bytes, created_at) VALUES ${placeholders.join(", ")}`, - ...args - ); - sqliteInsertMs += performance.now() - insertT0; - } - const commitT0 = performance.now(); - await c.db.execute("COMMIT"); - commitMs += performance.now() - commitT0; - inTransaction = false; - } - await c.db.execute("BEGIN"); - inTransaction = true; - for (let lowerId = 1; lowerId <= REVERSE_PROBE_ROWS; lowerId += 256) { - const upperId = Math.min(REVERSE_PROBE_ROWS, lowerId + 255); - const placeholders = []; - const args = []; - for (let id = lowerId; id <= upperId; id += 1) { - placeholders.push("(?, ?)"); - args.push(id, 1); - } - const insertT0 = performance.now(); - await c.db.execute( - `INSERT INTO ${REVERSE_PROBE_TABLE} (id, marker) VALUES ${placeholders.join(", ")}`, - ...args - ); - sqliteInsertMs += performance.now() - insertT0; - } - const reverseCommitT0 = performance.now(); - await c.db.execute("COMMIT"); - commitMs += performance.now() - reverseCommitT0; - inTransaction = false; - return { - ms: sqliteInsertMs + commitMs, - writeWallMs: performance.now() - wallT0, - randomStringMs, - sqliteInsertMs, - commitMs, - ops: rows, - rows, - transactions, - bytes: targetBytes, - rowBytes, - batchRows, - transactionBytes, - reverseProbeRows: REVERSE_PROBE_ROWS - }; - } catch (err) { - if (inTransaction) { - await c.db.execute("ROLLBACK"); - } - throw err; - } - }, - readAll: async (c) => { - return readPayloads(c.db); - }, - readAllReverse: async (c) => { - return readPayloads(c.db, "backward"); - }, - wakeSqlite: async (c) => { - const t0 = performance.now(); - const [row] = await c.db.execute( - `SELECT COUNT(*) AS rows FROM ${PAYLOAD_TABLE} WHERE id = -1` - ); - return { - ms: performance.now() - t0, - rows: row?.rows ?? 0 - }; - }, - goToSleep: (c) => { - c.sleep(); - return { ok: true }; - } - } -}); - -// src/actors/testing/sqlite-realworld-bench.ts -import { actor as actor51 } from "rivetkit"; -import { db as db8 } from "rivetkit/db"; -var DEFAULT_ROW_BYTES2 = 2 * 1024; -var ORDER_BATCH_ROWS = 50; -var DOC_BATCH_ROWS = 75; -var LEDGER_BATCH_ROWS = 100; -var POINT_LOOKUP_OPS = 1e3; -var RANGE_CHUNK_ROWS = 512; -var SETUP_TRANSACTION_ROWS = 128; -var FEED_PAGE_ROWS = 100; -var CHAT_LOG_CHUNK_BYTES2 = 4 * 1024; -var CHAT_LOG_INSERT_BATCH_SIZE2 = 50; -var CHAT_THREAD_ID = "rw-chat-main"; -var SQL_RUSH_MSGS_COUNT = 2500; -var SQL_RUSH_TOOL_REFS_COUNT = 240; -var SQL_RUSH_EVENTS_COUNT = 700; -var SQL_RUSH_KV_COUNT = 40; -var SQL_RUSH_TOOLS_COUNT = 41; -var SQL_RUSH_META_COUNT = 12; -var WORKLOADS = [ - "small-rowid-point", - "small-schema-read", - "small-range-scan", - "rowid-range-forward", - "rowid-range-backward", - "secondary-index-covering-range", - "secondary-index-scattered-table", - "aggregate-status", - "aggregate-time-bucket", - "aggregate-tenant-time-range", - "parallel-read-aggregates", - "parallel-read-write-transition", - "feed-order-by-limit", - "feed-pagination-adjacent", - "join-order-items", - "random-point-lookups", - "hot-index-cold-table", - "ledger-without-rowid-range", - "chat-log-select-limit", - "chat-log-select-indexed", - "chat-log-count", - "chat-log-sum", - "chat-tool-read-fanout", - "chat-tool-script", - "write-batch-after-wake", - "update-hot-partition", - "delete-churn-range-read", - "migration-create-indexes-large", - "migration-create-indexes-skewed-large", - "migration-table-rebuild-large", - "migration-add-column-large", - "migration-ddl-small" -]; -function positiveInteger2(value, fallback, name) { - const resolved = value ?? fallback; - if (!Number.isInteger(resolved) || resolved < 1) { - throw new Error(`${name} must be a positive integer`); - } - return resolved; -} -function assertWorkload(workload) { - if (!WORKLOADS.includes(workload)) { - throw new Error(`unknown SQLite benchmark workload: ${workload}`); - } -} -function pseudoRandom(value) { - return Math.imul(value ^ 2654435769, 2246822507) >>> 0; -} -function paddedHex(value) { - return pseudoRandom(value).toString(16).padStart(8, "0"); -} -function payload(prefix, bytes) { - return prefix + "x".repeat(Math.max(0, bytes - prefix.length)); -} -function typedRows(rows) { - return rows; -} -async function queryPageCount(database) { - const [row] = typedRows(await database.execute("PRAGMA page_count")); - return row?.page_count ?? 0; -} -async function resetCommerce(database) { - await database.execute("DELETE FROM rw_order_items"); - await database.execute("DELETE FROM rw_orders"); - await database.execute("DELETE FROM rw_customers"); - await database.execute("DELETE FROM rw_events"); -} -async function resetDocs(database) { - await database.execute("DELETE FROM rw_docs"); -} -async function resetLedger(database) { - await database.execute("DELETE FROM rw_ledger"); -} -async function resetChatLog(database) { - await database.execute("DELETE FROM rw_chat_log"); -} -async function resetSqlRush(database) { - await database.execute("DELETE FROM tool_refs"); - await database.execute("DELETE FROM msgs"); - await database.execute("DELETE FROM events"); - await database.execute("DELETE FROM kv"); - await database.execute("DELETE FROM tools"); - await database.execute("DELETE FROM meta"); -} -async function resetMigration(database) { - await database.execute("DROP INDEX IF EXISTS idx_rw_migration_source_account"); - await database.execute("DROP INDEX IF EXISTS idx_rw_migration_source_created"); - await database.execute("DROP INDEX IF EXISTS idx_rw_migration_source_status_total"); - await database.execute("DROP INDEX IF EXISTS idx_rw_migration_source_skew_account"); - await database.execute("DROP INDEX IF EXISTS idx_rw_migration_source_skew_status"); - await database.execute("DROP TABLE IF EXISTS rw_migration_source_rebuilt"); - await database.execute("DROP TABLE IF EXISTS rw_migration_source"); - await database.execute("DROP TABLE IF EXISTS rw_migration_audit"); - await database.execute("DROP TABLE IF EXISTS rw_migration_empty"); -} -async function withTransaction(database, fn) { - let inTransaction = false; - await database.execute("BEGIN"); - inTransaction = true; - try { - await fn(); - await database.execute("COMMIT"); - inTransaction = false; - } catch (err) { - if (inTransaction) { - await database.execute("ROLLBACK").catch(() => void 0); - } - throw err; - } -} -async function seedCommerce(database, targetBytes, rowBytes) { - await resetCommerce(database); - const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); - const customerCount = Math.max(32, Math.ceil(rows / 16)); - const startedAt = performance.now(); - await withTransaction(database, async () => { - for (let offset = 0; offset < customerCount; offset += ORDER_BATCH_ROWS) { - const placeholders = []; - const args = []; - const batchEnd = Math.min(customerCount, offset + ORDER_BATCH_ROWS); - for (let i = offset; i < batchEnd; i += 1) { - placeholders.push("(?, ?, ?, ?, ?)"); - args.push( - i + 1, - `acct-${i % 64}`, - `user-${paddedHex(i)}@example.test`, - ["free", "pro", "team", "enterprise"][i % 4], - ["iad", "sfo", "fra", "sin"][i % 4] - ); - } - await database.execute( - `INSERT INTO rw_customers (id, account_id, email, plan, region) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - }); - for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { - const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); - await withTransaction(database, async () => { - for (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) { - const orderPlaceholders = []; - const orderArgs = []; - const itemPlaceholders = []; - const itemArgs = []; - const eventPlaceholders = []; - const eventArgs = []; - const batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS); - for (let i = offset; i < batchEnd; i += 1) { - const id = i + 1; - const customerId = pseudoRandom(i) % customerCount + 1; - const createdAt = 17e11 + i * 1e3; - const status = ["pending", "paid", "shipped", "refunded"][i % 4]; - const totalCents = 500 + pseudoRandom(i + 17) % 25e3; - const note = payload(`order-${id}-${status}:`, rowBytes); - orderPlaceholders.push("(?, ?, ?, ?, ?, ?, ?)"); - orderArgs.push( - id, - customerId, - createdAt, - status, - totalCents, - i % 128, - note - ); - for (let item = 0; item < 2; item += 1) { - itemPlaceholders.push("(?, ?, ?, ?, ?)"); - itemArgs.push( - id, - `sku-${paddedHex(i + item).slice(0, 6)}`, - 1 + (i + item) % 5, - 100 + pseudoRandom(i + item + 31) % 5e3, - item - ); - } - eventPlaceholders.push("(?, ?, ?, ?, ?)"); - eventArgs.push( - `acct-${customerId % 64}`, - ["click", "purchase", "refund", "shipment"][i % 4], - createdAt, - `order:${id}`, - payload(`event-${id}:`, Math.min(rowBytes, 512)) - ); - } - await database.execute( - `INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${orderPlaceholders.join(", ")}`, - ...orderArgs - ); - await database.execute( - `INSERT INTO rw_order_items (order_id, sku, quantity, price_cents, line_no) VALUES ${itemPlaceholders.join(", ")}`, - ...itemArgs - ); - await database.execute( - `INSERT INTO rw_events (account_id, event_type, created_at, entity_key, properties) VALUES ${eventPlaceholders.join(", ")}`, - ...eventArgs - ); - } - }); - } - return { - rows, - targetBytes, - rowBytes, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -async function seedDocs(database, targetBytes, rowBytes) { - await resetDocs(database); - const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); - const startedAt = performance.now(); - for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { - const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); - await withTransaction(database, async () => { - for (let offset = txStart; offset < txEnd; offset += DOC_BATCH_ROWS) { - const placeholders = []; - const args = []; - const batchEnd = Math.min(txEnd, offset + DOC_BATCH_ROWS); - for (let i = offset; i < batchEnd; i += 1) { - const rank = pseudoRandom(i); - const body = payload(`doc-${i}-${rank}:`, rowBytes); - placeholders.push("(?, ?, ?, ?, ?)"); - args.push( - `doc-${paddedHex(i)}`, - rank, - `tenant-${rank % 128}`, - body, - rowBytes - ); - } - await database.execute( - `INSERT INTO rw_docs (external_key, row_rank, tenant_id, body, body_bytes) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - }); - } - return { - rows, - targetBytes, - rowBytes, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -async function seedLedger(database, targetBytes, rowBytes) { - await resetLedger(database); - const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); - const startedAt = performance.now(); - for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { - const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); - await withTransaction(database, async () => { - for (let offset = txStart; offset < txEnd; offset += LEDGER_BATCH_ROWS) { - const placeholders = []; - const args = []; - const batchEnd = Math.min(txEnd, offset + LEDGER_BATCH_ROWS); - for (let i = offset; i < batchEnd; i += 1) { - const accountId = `acct-${String(i % 256).padStart(4, "0")}`; - const entryId = Math.floor(i / 256) + 1; - placeholders.push("(?, ?, ?, ?, ?)"); - args.push( - accountId, - entryId, - (i % 2 === 0 ? 1 : -1) * (100 + i % 1e4), - 17e11 + i * 1e3, - payload(`ledger-${accountId}-${entryId}:`, Math.min(rowBytes, 512)) - ); - } - await database.execute( - `INSERT INTO rw_ledger (account_id, entry_id, amount_cents, created_at, memo) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - }); - } - return { - rows, - targetBytes, - rowBytes, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -function buildChatLogMessage2(seq, targetBytes) { - const prefix = `message-${seq}: `; - return prefix + "x".repeat(Math.max(0, targetBytes - prefix.length)); -} -async function seedChatLog2(database, targetBytes) { - await resetChatLog(database); - const createdAtBase = 17e11; - let remainingBytes = targetBytes; - let rows = 0; - const startedAt = performance.now(); - await withTransaction(database, async () => { - while (remainingBytes > 0) { - const placeholders = []; - const args = []; - for (let batchIndex = 0; batchIndex < CHAT_LOG_INSERT_BATCH_SIZE2 && remainingBytes > 0; batchIndex += 1) { - const contentBytes = Math.min(CHAT_LOG_CHUNK_BYTES2, remainingBytes); - const seq = rows; - const role = seq % 2 === 0 ? "user" : "assistant"; - placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); - args.push( - CHAT_THREAD_ID, - seq, - role, - buildChatLogMessage2(seq, contentBytes), - contentBytes, - Math.ceil(contentBytes / 4), - createdAtBase + seq - ); - remainingBytes -= contentBytes; - rows += 1; - } - await database.execute( - `INSERT INTO rw_chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - }); - return { - rows, - targetBytes, - rowBytes: CHAT_LOG_CHUNK_BYTES2, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -async function batchInsert(database, sql, rows, batchSize) { - if (rows.length === 0) return; - const colsPerRow = rows[0]?.length ?? 0; - if (colsPerRow === 0) return; - const placeholder = `(${"?,".repeat(colsPerRow).slice(0, -1)})`; - for (let i = 0; i < rows.length; i += batchSize) { - const chunk = rows.slice(i, i + batchSize); - const values = new Array(chunk.length).fill(placeholder).join(","); - const args = []; - for (const row of chunk) args.push(...row); - await database.execute(`${sql} VALUES ${values}`, ...args); - } -} -async function seedSqlRush(database, targetBytes) { - await resetSqlRush(database); - const now = 17e11; - const startedAt = performance.now(); - await withTransaction(database, async () => { - const msgsRows = []; - for (let i = 0; i < SQL_RUSH_MSGS_COUNT; i += 1) { - msgsRows.push([ - i === 0 ? null : i, - i % 3 === 0 ? "user" : "assistant", - payload("msg:", 512), - 0, - now - (SQL_RUSH_MSGS_COUNT - i) * 1e3 - ]); - } - await batchInsert( - database, - "INSERT INTO msgs (parent, role, content, cancelled, created_at)", - msgsRows, - 50 - ); - const toolRefsRows = []; - for (let i = 0; i < SQL_RUSH_TOOL_REFS_COUNT; i += 1) { - toolRefsRows.push([ - i + 1, - `tool_${i % 20}`, - `call_${i}`, - i % 5 === 0 ? "pending" : "done" - ]); - } - await batchInsert( - database, - "INSERT INTO tool_refs (msg_id, tool_name, tool_call_id, status)", - toolRefsRows, - 100 - ); - const eventsRows = []; - for (let i = 0; i < SQL_RUSH_EVENTS_COUNT; i += 1) { - eventsRows.push([ - i + 1, - `event_${i % 8}`, - payload("event:", 256), - now - (SQL_RUSH_EVENTS_COUNT - i) * 100 - ]); - } - await batchInsert( - database, - "INSERT INTO events (seq, event_type, payload, created_at)", - eventsRows, - 100 - ); - const kvRows = []; - for (let i = 0; i < SQL_RUSH_KV_COUNT; i += 1) { - kvRows.push([`kv_${i}`, payload("kv:", 128), now]); - } - await batchInsert(database, "INSERT INTO kv (key, value, updated_at)", kvRows, 40); - const toolsRows = []; - for (let i = 0; i < SQL_RUSH_TOOLS_COUNT; i += 1) { - toolsRows.push(["exec-1", `tool_${i}`, payload("tool:", 1024), now]); - } - await batchInsert( - database, - "INSERT INTO tools (executor_id, name, spec, updated_at)", - toolsRows, - 41 - ); - const metaRows = []; - for (let i = 0; i < SQL_RUSH_META_COUNT; i += 1) { - metaRows.push([`key_${i}`, payload("meta:", 64)]); - } - await batchInsert(database, "INSERT INTO meta (key, value)", metaRows, 12); - }); - return { - rows: SQL_RUSH_MSGS_COUNT + SQL_RUSH_TOOL_REFS_COUNT + SQL_RUSH_EVENTS_COUNT + SQL_RUSH_KV_COUNT + SQL_RUSH_TOOLS_COUNT + SQL_RUSH_META_COUNT, - targetBytes, - rowBytes: 0, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -async function seedMigrationSource(database, targetBytes, rowBytes, skewed = false) { - await resetMigration(database); - await database.execute(`CREATE TABLE rw_migration_source ( - id INTEGER PRIMARY KEY, - account_id TEXT NOT NULL, - status TEXT NOT NULL, - created_at INTEGER NOT NULL, - total_cents INTEGER NOT NULL, - body TEXT NOT NULL - )`); - const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); - const startedAt = performance.now(); - for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { - const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); - await withTransaction(database, async () => { - for (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) { - const placeholders = []; - const args = []; - const batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS); - for (let i = offset; i < batchEnd; i += 1) { - const accountId = skewed ? `acct-${i % 10 === 0 ? i % 512 : i % 8}` : `acct-${pseudoRandom(i) % 512}`; - const status = skewed ? i % 20 === 0 ? "failed" : "open" : ["open", "closed", "failed", "pending"][i % 4]; - placeholders.push("(?, ?, ?, ?, ?, ?)"); - args.push( - i + 1, - accountId, - status, - 17e11 + i * 1e3, - 100 + pseudoRandom(i + 41) % 5e4, - payload(`migration-${i}:`, rowBytes) - ); - } - await database.execute( - `INSERT INTO rw_migration_source (id, account_id, status, created_at, total_cents, body) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - }); - } - return { - rows, - targetBytes, - rowBytes, - setupMs: performance.now() - startedAt, - pageCount: await queryPageCount(database) - }; -} -async function readRowidRange(database, direction) { - const [count] = typedRows( - await database.execute("SELECT COUNT(*) AS rows FROM rw_orders") - ); - const rows = count?.rows ?? 0; - let bytes = 0; - let scannedRows = 0; - if (direction === "backward") { - for (let upper = rows; upper > 0; upper -= RANGE_CHUNK_ROWS) { - const lower = Math.max(1, upper - RANGE_CHUNK_ROWS + 1); - const chunk = typedRows( - await database.execute( - `SELECT length(note) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ? ORDER BY id DESC`, - lower, - upper - ) - ); - for (const row of chunk) { - bytes += row.bytes; - scannedRows += 1; - } - } - return { rows: scannedRows, bytes }; - } - for (let lower = 1; lower <= rows; lower += RANGE_CHUNK_ROWS) { - const upper = lower + RANGE_CHUNK_ROWS - 1; - const [chunk] = typedRows( - await database.execute( - `SELECT COUNT(*) AS rows, COALESCE(SUM(length(note)), 0) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ?`, - lower, - upper - ) - ); - bytes += chunk?.bytes ?? 0; - scannedRows += chunk?.rows ?? 0; - } - return { rows: scannedRows, bytes }; -} -var sqliteRealworldBench = actor51({ - options: { - actionTimeout: 12e5, - sleepGracePeriod: 3e4 - }, - db: db8({ - onMigrate: async (database) => { - await database.execute(`CREATE TABLE IF NOT EXISTS rw_customers ( - id INTEGER PRIMARY KEY, - account_id TEXT NOT NULL, - email TEXT NOT NULL, - plan TEXT NOT NULL, - region TEXT NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_orders ( - id INTEGER PRIMARY KEY, - customer_id INTEGER NOT NULL, - created_at INTEGER NOT NULL, - status TEXT NOT NULL, - total_cents INTEGER NOT NULL, - shard INTEGER NOT NULL, - note TEXT NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_order_items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id INTEGER NOT NULL, - sku TEXT NOT NULL, - quantity INTEGER NOT NULL, - price_cents INTEGER NOT NULL, - line_no INTEGER NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - account_id TEXT NOT NULL, - event_type TEXT NOT NULL, - created_at INTEGER NOT NULL, - entity_key TEXT NOT NULL, - properties TEXT NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_docs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - external_key TEXT NOT NULL UNIQUE, - row_rank INTEGER NOT NULL, - tenant_id TEXT NOT NULL, - body TEXT NOT NULL, - body_bytes INTEGER NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_ledger ( - account_id TEXT NOT NULL, - entry_id INTEGER NOT NULL, - amount_cents INTEGER NOT NULL, - created_at INTEGER NOT NULL, - memo TEXT NOT NULL, - PRIMARY KEY (account_id, entry_id) - ) WITHOUT ROWID`); - await database.execute(`CREATE TABLE IF NOT EXISTS rw_chat_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - thread_id TEXT NOT NULL, - seq INTEGER NOT NULL, - role TEXT NOT NULL, - content TEXT NOT NULL, - content_bytes INTEGER NOT NULL, - token_estimate INTEGER NOT NULL, - created_at INTEGER NOT NULL DEFAULT 0 - )`); - await database.execute( - "CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY AUTOINCREMENT, parent INTEGER, role TEXT NOT NULL, content TEXT NOT NULL, cancelled INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL)" - ); - await database.execute( - "CREATE TABLE IF NOT EXISTS tool_refs (id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id INTEGER NOT NULL, tool_name TEXT NOT NULL, tool_call_id TEXT NOT NULL, status TEXT NOT NULL)" - ); - await database.execute( - "CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY, event_type TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL)" - ); - await database.execute( - "CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)" - ); - await database.execute( - "CREATE TABLE IF NOT EXISTS tools (id INTEGER PRIMARY KEY AUTOINCREMENT, executor_id TEXT NOT NULL, name TEXT NOT NULL, spec TEXT NOT NULL, updated_at INTEGER NOT NULL)" - ); - await database.execute( - "CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_orders_customer_created ON rw_orders(customer_id, created_at DESC)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_orders_status_created ON rw_orders(status, created_at)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_orders_created ON rw_orders(created_at DESC)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_order_items_order ON rw_order_items(order_id)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_events_account_created ON rw_events(account_id, created_at)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_docs_external_rank ON rw_docs(external_key, row_rank)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_docs_tenant_rank ON rw_docs(tenant_id, row_rank)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_rw_chat_log_thread_seq ON rw_chat_log(thread_id, seq DESC)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_msgs_parent_role_cancelled_created_at ON msgs (parent, role, cancelled, created_at)" - ); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_tool_refs_msg_id ON tool_refs (msg_id)" - ); - } - }), - actions: { - inspectCacheConfig: async (c) => { - const [cacheSize] = typedRows( - await c.db.execute("PRAGMA cache_size") - ); - const [pageSize] = typedRows( - await c.db.execute("PRAGMA page_size") - ); - return { - sqliteCacheSizePragma: cacheSize?.cache_size ?? null, - sqlitePageSize: pageSize?.page_size ?? null, - pageCount: await queryPageCount(c.db) - }; - }, - setupWorkload: async (c, input) => { - assertWorkload(input.workload); - const rowBytes = positiveInteger2(input.rowBytes, DEFAULT_ROW_BYTES2, "rowBytes"); - if (input.workload === "migration-ddl-small") { - await resetMigration(c.db); - return { - rows: 0, - targetBytes: 0, - rowBytes, - setupMs: 0, - pageCount: await queryPageCount(c.db) - }; - } - const targetBytes = positiveInteger2( - input.targetBytes, - 8 * 1024 * 1024, - "targetBytes" - ); - switch (input.workload) { - case "small-rowid-point": - case "small-schema-read": - case "small-range-scan": - case "rowid-range-forward": - case "rowid-range-backward": - case "aggregate-status": - case "aggregate-time-bucket": - case "aggregate-tenant-time-range": - case "parallel-read-aggregates": - case "parallel-read-write-transition": - case "feed-order-by-limit": - case "feed-pagination-adjacent": - case "join-order-items": - case "random-point-lookups": - case "write-batch-after-wake": - case "update-hot-partition": - case "delete-churn-range-read": - return seedCommerce(c.db, targetBytes, rowBytes); - case "secondary-index-covering-range": - case "secondary-index-scattered-table": - case "hot-index-cold-table": - return seedDocs(c.db, targetBytes, rowBytes); - case "ledger-without-rowid-range": - return seedLedger(c.db, targetBytes, rowBytes); - case "chat-log-select-limit": - case "chat-log-select-indexed": - case "chat-log-count": - case "chat-log-sum": - case "chat-tool-read-fanout": - return seedChatLog2(c.db, targetBytes); - case "chat-tool-script": - return seedSqlRush(c.db, targetBytes); - case "migration-create-indexes-large": - return seedMigrationSource(c.db, targetBytes, rowBytes); - case "migration-create-indexes-skewed-large": - return seedMigrationSource(c.db, targetBytes, rowBytes, true); - case "migration-table-rebuild-large": - case "migration-add-column-large": - return seedMigrationSource(c.db, targetBytes, rowBytes); - } - }, - runWorkload: async (c, input) => { - assertWorkload(input.workload); - const t0 = performance.now(); - let details; - switch (input.workload) { - case "small-rowid-point": { - let bytes = 0; - for (let i = 0; i < 50; i += 1) { - const id = i % 16 + 1; - const [row] = typedRows( - await c.db.execute( - "SELECT length(note) AS bytes FROM rw_orders WHERE id = ?", - id - ) - ); - bytes += row?.bytes ?? 0; - } - details = { ops: 50, bytes }; - break; - } - case "small-schema-read": { - const tables = await c.db.execute( - "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index') ORDER BY name" - ); - const columns = await c.db.execute("PRAGMA table_info(rw_orders)"); - const [count] = typedRows( - await c.db.execute("SELECT COUNT(*) AS rows FROM rw_orders") - ); - details = { - objects: tables.length, - columns: columns.length, - rows: count?.rows ?? 0 - }; - break; - } - case "small-range-scan": - case "rowid-range-forward": { - details = await readRowidRange(c.db, "forward"); - break; - } - case "rowid-range-backward": { - details = await readRowidRange(c.db, "backward"); - break; - } - case "secondary-index-covering-range": { - const rows = typedRows( - await c.db.execute( - `SELECT external_key, row_rank FROM rw_docs - WHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff' - ORDER BY external_key` - ) - ); - let checksum2 = 0; - for (const row of rows) checksum2 = checksum2 + row.row_rank >>> 0; - details = { rows: rows.length, checksum: checksum2 }; - break; - } - case "secondary-index-scattered-table": { - const rows = typedRows( - await c.db.execute( - `SELECT body_bytes AS bytes FROM rw_docs - WHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff' - ORDER BY external_key` - ) - ); - let bytes = 0; - for (const row of rows) bytes += row.bytes; - details = { rows: rows.length, bytes }; - break; - } - case "aggregate-status": { - const rows = typedRows( - await c.db.execute( - `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total - FROM rw_orders - GROUP BY status - ORDER BY status` - ) - ); - details = { - groups: rows.length, - rows: rows.reduce((sum, row) => sum + row.rows, 0), - total: rows.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "aggregate-time-bucket": { - const rows = typedRows( - await c.db.execute( - `SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total - FROM rw_orders - GROUP BY bucket - ORDER BY bucket` - ) - ); - details = { - buckets: rows.length, - rows: rows.reduce((sum, row) => sum + row.rows, 0), - total: rows.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "aggregate-tenant-time-range": { - const rows = typedRows( - await c.db.execute( - `SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total - FROM rw_events e - JOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER) - WHERE e.account_id = ? AND e.created_at BETWEEN ? AND ? - GROUP BY e.event_type - ORDER BY e.event_type`, - "acct-7", - 17e11, - 17e11 + 864e5 - ) - ); - details = { - groups: rows.length, - rows: rows.reduce((sum, row) => sum + row.rows, 0), - total: rows.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "parallel-read-aggregates": { - const [ - statusRows, - bucketRows, - tenantRows, - joinRows - ] = await Promise.all([ - c.db.execute( - `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total - FROM rw_orders - GROUP BY status - ORDER BY status` - ), - c.db.execute( - `SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total - FROM rw_orders - GROUP BY bucket - ORDER BY bucket` - ), - c.db.execute( - `SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total - FROM rw_events e - JOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER) - WHERE e.account_id = ? AND e.created_at BETWEEN ? AND ? - GROUP BY e.event_type - ORDER BY e.event_type`, - "acct-7", - 17e11, - 17e11 + 864e5 - ), - c.db.execute( - `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total - FROM rw_orders o - JOIN rw_order_items oi ON oi.order_id = o.id - GROUP BY o.status - ORDER BY o.status` - ) - ]); - const aggregates = [ - ...typedRows(statusRows), - ...typedRows(bucketRows), - ...typedRows(tenantRows), - ...typedRows(joinRows) - ]; - details = { - ops: 4, - groups: aggregates.length, - rows: aggregates.reduce((sum, row) => sum + row.rows, 0), - total: aggregates.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "parallel-read-write-transition": { - const readStatus = c.db.execute( - `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total - FROM rw_orders - GROUP BY status - ORDER BY status` - ); - const readJoin = c.db.execute( - `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total - FROM rw_orders o - JOIN rw_order_items oi ON oi.order_id = o.id - GROUP BY o.status - ORDER BY o.status` - ); - const writeHotShard = c.db.execute( - "UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 7" - ); - const readAfterWrite = c.db.execute( - "SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 7" - ); - const [statusRows, joinRows, , shardRows] = await Promise.all([ - readStatus, - readJoin, - writeHotShard, - readAfterWrite - ]); - const aggregates = [ - ...typedRows(statusRows), - ...typedRows(joinRows) - ]; - const [shardCount] = typedRows(shardRows); - details = { - ops: 4, - readOps: 3, - writeOps: 1, - groups: aggregates.length, - rows: aggregates.reduce((sum, row) => sum + row.rows, 0) + (shardCount?.rows ?? 0), - total: aggregates.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "feed-order-by-limit": { - const rows = await c.db.execute( - `SELECT id, customer_id, created_at, status, total_cents - FROM rw_orders - WHERE created_at >= ? - ORDER BY created_at DESC - LIMIT 1000`, - 17e11 - ); - details = { rows: rows.length }; - break; - } - case "feed-pagination-adjacent": { - const firstPage = typedRows( - await c.db.execute( - `SELECT created_at - FROM rw_orders - WHERE created_at >= ? - ORDER BY created_at DESC - LIMIT ?`, - 17e11, - FEED_PAGE_ROWS - ) - ); - const cursor = firstPage.at(-1)?.created_at ?? 17e11; - const secondPage = await c.db.execute( - `SELECT id, customer_id, created_at, status, total_cents - FROM rw_orders - WHERE created_at < ? - ORDER BY created_at DESC - LIMIT ?`, - cursor, - FEED_PAGE_ROWS - ); - details = { firstPageRows: firstPage.length, rows: secondPage.length }; - break; - } - case "join-order-items": { - const rows = typedRows( - await c.db.execute( - `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total - FROM rw_orders o - JOIN rw_order_items oi ON oi.order_id = o.id - GROUP BY o.status - ORDER BY o.status` - ) - ); - details = { - groups: rows.length, - rows: rows.reduce((sum, row) => sum + row.rows, 0), - total: rows.reduce((sum, row) => sum + row.total, 0) - }; - break; - } - case "random-point-lookups": { - const [count] = typedRows( - await c.db.execute("SELECT COUNT(*) AS rows FROM rw_orders") - ); - const rows = Math.max(1, count?.rows ?? 1); - let bytes = 0; - for (let i = 0; i < POINT_LOOKUP_OPS; i += 1) { - const id = pseudoRandom(i) % rows + 1; - const [row] = typedRows( - await c.db.execute( - "SELECT length(note) AS bytes FROM rw_orders WHERE id = ?", - id - ) - ); - bytes += row?.bytes ?? 0; - } - details = { ops: POINT_LOOKUP_OPS, bytes }; - break; - } - case "hot-index-cold-table": { - const indexRows = typedRows( - await c.db.execute( - `SELECT id - FROM rw_docs - WHERE tenant_id = ? - ORDER BY row_rank - LIMIT 1000`, - "tenant-7" - ) - ); - let bytes = 0; - for (const row of indexRows) { - const [doc] = typedRows( - await c.db.execute( - "SELECT body_bytes AS bytes FROM rw_docs WHERE id = ?", - row.id - ) - ); - bytes += doc?.bytes ?? 0; - } - details = { rows: indexRows.length, bytes }; - break; - } - case "ledger-without-rowid-range": { - const rows = typedRows( - await c.db.execute( - `SELECT account_id, entry_id, amount_cents, length(memo) AS bytes - FROM rw_ledger - WHERE account_id BETWEEN 'acct-0040' AND 'acct-0180' - ORDER BY account_id, entry_id` - ) - ); - let bytes = 0; - for (const row of rows) bytes += row.bytes; - details = { rows: rows.length, bytes }; - break; - } - case "chat-log-select-limit": { - const rows = await c.db.execute( - "SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100" - ); - details = { rows: rows.length }; - break; - } - case "chat-log-select-indexed": { - const expectedRows = Math.max( - 1, - Math.ceil( - positiveInteger2(input.targetBytes, CHAT_LOG_CHUNK_BYTES2, "targetBytes") / CHAT_LOG_CHUNK_BYTES2 - ) - ); - const lowerBound = Math.max(0, expectedRows - 100); - const rows = await c.db.execute( - "SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", - CHAT_THREAD_ID, - lowerBound - ); - details = { rows: rows.length }; - break; - } - case "chat-log-count": { - const [row] = typedRows( - await c.db.execute( - "SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?", - CHAT_THREAD_ID - ) - ); - details = { ops: 1, rows: row?.count ?? 0 }; - break; - } - case "chat-log-sum": { - const [row] = typedRows( - await c.db.execute( - "SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?", - CHAT_THREAD_ID - ) - ); - details = { ops: 1, bytes: row?.total_bytes ?? 0 }; - break; - } - case "chat-tool-read-fanout": { - const expectedRows = Math.max( - 1, - Math.ceil( - positiveInteger2(input.targetBytes, CHAT_LOG_CHUNK_BYTES2, "targetBytes") / CHAT_LOG_CHUNK_BYTES2 - ) - ); - const lowerBound = Math.max(0, expectedRows - 100); - const [limitRows, indexedRows, countRows, sumRows] = await Promise.all([ - c.db.execute( - "SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100" - ), - c.db.execute( - "SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", - CHAT_THREAD_ID, - lowerBound - ), - c.db.execute( - "SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?", - CHAT_THREAD_ID - ), - c.db.execute( - "SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?", - CHAT_THREAD_ID - ) - ]); - const [countRow] = typedRows(countRows); - const [sumRow] = typedRows(sumRows); - details = { - ops: 4, - limitRows: limitRows.length, - indexedRows: indexedRows.length, - rows: countRow?.count ?? 0, - bytes: sumRow?.total_bytes ?? 0 - }; - break; - } - case "chat-tool-script": { - const [ - msgsRows, - toolRefsRows, - eventsRows, - kvRows, - toolsRows, - metaRows, - unresolvedRows - ] = await Promise.all([ - c.db.execute( - "SELECT id, role, length(content) AS bytes FROM msgs WHERE parent IS NOT NULL AND role = ? AND cancelled = 0 ORDER BY created_at DESC LIMIT 50", - "assistant" - ), - c.db.execute( - "SELECT id, tool_name, status FROM tool_refs WHERE status = ? ORDER BY id DESC LIMIT 50", - "pending" - ), - c.db.execute( - "SELECT seq, event_type, length(payload) AS bytes FROM events WHERE seq > ? ORDER BY seq ASC LIMIT 100", - 600 - ), - c.db.execute( - "SELECT key, length(value) AS bytes FROM kv ORDER BY updated_at DESC LIMIT 20" - ), - c.db.execute( - "SELECT id, name, length(spec) AS bytes FROM tools WHERE executor_id = ? ORDER BY updated_at DESC", - "exec-1" - ), - c.db.execute("SELECT key, length(value) AS bytes FROM meta"), - c.db.execute(`SELECT m.id, m.role, count(tr.id) AS pending_refs - FROM msgs m - LEFT JOIN tool_refs tr ON tr.msg_id = m.id AND tr.status = 'pending' - WHERE m.role = 'assistant' AND m.cancelled = 0 - GROUP BY m.id - ORDER BY m.created_at DESC - LIMIT 100`) - ]); - details = { - ops: 7, - msgsRows: msgsRows.length, - toolRefsRows: toolRefsRows.length, - eventsRows: eventsRows.length, - kvRows: kvRows.length, - toolsRows: toolsRows.length, - metaRows: metaRows.length, - unresolvedRows: unresolvedRows.length - }; - break; - } - case "write-batch-after-wake": { - const [count] = typedRows( - await c.db.execute("SELECT COUNT(*) AS rows FROM rw_orders") - ); - const startId = (count?.rows ?? 0) + 1; - await c.db.execute("BEGIN"); - for (let offset = 0; offset < 1e3; offset += ORDER_BATCH_ROWS) { - const placeholders = []; - const args = []; - for (let i = offset; i < offset + ORDER_BATCH_ROWS; i += 1) { - const id = startId + i; - placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); - args.push( - id, - i % 128 + 1, - 18e11 + i, - "pending", - 1e3 + i, - i % 128, - payload(`wake-insert-${id}:`, DEFAULT_ROW_BYTES2) - ); - } - await c.db.execute( - `INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${placeholders.join(", ")}`, - ...args - ); - } - await c.db.execute("COMMIT"); - details = { rows: 1e3 }; - break; - } - case "update-hot-partition": { - await c.db.execute( - "UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 15" - ); - const [count] = typedRows( - await c.db.execute( - "SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 15" - ) - ); - details = { rows: count?.rows ?? 0 }; - break; - } - case "delete-churn-range-read": { - await c.db.execute("DELETE FROM rw_orders WHERE shard BETWEEN 0 AND 15"); - const result = await readRowidRange(c.db, "forward"); - details = { - ...result, - deletedShardCount: 16 - }; - break; - } - case "migration-create-indexes-large": { - await c.db.execute( - "CREATE INDEX idx_rw_migration_source_account ON rw_migration_source(account_id)" - ); - await c.db.execute( - "CREATE INDEX idx_rw_migration_source_created ON rw_migration_source(created_at)" - ); - await c.db.execute( - "CREATE INDEX idx_rw_migration_source_status_total ON rw_migration_source(status, total_cents)" - ); - details = { indexes: 3 }; - break; - } - case "migration-create-indexes-skewed-large": { - await c.db.execute( - "CREATE INDEX idx_rw_migration_source_skew_account ON rw_migration_source(account_id, created_at)" - ); - await c.db.execute( - "CREATE INDEX idx_rw_migration_source_skew_status ON rw_migration_source(status, total_cents)" - ); - details = { indexes: 2, skewed: true }; - break; - } - case "migration-table-rebuild-large": { - await c.db.execute(`CREATE TABLE rw_migration_source_rebuilt ( - id INTEGER PRIMARY KEY, - account_id TEXT NOT NULL, - status TEXT NOT NULL, - created_at INTEGER NOT NULL, - total_cents INTEGER NOT NULL, - body TEXT NOT NULL, - archived_at INTEGER - )`); - await c.db.execute(`INSERT INTO rw_migration_source_rebuilt ( - id, account_id, status, created_at, total_cents, body, archived_at - ) - SELECT id, account_id, status, created_at, total_cents, body, NULL - FROM rw_migration_source`); - await c.db.execute("DROP TABLE rw_migration_source"); - await c.db.execute( - "ALTER TABLE rw_migration_source_rebuilt RENAME TO rw_migration_source" - ); - details = { rebuilt: true }; - break; - } - case "migration-add-column-large": { - await c.db.execute( - "ALTER TABLE rw_migration_source ADD COLUMN archived_at INTEGER" - ); - details = { alters: 1, rewritesRows: false }; - break; - } - case "migration-ddl-small": { - await c.db.execute(`CREATE TABLE rw_migration_empty ( - id INTEGER PRIMARY KEY, - tenant_id TEXT NOT NULL, - created_at INTEGER NOT NULL - )`); - await c.db.execute("ALTER TABLE rw_migration_empty ADD COLUMN status TEXT"); - await c.db.execute( - "CREATE INDEX idx_rw_migration_empty_tenant_created ON rw_migration_empty(tenant_id, created_at)" - ); - await c.db.execute(`CREATE TABLE rw_migration_audit ( - id INTEGER PRIMARY KEY, - migration_name TEXT NOT NULL, - applied_at INTEGER NOT NULL - )`); - details = { tables: 2, indexes: 1, alters: 1 }; - break; - } - } - const ms = performance.now() - t0; - return { - ms, - workload: input.workload, - ...details, - pageCount: await queryPageCount(c.db) - }; - }, - goToSleep: (c) => { - c.sleep(); - return { ok: true }; - } - } -}); - -// src/actors/testing/raw-sqlite-fuzzer.ts -import { actor as actor52 } from "rivetkit"; -import { db as db9 } from "rivetkit/db"; -var ACCOUNT_COUNT = 8; -var ACCOUNT_INITIAL_BALANCE = 1e5; -var DEFAULT_KEY_SPACE = 64; -var DEFAULT_MAX_PAYLOAD_BYTES = 8 * 1024; -var DEFAULT_GROWTH_TARGET_BYTES = 1024 * 1024; -var LARGE_WRITE_CHUNK_BYTES = 96 * 1024; -var PAGE_BOUNDARY_SIZES = [ - 1, - 4095, - 4096, - 4097, - 8191, - 8192, - 8193, - 32768, - 65535, - 65536, - 98304, - 131072 -]; -function hashSeed(input) { - let hash = 2166136261; - for (let i = 0; i < input.length; i += 1) { - hash ^= input.charCodeAt(i); - hash = Math.imul(hash, 16777619); - } - return hash >>> 0; -} -function makeRng(seed) { - let state = hashSeed(seed) || 2654435769; - return () => { - state = state + 1831565813 >>> 0; - let t = state; - t = Math.imul(t ^ t >>> 15, t | 1); - t ^= t + Math.imul(t ^ t >>> 7, t | 61); - return ((t ^ t >>> 14) >>> 0) / 4294967296; - }; -} -function intBetween(rng, min, max) { - return min + Math.floor(rng() * (max - min + 1)); -} -function checksum(input) { - let hash = 2166136261; - for (let i = 0; i < input.length; i += 1) { - hash ^= input.charCodeAt(i); - hash = Math.imul(hash, 16777619); - } - return hash >>> 0; -} -function payloadFor(seed, phase, index, bytes) { - const prefix = `${seed}:${phase}:${index}:`; - if (bytes <= prefix.length) return prefix.slice(0, bytes); - return prefix + "x".repeat(bytes - prefix.length); -} -async function queryOne(database, sql, ...args) { - const rows = await database.execute(sql, ...args); - return rows[0]; -} -async function transaction(database, fn) { - await database.execute("BEGIN"); - try { - const result = await fn(); - await database.execute("COMMIT"); - return result; - } catch (err) { - await database.execute("ROLLBACK").catch(() => void 0); - throw err; - } -} -async function recordProbe(database, phase, scenario, name, expected, actual, mismatch) { - await database.execute( - `INSERT INTO fuzz_probe_results ( - phase, scenario, name, expected, actual, mismatch, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?)`, - phase, - scenario, - name, - String(expected), - String(actual), - mismatch ? 1 : 0, - Date.now() - ); -} -function firstColumn(row) { - if (!row || typeof row !== "object") return void 0; - const values = Object.values(row); - return values[0]; -} -async function ensureAccounts(database) { - await database.execute("BEGIN"); - try { - for (let i = 0; i < ACCOUNT_COUNT; i += 1) { - await database.execute( - "INSERT OR IGNORE INTO fuzz_accounts (id, balance) VALUES (?, ?)", - `acct-${i}`, - ACCOUNT_INITIAL_BALANCE - ); - } - await database.execute("COMMIT"); - } catch (err) { - await database.execute("ROLLBACK").catch(() => void 0); - throw err; - } -} -async function recordItemEvent(database, phase, localIndex, kind, itemKey, present, value, version, updateCount, payload2, applied) { - await database.execute( - `INSERT INTO fuzz_item_events ( - phase, local_index, kind, item_key, present, value, version, - update_count, payload_checksum, payload_bytes, applied, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - phase, - localIndex, - kind, - itemKey, - present ? 1 : 0, - value, - version, - updateCount, - checksum(payload2), - payload2.length, - applied ? 1 : 0, - Date.now() - ); -} -async function upsertLiveItem(database, row, payload2) { - await database.execute( - `INSERT INTO fuzz_items ( - item_key, value, version, update_count, payload, payload_checksum, - payload_bytes, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(item_key) DO UPDATE SET - value = excluded.value, - version = excluded.version, - update_count = excluded.update_count, - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - row.item_key, - row.value, - row.version, - row.update_count, - payload2, - row.payload_checksum, - row.payload_bytes, - Date.now() + GROUP BY m.message_id + ORDER BY m.created_at DESC + LIMIT 1` ); -} -async function applyItemOperation(database, opts) { - let current; - try { - current = await queryOne( - database, - "SELECT item_key, value, version, update_count, payload, payload_checksum, payload_bytes FROM fuzz_items WHERE item_key = ?", - opts.itemKey + const unresolvedMessageId = unresolvedRows[0]?.message_id; + if (typeof unresolvedMessageId === "string") { + await timedQuery( + sql, + stats, + steps, + "get-persisted-tool-result-ids", + `SELECT tool_result.tool_use_id + FROM message_tool_refs AS tool_result + JOIN messages AS tool_result_message + ON tool_result_message.message_id = tool_result.source_message_id + WHERE tool_result.assistant_message_id = ? + AND tool_result.block_type = 'tool_result' + AND tool_result.cancelled = 0 + AND tool_result_message.parent_tool_use_id IS NULL`, + unresolvedMessageId ); - } catch (error) { - throw new Error( - `item operation select failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, - { cause: error } + await timedQuery( + sql, + stats, + steps, + "get-tool-calls-by-message-id", + `SELECT * FROM tool_calls WHERE message_id = ?`, + unresolvedMessageId ); } - const payload2 = payloadFor( - opts.seed, - opts.phase, - opts.localIndex, - opts.payloadBytes + await timedQuery( + sql, + stats, + steps, + "is-last-message-cancelled-assistant", + `SELECT role, cancelled FROM messages + WHERE parent_tool_use_id IS NULL + ORDER BY created_at DESC + LIMIT 1` ); - const nextVersion = (current?.version ?? 0) + 1; - const nextUpdateCount = (current?.update_count ?? 0) + 1; - const nextValue = `${opts.kind}:${opts.phase}:${opts.localIndex}:${nextVersion}`; - if (opts.kind === "delete") { - try { - await recordItemEvent( - database, - opts.phase, - opts.localIndex, - opts.kind, - opts.itemKey, - false, - null, - nextVersion, - nextUpdateCount, - "", - current !== void 0 - ); - } catch (error) { - throw new Error(`item operation event insert failed for delete key ${JSON.stringify(opts.itemKey)}`, { - cause: error - }); - } - try { - await database.execute("DELETE FROM fuzz_items WHERE item_key = ?", opts.itemKey); - } catch (error) { - throw new Error(`item operation delete failed for key ${JSON.stringify(opts.itemKey)}`, { - cause: error - }); - } - return; - } - if (opts.kind === "insert" && current) { - try { - await recordItemEvent( - database, - opts.phase, - opts.localIndex, - opts.kind, - opts.itemKey, - true, - current.value, - current.version, - current.update_count, - current.payload ?? "", - false - ); - } catch (error) { - throw new Error( - `item operation event insert failed for noop insert key ${JSON.stringify(opts.itemKey)}`, - { cause: error } - ); - } - return; - } - const row = { - item_key: opts.itemKey, - value: nextValue, - version: nextVersion, - update_count: nextUpdateCount, - payload_checksum: checksum(payload2), - payload_bytes: payload2.length + await timedQuery( + sql, + stats, + steps, + "get-last-uncancelled", + `SELECT m.* FROM messages m + WHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL + ORDER BY m.created_at DESC + LIMIT 1` + ); + return { + name: "build-tool-plan-context", + totalMs: Math.round(performance.now() - startedAt), + steps + }; +} +async function runCatchupSnapshot(sql, version, stats) { + const startedAt = performance.now(); + const steps = []; + await Promise.all([ + timedQuery( + sql, + stats, + steps, + "thread-events-list-since-version", + `SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`, + version + ), + timedQuery( + sql, + stats, + steps, + "environment-snapshot", + `SELECT snapshot FROM environment_snapshot WHERE id = 1` + ), + timedQuery( + sql, + stats, + steps, + "thread-settings-snapshot", + `SELECT settings FROM thread_settings_snapshot WHERE id = 1` + ), + timedQuery( + sql, + stats, + steps, + "retry-state", + `SELECT * FROM retry_state WHERE id = 1` + ), + timedQuery( + sql, + stats, + steps, + "queued-messages", + `SELECT * FROM queued_messages ORDER BY created_at ASC` + ), + timedQuery( + sql, + stats, + steps, + "executor-artifacts", + `SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC` + ), + timedQuery( + sql, + stats, + steps, + "tool-approvals", + `SELECT * FROM tool_approvals ORDER BY timestamp ASC` + ), + timedQuery( + sql, + stats, + steps, + "compaction-summaries", + `SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC` + ), + timedQuery( + sql, + stats, + steps, + "executor-status", + `SELECT value FROM thread_meta_kv WHERE key = 'executor_status'` + ) + ]); + steps.sort((a, b) => b.durationMs - a.durationMs); + return { + name: "catchup-snapshot", + totalMs: Math.round(performance.now() - startedAt), + steps }; - try { - await recordItemEvent( - database, - opts.phase, - opts.localIndex, - opts.kind, - opts.itemKey, - true, - row.value, - row.version, - row.update_count, - payload2, - true - ); - } catch (error) { - throw new Error( - `item operation event insert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, - { cause: error } - ); - } - try { - await upsertLiveItem(database, row, payload2); - } catch (error) { - throw new Error( - `item operation live-row upsert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, - { cause: error } - ); - } } -async function applyHotUpdates(database, opts) { - for (let i = 0; i < opts.updates; i += 1) { - try { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: opts.localIndex * 1e3 + i, - kind: "update", - itemKey: opts.itemKey, - payloadBytes: opts.payloadBytes - }); - } catch (error) { - throw new Error( - `hot update failed for ${opts.itemKey} at sub-update ${i + 1}/${opts.updates} with payloadBytes ${opts.payloadBytes}`, - { cause: error } - ); - } - } +async function runRecoverToolCalls(sql, stats) { + const startedAt = performance.now(); + const steps = []; + await timedQuery( + sql, + stats, + steps, + "hydrate-tool-progress", + `SELECT id, progress + FROM tool_calls + WHERE progress IS NOT NULL + AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + ); + await timedQuery( + sql, + stats, + steps, + "get-pending-tool-calls", + `SELECT * FROM tool_calls + WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running') + ORDER BY issued_at ASC` + ); + await timedQuery( + sql, + stats, + steps, + "get-next-tool-expiry", + `SELECT MIN(expires_at) AS expires_at + FROM tool_calls + WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + ); + return { + name: "recover-tool-calls", + totalMs: Math.round(performance.now() - startedAt), + steps + }; } -async function applyTransfer(database, opts) { - await transaction(database, async () => { - const before = await queryOne( - database, - "SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts" - ); - await database.execute( - "UPDATE fuzz_accounts SET balance = balance - ? WHERE id = ?", - opts.amount, - opts.fromAccount +async function runMutationMix(sql, clientId, stats) { + const startedAt = performance.now(); + const steps = []; + const writeCount = await sql.withTransaction(stats, async (tx) => { + const now = (/* @__PURE__ */ new Date()).toISOString(); + const suffix = safeId(clientId); + const seqRows = await timedQuery( + tx, + stats, + steps, + "select-max-thread-event-seq", + `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` ); - await database.execute( - "UPDATE fuzz_accounts SET balance = balance + ? WHERE id = ?", - opts.amount, - opts.toAccount + const seq = Number(seqRows[0]?.seq ?? 1); + const lastMessageRows = await timedQuery( + tx, + stats, + steps, + "select-last-message-created-at", + `SELECT MAX(created_at) AS created_at FROM messages` ); - const after = await queryOne( - database, - "SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts" + const latestToolRows = await timedQuery( + tx, + stats, + steps, + "select-existing-tool-call", + `SELECT id FROM tool_calls ORDER BY issued_at DESC LIMIT 1` ); - await database.execute( - `INSERT INTO fuzz_transfer_events ( - phase, local_index, from_account, to_account, amount, - balance_sum_before, balance_sum_after, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - opts.phase, - opts.localIndex, - opts.fromAccount, - opts.toAccount, - opts.amount, - before?.total ?? 0, - after?.total ?? 0, - Date.now() + await timedQuery( + tx, + stats, + steps, + "select-sandbox-row", + `SELECT sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup + FROM e2b_sandbox + WHERE id = 1` ); - }); -} -async function applyEdgePayloads(database, opts) { - const writeEdgePayload = async (id, kind, payload2, sizeLabel) => { - const payloadChecksum = checksum(payload2); - const payloadBytes = payload2.length; - try { - await database.execute("BEGIN"); - } catch (error) { - throw new Error(`edge payload begin failed for ${sizeLabel}`, { - cause: error - }); - } - try { - try { - await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - kind = excluded.kind, - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - id, - kind, - payload2, - payloadChecksum, - payloadBytes, - Date.now() - ); - } catch (error) { - throw new Error(`edge payload row upsert failed for ${sizeLabel}`, { - cause: error - }); - } - try { - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = excluded.present, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - id, - payloadChecksum, - payloadBytes - ); - } catch (error) { - throw new Error(`edge payload expectation upsert failed for ${sizeLabel}`, { - cause: error - }); - } - try { - await database.execute("COMMIT"); - } catch (error) { - throw new Error(`edge payload commit failed for ${sizeLabel}`, { - cause: error - }); - } - } catch (error) { - await database.execute("ROLLBACK").catch(() => void 0); - throw error; - } - }; - const sizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= opts.maxPayloadBytes); - if (!sizes.includes(opts.maxPayloadBytes)) sizes.push(opts.maxPayloadBytes); - let ops = 0; - for (const size of sizes) { - const id = `edge-${opts.phase}-${size}`; - const payload2 = payloadFor(opts.seed, opts.phase, size, size); - try { - await writeEdgePayload(id, "boundary", payload2, `size ${size}`); - } catch (error) { - throw new Error(`edge payload write failed for size ${size}`, { - cause: error - }); - } - ops += 1; - } - const unicodePayload = `escaped-nul:\\0 unicode:\u2603\uFE0F phase:${opts.phase} seed:${opts.seed}`; - const unicodeId = `edge-${opts.phase}-unicode-nul`; - try { - await writeEdgePayload( - unicodeId, - "unicode-nul", - unicodePayload, - "unicode escaped-nul payload" + const messageIdValue = `agent2-message-${suffix}-${seq}`; + const toolUseIdValue = `agent2-tool-${suffix}-${seq}`; + const toolCallIdValue = `agent2-call-${suffix}-${seq}`; + const latestToolCallId = String(latestToolRows[0]?.id ?? toolUseID(1)); + const lastCreatedAt = String(lastMessageRows[0]?.created_at ?? now); + await timedQuery( + tx, + stats, + steps, + "upsert-agent-state", + `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES (?, ?, ?)`, + "last_agent_state", + JSON.stringify({ status: "working", clientId, lastCreatedAt }), + now ); - } catch (error) { - throw new Error("edge payload write failed for unicode escaped-nul payload", { - cause: error - }); - } - return ops + 1; -} -async function applyActualNulPayload(database, opts) { - const payload2 = `actual-nul:\0 phase:${opts.phase} seed:${opts.seed}`; - const id = `actual-nul-${opts.phase}`; - await transaction(database, async () => { - await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'actual-nul', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - id, - payload2, - checksum(payload2), - payload2.length, - Date.now() + await timedQuery( + tx, + stats, + steps, + "insert-work-event", + `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, + seq, + "message_added", + JSON.stringify({ + type: "message_added", + messageId: messageIdValue + }), + now ); - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = 1, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - id, - checksum(payload2), - payload2.length + await timedQuery( + tx, + stats, + steps, + "insert-message", + `INSERT INTO messages (role, content, meta, user_state, message_id, created_at, cancelled, parent_tool_use_id, tool_result_for_message_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "assistant", + "agent concurrent 2 mutation payload", + JSON.stringify({ clientId, seq }), + null, + messageIdValue, + now, + 0, + null, + null + ); + await timedQuery( + tx, + stats, + steps, + "delete-message-tool-refs", + `DELETE FROM message_tool_refs WHERE source_message_id = ?`, + messageIdValue + ); + await timedQuery( + tx, + stats, + steps, + "insert-message-added-event", + `INSERT OR IGNORE INTO message_added_events (message_id, seq) VALUES (?, ?)`, + messageIdValue, + seq + ); + await timedQuery( + tx, + stats, + steps, + "insert-message-tool-ref", + `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled) + VALUES (?, ?, ?, ?, ?)`, + messageIdValue, + messageIdValue, + toolUseIdValue, + "tool_use", + 0 + ); + await timedQuery( + tx, + stats, + steps, + "insert-tool-call", + `INSERT OR IGNORE INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + toolCallIdValue, + `provider-${toolCallIdValue}`, + "tool_1", + JSON.stringify({ path: `/tmp/${toolCallIdValue}` }), + "seed-executor", + messageIdValue, + now, + null, + "running", + null, + JSON.stringify({ pct: 0.5, clientId }), + null + ); + await timedQuery( + tx, + stats, + steps, + "update-tool-call-progress", + `UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`, + JSON.stringify({ pct: 0.75, clientId, updatedAt: now }), + toolCallIdValue + ); + await timedQuery( + tx, + stats, + steps, + "update-existing-tool-call-progress", + `UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`, + JSON.stringify({ pct: 0.25, clientId, updatedAt: now }), + latestToolCallId ); + return seq; }); - return 1; + steps.push({ + name: "transaction-total", + durationMs: Math.round(performance.now() - startedAt), + rowCount: writeCount + }); + return { + name: "mutation-mix", + totalMs: Math.round(performance.now() - startedAt), + steps + }; } -async function applyFragmentationChurn(database, opts) { - const rows = Math.max(12, Math.floor(opts.iterations / 2)); - let ops = 0; - for (let i = 0; i < rows; i += 1) { - const size = intBetween(opts.rng, 32, Math.max(32, opts.maxPayloadBytes)); - const id = `frag-${opts.phase}-${i}`; - const payload2 = payloadFor(opts.seed, opts.phase, 1e4 + i, size); - try { - await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'fragment', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - id, - payload2, - checksum(payload2), - payload2.length, - Date.now() - ); - } catch (error) { - throw new Error(`fragmentation payload upsert failed for ${id} at size ${size}`, { - cause: error - }); - } - try { - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = 1, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - id, - checksum(payload2), - payload2.length - ); - } catch (error) { - throw new Error(`fragmentation expectation upsert failed for ${id} at size ${size}`, { - cause: error - }); - } - ops += 1; - } - for (let i = 0; i < rows; i += 3) { - const id = `frag-${opts.phase}-${i}`; - try { - await database.execute("DELETE FROM fuzz_edge_payloads WHERE id = ?", id); - } catch (error) { - throw new Error(`fragmentation delete failed for ${id}`, { - cause: error - }); - } - try { - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 0, 0, 0) - ON CONFLICT(id) DO UPDATE SET - present = 0, - payload_checksum = 0, - payload_bytes = 0`, - id - ); - } catch (error) { - throw new Error(`fragmentation tombstone expectation failed for ${id}`, { - cause: error - }); - } - ops += 1; - } - for (let i = 1; i < rows; i += 4) { - const size = intBetween(opts.rng, 1, Math.max(1, opts.maxPayloadBytes)); - const id = `frag-${opts.phase}-${i}`; - const payload2 = payloadFor(opts.seed, opts.phase, 2e4 + i, size); - try { - await database.execute( - `UPDATE fuzz_edge_payloads - SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? - WHERE id = ?`, - payload2, - checksum(payload2), - payload2.length, - Date.now(), - id - ); - } catch (error) { - throw new Error(`fragmentation payload rewrite failed for ${id} at size ${size}`, { - cause: error - }); - } - try { - await database.execute( - `UPDATE fuzz_edge_expectations - SET payload_checksum = ?, payload_bytes = ? - WHERE id = ? AND present = 1`, - checksum(payload2), - payload2.length, - id - ); - } catch (error) { - throw new Error(`fragmentation expectation rewrite failed for ${id} at size ${size}`, { - cause: error - }); - } - ops += 1; - } - if (opts.phase % 2 === 1) { - try { - await database.execute("VACUUM"); - } catch (error) { - throw new Error(`fragmentation vacuum failed for phase ${opts.phase}`, { - cause: error - }); - } - ops += 1; +async function timedQuery(sql, stats, steps, name, query, ...values) { + const startedAt = performance.now(); + try { + const rows = await sql(query, ...values); + const durationMs = Math.round(performance.now() - startedAt); + recordAgentConcurrent2Query( + stats, + name, + query, + durationMs, + rows.length, + false + ); + steps.push({ + name, + durationMs, + rowCount: rows.length + }); + return rows; + } catch (error) { + const durationMs = Math.round(performance.now() - startedAt); + recordAgentConcurrent2Query(stats, name, query, durationMs, 0, true); + throw error; } - return ops; } -async function applySchemaChurn(database, phase) { - const table = `fuzz_schema_phase_${phase}`; - const index = `idx_fuzz_schema_phase_${phase}_name`; - const view = `view_fuzz_schema_phase_${phase}`; - const dropIndex = `idx_fuzz_schema_drop_probe_${phase}`; - await database.execute( - `CREATE TABLE IF NOT EXISTS ${table} ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - value INTEGER NOT NULL DEFAULT 0, - extra TEXT - )` - ); - await database.execute(`CREATE INDEX IF NOT EXISTS ${index} ON ${table}(name, value)`); +async function executeTrackedQuery(execute, stats, name, query, ...values) { + const startedAt = performance.now(); try { - await database.execute(`ALTER TABLE ${table} ADD COLUMN altered_${phase} TEXT DEFAULT 'altered'`); - } catch { - const column = await queryOne( - database, - `SELECT COUNT(*) AS count FROM pragma_table_info('${table}') WHERE name = ?`, - `altered_${phase}` + const rows = await execute(query, ...values); + recordAgentConcurrent2Query( + stats, + name, + query, + Math.round(performance.now() - startedAt), + rows.length, + false ); - if ((column?.count ?? 0) !== 1) throw new Error(`failed to add altered_${phase}`); - } - await database.execute(`CREATE VIEW IF NOT EXISTS ${view} AS SELECT id, name, value FROM ${table}`); - await database.execute( - `INSERT INTO ${table} (name, value, extra) - VALUES (?, ?, ?) - ON CONFLICT(name) DO UPDATE SET - value = excluded.value, - extra = excluded.extra`, - `schema-${phase}`, - phase, - `extra-${phase}` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS fuzz_without_rowid ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL - ) WITHOUT ROWID` - ); - await database.execute(` - CREATE TRIGGER IF NOT EXISTS trg_fuzz_edge_payload_update - AFTER UPDATE ON fuzz_edge_payloads - BEGIN - INSERT INTO fuzz_trigger_audit ( - payload_id, old_checksum, new_checksum, created_at - ) VALUES ( - new.id, old.payload_checksum, new.payload_checksum, strftime('%s', 'now') * 1000 - ); - END - `); - await database.execute( - `INSERT INTO fuzz_without_rowid (id, value) - VALUES (?, ?) - ON CONFLICT(id) DO UPDATE SET value = excluded.value`, - `phase-${phase}`, - phase - ); - for (const [name, type] of [ - [table, "table"], - [index, "index"], - [view, "view"], - ["trg_fuzz_edge_payload_update", "trigger"], - ["fuzz_without_rowid", "table"] - ]) { - await database.execute( - `INSERT INTO fuzz_schema_registry (name, type) - VALUES (?, ?) - ON CONFLICT(name) DO UPDATE SET type = excluded.type`, + return rows; + } catch (error) { + recordAgentConcurrent2Query( + stats, name, - type + query, + Math.round(performance.now() - startedAt), + 0, + true ); + throw error; } - await database.execute("CREATE TEMP TABLE IF NOT EXISTS fuzz_temp_probe (id INTEGER PRIMARY KEY, value TEXT)"); - await database.execute("INSERT INTO fuzz_temp_probe (value) VALUES (?)", `temp-${phase}`); - await database.execute("DROP TABLE fuzz_temp_probe"); - await database.execute(`CREATE INDEX IF NOT EXISTS ${dropIndex} ON fuzz_schema_registry(type)`); - await database.execute(`DROP INDEX IF EXISTS ${dropIndex}`); - const dropped = await queryOne( - database, - "SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = ?", - dropIndex - ); - await recordProbe( - database, - phase, - "schema", - "drop-index", - 0, - dropped?.count ?? -1, - (dropped?.count ?? -1) !== 0 - ); - return 13; } -async function applyIndexProbe(database, opts) { - const rows = Math.max(20, opts.iterations); - await transaction(database, async () => { - for (let i = 0; i < rows; i += 1) { - const tenant = `tenant-${intBetween(opts.rng, 0, 5)}`; - const bucket = intBetween(opts.rng, 0, 12); - const score = intBetween(opts.rng, -500, 500); - const label = `${opts.seed}:${opts.phase}:${i}`; - await database.execute( - `INSERT INTO fuzz_indexed (tenant, bucket, score, label, payload) - VALUES (?, ?, ?, ?, ?)`, - tenant, - bucket, - score, - label, - payloadFor(opts.seed, opts.phase, 3e4 + i, intBetween(opts.rng, 8, 256)) - ); +function recordAgentConcurrent2Query(stats, name, query, durationMs, rowCount, failed) { + const classification = classifyAgentConcurrent2Query(query); + for (const target of [stats.cycle, stats.wake, stats.actor]) { + target.total++; + target.rows += rowCount; + if (failed) target.errors++; + if (durationMs >= SLOW_QUERY_MS) target.slow++; + if (durationMs > target.maxMs) { + target.maxMs = durationMs; + target.maxStep = `${name}:${classification.table}`; } - }); - return rows; + target.byOperation[classification.operation] = (target.byOperation[classification.operation] ?? 0) + 1; + target.byTable[classification.table] = (target.byTable[classification.table] ?? 0) + 1; + if (classification.kind === "read") { + target.reads++; + } else if (classification.kind === "mutation") { + target.mutations++; + } else if (classification.kind === "tx") { + target.tx++; + } else { + target.other++; + } + } } -async function applyPreparedChurn(database, opts) { - const rows = Math.max(32, opts.iterations); - for (let i = 0; i < rows; i += 1) { - const id = `prep-${opts.phase}-${i}`; - const payload2 = payloadFor( - opts.seed, - opts.phase, - 7e4 + i, - Math.min(opts.maxPayloadBytes, 64 + i % 257) - ); - await database.execute( - `INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum) - VALUES (?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - value = excluded.value, - payload = excluded.payload, - payload_checksum = excluded.payload_checksum - /* unique-prepared-${opts.phase}-${i} */`, - id, - i, - payload2, - checksum(payload2) - ); - await database.execute( - `INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum) - VALUES (?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - value = excluded.value, - payload_checksum = excluded.payload_checksum`, - id, - i, - checksum(payload2) - ); +function classifyAgentConcurrent2Query(query) { + const normalized = query.trim().replace(/\s+/g, " "); + const operation = normalized.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? "other"; + const table = extractAgentConcurrent2Table(normalized, operation); + if (operation === "select") { + return { operation, kind: "read", table }; } - const repeatedId = `prep-repeat-${opts.phase}`; + if (operation === "insert" || operation === "update" || operation === "delete" || operation === "replace") { + return { operation, kind: "mutation", table }; + } + if (operation === "begin" || operation === "commit" || operation === "rollback") { + return { operation, kind: "tx", table }; + } + return { operation, kind: "other", table }; +} +function extractAgentConcurrent2Table(query, operation) { + const lower = query.toLowerCase(); + if (operation === "select") { + return firstMatch(lower, /\bfrom\s+([a-z0-9_]+)/) ?? "unknown"; + } + if (operation === "insert" || operation === "replace") { + return firstMatch(lower, /\binto\s+([a-z0-9_]+)/) ?? "unknown"; + } + if (operation === "update") { + return firstMatch(lower, /\bupdate\s+([a-z0-9_]+)/) ?? "unknown"; + } + if (operation === "delete") { + return firstMatch(lower, /\bfrom\s+([a-z0-9_]+)/) ?? "unknown"; + } + if (operation === "begin" || operation === "commit" || operation === "rollback") { + return "transaction"; + } + return "unknown"; +} +function firstMatch(value, pattern) { + return pattern.exec(value)?.[1] ?? null; +} +function hasPendingLaunch(value) { + if (typeof value !== "string" || value.length === 0) { + return false; + } + try { + const parsed = JSON.parse(value); + return parsed.pendingLaunch !== null && parsed.pendingLaunch !== void 0; + } catch { + return false; + } +} +function delay(ms) { + return new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms))); +} +async function createAgentConcurrent2Schema(database) { + await database.execute(`CREATE TABLE IF NOT EXISTS executor_tools ( + executor_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + schema TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (executor_id, tool_name) + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv ( + key TEXT PRIMARY KEY, + value TEXT, + updated_at TEXT NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS thread_events ( + seq INTEGER PRIMARY KEY, + event_type TEXT NOT NULL, + payload TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + )`); await database.execute( - `INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum) - VALUES (?, 0, '', 0) - ON CONFLICT(id) DO UPDATE SET value = 0, payload = '', payload_checksum = 0`, - repeatedId + `CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)` ); - for (let i = 0; i < rows; i += 1) { - const payload2 = payloadFor(opts.seed, opts.phase, 8e4 + i, Math.min(512, opts.maxPayloadBytes)); - await database.execute( - `UPDATE fuzz_prepared_churn - SET value = value + ?, payload = ?, payload_checksum = ? - WHERE id = ?`, - 1, - payload2, - checksum(payload2), - repeatedId - ); - } - const finalPayload = payloadFor(opts.seed, opts.phase, 8e4 + rows - 1, Math.min(512, opts.maxPayloadBytes)); + await database.execute(`CREATE TABLE IF NOT EXISTS message_added_events ( + message_id TEXT PRIMARY KEY, + seq INTEGER NOT NULL UNIQUE + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS messages ( + message_id TEXT PRIMARY KEY, + role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')), + content TEXT NOT NULL, + meta TEXT, + user_state TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + cancelled INTEGER NOT NULL DEFAULT 0, + read_at TEXT, + parent_tool_use_id TEXT, + tool_result_for_message_id TEXT + )`); await database.execute( - `INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum) - VALUES (?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - value = excluded.value, - payload_checksum = excluded.payload_checksum`, - repeatedId, - rows, - checksum(finalPayload) + `CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)` ); - return rows * 2 + 1; -} -async function applyReadWriteProbe(database, opts) { - if ((await queryOne(database, "SELECT COUNT(*) AS count FROM fuzz_indexed"))?.count === 0) { - await applyIndexProbe(database, opts); - } - const read = database.execute( - `SELECT - COUNT(*) AS joined_rows, - COALESCE(SUM(a.score + b.score), 0) AS score_sum - FROM fuzz_indexed a - JOIN fuzz_indexed b ON b.bucket = a.bucket - WHERE a.tenant <= 'tenant-3'` + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)` ); - const write = applyIndexProbe(database, { - seed: opts.seed, - phase: opts.phase, - rng: opts.rng, - iterations: Math.max(10, Math.floor(opts.iterations / 2)) - }); - const [readRows, writeOps] = await Promise.all([read, write]); - const row = readRows[0]; - const joinedRows = Number(row?.joined_rows ?? -1); - const scoreSum = Number(row?.score_sum ?? Number.NaN); - await recordProbe( - database, - opts.phase, - "readwrite", - "long-read-while-write", - "nonnegative-finite", - `${joinedRows}:${scoreSum}`, - joinedRows < 0 || !Number.isFinite(scoreSum) + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)` ); - return writeOps + 1; -} -async function applyBoundaryKeys(database, opts) { - const keys = [ - "", - " ", - `long-${"k".repeat(2048)}`, - "slash/key", - "comma,key", - "percent%key", - "CaseKey", - "casekey" - ]; - let ops = 0; - for (const [index, key] of keys.entries()) { - try { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 9e4 + index, - kind: "upsert", - itemKey: key, - payloadBytes: Math.min(opts.maxPayloadBytes, 128 + index) - }); - } catch (error) { - throw new Error( - `boundary key write failed for literal key ${JSON.stringify(key)} at index ${index}`, - { cause: error } - ); - } - ops += 1; - } - for (let i = 0; i < 128; i += 1) { - const itemKey = `seq-${opts.phase}-${i.toString().padStart(4, "0")}`; - try { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 91e3 + i, - kind: i % 4 === 0 ? "delete" : "upsert", - itemKey, - payloadBytes: Math.min(opts.maxPayloadBytes, 32 + i % 97) - }); - } catch (error) { - throw new Error( - `boundary key write failed for sequential key ${JSON.stringify(itemKey)} at index ${i}`, - { cause: error } - ); - } - ops += 1; - } - await recordProbe(database, opts.phase, "boundary-keys", "keys-written", 136, ops, ops !== 136); - return ops; -} -async function applyGrowthProbe(database, opts) { - const chunkBytes = Math.max(1, Math.min(LARGE_WRITE_CHUNK_BYTES, opts.maxPayloadBytes)); - const rows = Math.max(1, Math.ceil(opts.growthTargetBytes / chunkBytes)); - let written = 0; - for (let i = 0; i < rows; i += 1) { - const size = Math.min(chunkBytes, opts.growthTargetBytes - written); - const id = `growth-${opts.phase}-${opts.growthTargetBytes}-${i}`; - const payload2 = payloadFor(opts.seed, opts.phase, 1e5 + i, size); - await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'growth', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - id, - payload2, - checksum(payload2), - payload2.length, - Date.now() - ); - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = 1, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - id, - checksum(payload2), - payload2.length - ); - written += size; - } - await recordProbe( - database, - opts.phase, - "growth", - "target-bytes-written", - opts.growthTargetBytes, - written, - written !== opts.growthTargetBytes + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)` ); - return rows; -} -async function applyTruncateRecreateProbe(database, opts) { - const id = `truncate-${opts.phase}`; - const largeSize = Math.max(1, Math.min(opts.maxPayloadBytes, 131072)); - const largePayload = payloadFor(opts.seed, opts.phase, 11e4, largeSize); - const tinyPayload = payloadFor(opts.seed, opts.phase, 110001, 1); - const recreatedPayload = payloadFor(opts.seed, opts.phase, 110002, Math.min(4096, largeSize)); + await database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs ( + source_message_id TEXT NOT NULL, + assistant_message_id TEXT NOT NULL, + tool_use_id TEXT NOT NULL, + block_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')), + cancelled INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (source_message_id, block_type, tool_use_id) + )`); await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'truncate', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - id, - largePayload, - checksum(largePayload), - largePayload.length, - Date.now() + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)` ); await database.execute( - "UPDATE fuzz_edge_payloads SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? WHERE id = ?", - tinyPayload, - checksum(tinyPayload), - tinyPayload.length, - Date.now(), - id + `CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0` ); - await database.execute("DELETE FROM fuzz_edge_payloads WHERE id = ?", id); - await database.execute("VACUUM"); await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'truncate', ?, ?, ?, ?)`, - id, - recreatedPayload, - checksum(recreatedPayload), - recreatedPayload.length, - Date.now() + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS tool_calls ( + id TEXT PRIMARY KEY, + provider_tool_use_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + args TEXT NOT NULL, + executor_id TEXT, + message_id TEXT NOT NULL, + issued_at TEXT NOT NULL, + expires_at TEXT, + state TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')), + result TEXT, + progress TEXT, + completed_at TEXT + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` ); await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = 1, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - id, - checksum(recreatedPayload), - recreatedPayload.length + `CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)` ); - return 5; -} -async function updateShadowChecksums(database, phase) { - const item = await queryOne( - database, - `SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + version + update_count), 0) AS value - FROM fuzz_items` + await database.execute( + `CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)` ); - const edge = await queryOne( - database, - `SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value - FROM fuzz_edge_payloads` + await database.execute( + `CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)` ); - await transaction(database, async () => { - for (const [name, rows, value] of [ - ["items", item?.rows ?? 0, item?.value ?? 0], - ["edge", edge?.rows ?? 0, edge?.value ?? 0] - ]) { - await database.execute( - `INSERT INTO fuzz_shadow_checksums (name, value, row_count) - VALUES (?, ?, ?) - ON CONFLICT(name) DO UPDATE SET - value = excluded.value, - row_count = excluded.row_count`, - name, - value, - rows - ); - } - }); - await recordProbe(database, phase, "shadow", "shadow-updated", 2, 2, false); - return 2; -} -async function applyConstraintChaos(database, phase) { - await database.execute("PRAGMA foreign_keys = ON"); - const validPrefix = `valid-${phase}`; - const existingValidRows = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_constraints WHERE id LIKE ?", - `${validPrefix}-%` + await database.execute( + `CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)` ); - const runSeq = existingValidRows?.count ?? 0; - const validId = `${validPrefix}-${runSeq}`; - const uniqValue = `uniq-${phase}-${runSeq}`; - const before = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_constraints" + await database.execute( + `CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)` ); await database.execute( - `INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) - VALUES (?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - must_not_null = excluded.must_not_null, - qty = excluded.qty, - uniq = excluded.uniq`, - validId, - "ok", - phase, - uniqValue + `CREATE TABLE IF NOT EXISTS e2b_sandbox ( + id INTEGER PRIMARY KEY CHECK (id = 1), + sandbox_id TEXT, + restart_attempts INTEGER NOT NULL DEFAULT 0, + traffic_access_token TEXT, + project_id TEXT, + repository_url TEXT, + additional_repositories TEXT, + setup TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )` ); - const attempts = [ - { - name: `not-null-${phase}-${runSeq}`, - sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", - args: [`bad-null-${phase}-${runSeq}`, null, 1, `bad-null-${phase}-${runSeq}`] - }, - { - name: `check-${phase}-${runSeq}`, - sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", - args: [`bad-check-${phase}-${runSeq}`, "ok", -1, `bad-check-${phase}-${runSeq}`] - }, - { - name: `unique-${phase}-${runSeq}`, - sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", - args: [`bad-unique-${phase}-${runSeq}`, "ok", 1, uniqValue] - } - ]; - for (const attempt of attempts) { - const attemptBefore = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_constraints" - ); - let failed = false; - try { - await database.execute(attempt.sql, ...attempt.args); - } catch { - failed = true; - } - const attemptAfter = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_constraints" - ); - await database.execute( - `INSERT INTO fuzz_constraint_attempts ( - name, expected_failed, actually_failed, before_count, after_count - ) VALUES (?, 1, ?, ?, ?)`, - attempt.name, - failed ? 1 : 0, - attemptBefore?.count ?? 0, - attemptAfter?.count ?? 0 - ); - } - const after = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_constraints" + await database.execute( + `CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))` ); - if ((after?.count ?? 0) !== (before?.count ?? 0) + 1) { - await database.execute( - `INSERT INTO fuzz_constraint_attempts ( - name, expected_failed, actually_failed, before_count, after_count - ) VALUES (?, 0, 0, ?, ?)`, - `valid-count-${phase}-${runSeq}`, - before?.count ?? 0, - after?.count ?? 0 - ); - } - const parentId = `fk-parent-${phase}-${runSeq}`; - const childId = `fk-child-${phase}-${runSeq}`; await database.execute( - "INSERT INTO fuzz_fk_parent (id) VALUES (?) ON CONFLICT(id) DO NOTHING", - parentId + `CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)` ); await database.execute( - `INSERT INTO fuzz_fk_child (id, parent_id) - VALUES (?, ?) - ON CONFLICT(id) DO UPDATE SET parent_id = excluded.parent_id`, - childId, - parentId + `CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)` ); - const childBeforeDelete = await queryOne( +} +async function seedAgentConcurrent2Data(database) { + const existing = await database.execute( + `SELECT COUNT(*) AS count FROM messages` + ); + if (Number(existing[0]?.count ?? 0) > 0) { + return; + } + const now = (/* @__PURE__ */ new Date("2026-05-16T03:58:18.661Z")).getTime(); + const text2 = (size) => "x".repeat(size); + const isoAt = (index) => new Date(now + index * 1e3).toISOString(); + await batchInsert( database, - "SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?", - parentId + `INSERT INTO thread_meta_kv (key, value, updated_at)`, + [ + ["executor_type", "local-client", isoAt(0)], + [ + "workspace_intent", + JSON.stringify({ spec: null, pendingLaunch: null }), + isoAt(0) + ], + [ + "executor_status", + JSON.stringify({ available: true, message: "ready" }), + isoAt(0) + ] + ] ); - await database.execute("DELETE FROM fuzz_fk_parent WHERE id = ?", parentId); - const childAfterDelete = await queryOne( + const messageRows = []; + for (let index = 1; index <= MESSAGE_COUNT; index++) { + const role = index % 2 === 0 ? "assistant" : "user"; + messageRows.push([ + messageId(index), + role, + text2(MESSAGE_CONTENT_BYTES), + null, + null, + isoAt(index), + 0, + null, + null, + null + ]); + } + await batchInsert( database, - "SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?", - parentId + `INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`, + messageRows, + 20 ); - await recordProbe( + const messageToolRefRows = []; + for (let index = 0; index < MESSAGE_TOOL_REF_COUNT / 2; index++) { + const assistantIndex = 2 + index % 42 * 2; + const sourceIndex = Math.max(1, assistantIndex - 1); + const resultIndex = Math.min(MESSAGE_COUNT, assistantIndex + 1); + const toolUseId = toolUseID(index + 1); + messageToolRefRows.push([ + messageId(sourceIndex), + messageId(assistantIndex), + toolUseId, + "tool_use", + 0 + ]); + messageToolRefRows.push([ + messageId(resultIndex), + messageId(assistantIndex), + toolUseId, + "tool_result", + 0 + ]); + } + await batchInsert( database, - phase, - "constraints", - "fk-cascade-delete", - 0, - childAfterDelete?.count ?? -1, - (childBeforeDelete?.count ?? 0) !== 1 || (childAfterDelete?.count ?? -1) !== 0 + `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`, + messageToolRefRows, + 50 ); - const fkBefore = await queryOne( + const toolCallRows = []; + for (let index = 1; index <= TOOL_CALL_COUNT; index++) { + const assistantIndex = 2 + (index - 1) % 42 * 2; + toolCallRows.push([ + toolUseID(index), + `provider-${index}`, + `tool_${index % 21}`, + JSON.stringify({ path: `/tmp/file-${index}` }), + "seed-executor", + messageId(assistantIndex), + isoAt(index), + null, + "completed", + JSON.stringify({ + ok: true, + run: { status: "done", result: text2(TOOL_CALL_RESULT_BYTES) } + }), + null, + isoAt(index + 100) + ]); + } + await batchInsert( database, - "SELECT COUNT(*) AS count FROM fuzz_fk_child" + `INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`, + toolCallRows, + 20 ); - let fkFailed = false; - try { - await database.execute( - "INSERT INTO fuzz_fk_child (id, parent_id) VALUES (?, ?)", - `fk-orphan-${phase}-${runSeq}`, - `missing-parent-${phase}-${runSeq}` - ); - } catch { - fkFailed = true; + const executorToolRows = []; + for (let index = 1; index <= EXECUTOR_TOOL_COUNT; index++) { + const schema = JSON.stringify({ + name: `tool_${index}`, + description: text2(EXECUTOR_TOOL_SCHEMA_BYTES), + input_schema: { type: "object", properties: {} } + }); + executorToolRows.push([ + "seed-executor", + `tool_${index}`, + schema, + isoAt(index) + ]); + } + await batchInsert( + database, + `INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`, + executorToolRows, + 42 + ); + const threadEventRows = []; + for (let index = 1; index <= THREAD_EVENT_COUNT; index++) { + threadEventRows.push([ + index, + index % 3 === 0 ? "message_added" : "agent_state_changed", + JSON.stringify({ + type: "seed_event", + body: text2(THREAD_EVENT_PAYLOAD_BYTES) + }), + isoAt(index) + ]); } - const fkAfter = await queryOne( + await batchInsert( database, - "SELECT COUNT(*) AS count FROM fuzz_fk_child" + `INSERT INTO thread_events (seq, event_type, payload, created_at)`, + threadEventRows, + 25 ); - await recordProbe( + const messageAddedRows = []; + for (let index = 1; index <= MESSAGE_COUNT; index++) { + messageAddedRows.push([messageId(index), index]); + } + await batchInsert( database, - phase, - "constraints", - "fk-failure-isolation", - `${fkBefore?.count ?? 0}:failed`, - `${fkAfter?.count ?? 0}:${fkFailed ? "failed" : "inserted"}`, - !fkFailed || (fkAfter?.count ?? 0) !== (fkBefore?.count ?? 0) + `INSERT INTO message_added_events (message_id, seq)`, + messageAddedRows, + 50 ); - return attempts.length + 3; -} -async function applyPragmaProbe(database, phase) { - let ops = 0; - for (const [name, setupSql, checkSql, expected] of [ - ["journal_mode", "PRAGMA journal_mode = DELETE", "PRAGMA journal_mode", "nonempty"], - ["synchronous", "PRAGMA synchronous = NORMAL", "PRAGMA synchronous", "nonempty"], - ["cache_size", "PRAGMA cache_size = -2000", "PRAGMA cache_size", "-2000"], - ["foreign_keys", "PRAGMA foreign_keys = ON", "PRAGMA foreign_keys", "1"], - ["auto_vacuum", "PRAGMA auto_vacuum", "PRAGMA auto_vacuum", "nonempty"] - ]) { - try { - await database.execute(setupSql); - const rows = await database.execute(checkSql); - const actual = String(firstColumn(rows[0]) ?? ""); - await recordProbe( - database, - phase, - "pragma", - name, - expected, - actual, - expected === "nonempty" ? actual.length === 0 : actual !== expected - ); - } catch (err) { - await recordProbe( - database, - phase, - "pragma", - name, - expected, - err instanceof Error ? err.message : "unknown error", - true - ); - } - ops += 1; - } - return ops; -} -async function applySavepointScenario(database, phase) { - const keepId = `save-keep-${phase}`; - const rolledBackId = `save-rolled-back-${phase}`; - await database.execute("BEGIN"); - try { - await database.execute( - "INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value", - keepId, - phase - ); - await database.execute("SAVEPOINT sp_rollback_probe"); - await database.execute( - "INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value", - rolledBackId, - 999e3 + phase - ); - await database.execute( - "UPDATE fuzz_savepoints SET value = value + 1000 WHERE id = ?", - keepId - ); - await database.execute("ROLLBACK TO sp_rollback_probe"); - await database.execute("RELEASE sp_rollback_probe"); - await database.execute("COMMIT"); - } catch (err) { - await database.execute("ROLLBACK").catch(() => void 0); - throw err; - } await database.execute( - `INSERT INTO fuzz_savepoint_expectations (id, present, value) - VALUES (?, 1, ?) - ON CONFLICT(id) DO UPDATE SET present = 1, value = excluded.value`, - keepId, - phase + `INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`, + JSON.stringify({ cwd: "/workspace", body: text2(3620) }), + isoAt(0) ); await database.execute( - `INSERT INTO fuzz_savepoint_expectations (id, present, value) - VALUES (?, 0, 0) - ON CONFLICT(id) DO UPDATE SET present = 0, value = 0`, - rolledBackId + `INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`, + JSON.stringify({ maxTokens: 2e4, body: text2(55) }), + isoAt(0) ); - return 5; -} -async function applyIdempotentReplay(database, phase) { - const targetId = `idem-target-${phase % 3}`; await database.execute( - "INSERT OR IGNORE INTO fuzz_idempotent_targets (id, value) VALUES (?, 0)", - targetId + `INSERT INTO e2b_sandbox (id, sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup, created_at, updated_at) + VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "sandbox-seed", + 0, + "token-seed", + "project-seed", + "https://example.invalid/repo.git", + JSON.stringify([]), + JSON.stringify({ commands: [] }), + isoAt(0), + isoAt(0) ); - for (let i = 0; i < 8; i += 1) { - const opId = `idem-${phase}-${i}`; - const amount = phase + i + 1; - for (let attempt = 0; attempt < 3; attempt += 1) { - await transaction(database, async () => { - const existing = await queryOne( - database, - "SELECT op_id FROM fuzz_idempotent_ops WHERE op_id = ?", - opId - ); - if (!existing) { - await database.execute( - "INSERT INTO fuzz_idempotent_ops (op_id, target_id, amount) VALUES (?, ?, ?)", - opId, - targetId, - amount - ); - await database.execute( - "UPDATE fuzz_idempotent_targets SET value = value + ? WHERE id = ?", - amount, - targetId - ); - } - }); +} +async function batchInsert(database, insertPrefix, rows, batchSize = 100) { + if (rows.length === 0) { + return; + } + const columnCount = rows[0]?.length ?? 0; + if (columnCount === 0) { + return; + } + const rowPlaceholder = `(${"?,".repeat(columnCount).slice(0, -1)})`; + for (let index = 0; index < rows.length; index += batchSize) { + const chunk = rows.slice(index, index + batchSize); + const values = chunk.map(() => rowPlaceholder).join(","); + const bindings = chunk.flat(); + await database.execute(`${insertPrefix} VALUES ${values}`, ...bindings); + } +} +function messageId(index) { + return `M-${String(index).padStart(22, "0")}`; +} +function toolUseID(index) { + return `toolu_${String(index).padStart(22, "0")}`; +} +function safeId(value) { + return value.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80); +} + +// src/actors/testing/mock-agentic-loop.ts +import { + actor as actor41 +} from "rivetkit"; +import { db as db7 } from "rivetkit/db"; +var DEFAULT_SLEEP_GRACE_PERIOD_MS = 12e4; +var DEFAULT_ON_SLEEP_DELAY_MS = 0; +var debugSocketsByActorId = /* @__PURE__ */ new Map(); +function sleep4(ms) { + return new Promise((resolve2) => setTimeout(resolve2, ms)); +} +function positiveInteger(value, name) { + if (!Number.isInteger(value) || value < 1) { + throw new Error(`${name} must be a positive integer`); + } + return value; +} +function stringValue(value, name) { + if (typeof value !== "string" || value.length === 0) { + throw new Error(`${name} must be a non-empty string`); + } + return value; +} +function typedRows(rows) { + return rows; +} +function numberFromEnv(name, fallback) { + const value = process.env[name]; + if (value === void 0 || value === "") return fallback; + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed < 0) { + throw new Error(`${name} must be a finite non-negative number`); + } + return parsed; +} +function send3(websocket, payload2) { + if (websocket.readyState !== 1) return; + websocket.send(JSON.stringify(payload2)); +} +function debugPayload(row, replayed) { + return { + type: "debugEvent", + eventId: row.event_id, + name: row.name, + actorId: row.actor_id, + connectionId: row.connection_id, + requestId: row.request_id, + details: JSON.parse(row.details_json), + createdAt: row.created_at, + replayed + }; +} +function publishDebugEvent(row) { + const sockets = debugSocketsByActorId.get(row.actor_id); + if (!sockets) return; + for (const socket of sockets) { + send3(socket, debugPayload(row, false)); + } +} +function addDebugSocket(actorId, websocket) { + const sockets = debugSocketsByActorId.get(actorId) ?? /* @__PURE__ */ new Set(); + sockets.add(websocket); + debugSocketsByActorId.set(actorId, sockets); + return () => { + sockets.delete(websocket); + if (sockets.size === 0) { + debugSocketsByActorId.delete(actorId); } + }; +} +async function recordDebugEvent(c, input) { + const row = { + event_id: crypto.randomUUID(), + name: input.name, + actor_id: c.actorId, + connection_id: input.connectionId ?? null, + request_id: input.requestId ?? null, + details_json: JSON.stringify(input.details ?? {}), + created_at: input.createdAt ?? Date.now() + }; + try { + await c.db.execute( + "INSERT INTO mock_agentic_debug_events (event_id, name, actor_id, connection_id, request_id, details_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", + row.event_id, + row.name, + row.actor_id, + row.connection_id, + row.request_id, + row.details_json, + row.created_at + ); + publishDebugEvent(row); + } catch (error) { + c.log.warn({ + msg: "mock agentic debug event failed", + name: input.name, + err: error instanceof Error ? error.message : String(error) + }); } - return 24; } -async function ensureRelationalSeed(database) { - await transaction(database, async () => { - for (let i = 0; i < 8; i += 1) { - await database.execute( - "INSERT OR IGNORE INTO fuzz_rel_users (id, name) VALUES (?, ?)", - `user-${i}`, - `User ${i}` - ); - } - for (let i = 0; i < 12; i += 1) { - const productId = `product-${i}`; - const initialQty = 1e4; - await database.execute( - "INSERT OR IGNORE INTO fuzz_rel_products (id, price) VALUES (?, ?)", - productId, - (i + 1) * 7 - ); - await database.execute( - `INSERT OR IGNORE INTO fuzz_inventory ( - product_id, initial_qty, sold_qty, stock_qty - ) VALUES (?, ?, 0, ?)`, - productId, - initialQty, - initialQty - ); - } - }); +async function replayDebugEvents(database, websocket) { + const rows = typedRows( + await database.execute(` + SELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at + FROM ( + SELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at + FROM mock_agentic_debug_events + ORDER BY created_at DESC + LIMIT 200 + ) + ORDER BY created_at ASC + `) + ); + for (const row of rows) { + send3(websocket, debugPayload(row, true)); + } } -async function applyRelationalOrder(database, opts) { - await ensureRelationalSeed(database); - const orderPrefix = `order-${opts.phase}-${opts.localIndex}`; - const existingOrders = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_orders WHERE id LIKE ?", - `${orderPrefix}-%` +function verifyEntryRows(rows, expectedSeconds) { + const seen = /* @__PURE__ */ new Set(); + const indexes = rows.map((row) => row.idx).sort((a, b) => a - b); + for (const idx of indexes) seen.add(idx); + const missing = []; + for (let idx = 1; idx <= expectedSeconds; idx += 1) { + if (!seen.has(idx)) missing.push(idx); + } + const contiguous = rows.length === expectedSeconds && missing.length === 0 && indexes.every((idx, offset) => idx === offset + 1); + return { + expectedSeconds, + count: rows.length, + contiguous, + missing, + indexes, + ok: contiguous + }; +} +function verifyAllRows(rows, expectedRequests) { + const expectedByRequest = new Map( + expectedRequests.map((request) => [request.requestId, request.seconds]) ); - const orderId = `${orderPrefix}-${existingOrders?.count ?? 0}`; - const userId = `user-${intBetween(opts.rng, 0, 7)}`; - const itemCount = intBetween(opts.rng, 1, 4); - let total = 0; - await transaction(database, async () => { - await database.execute( - "INSERT INTO fuzz_orders (id, user_id, total, status) VALUES (?, ?, 0, 'open')", - orderId, - userId + const rowsByRequest = /* @__PURE__ */ new Map(); + for (const row of rows) { + const requestRows = rowsByRequest.get(row.request_id) ?? []; + requestRows.push(row); + rowsByRequest.set(row.request_id, requestRows); + } + const requests = expectedRequests.map((request) => { + const result = verifyEntryRows( + rowsByRequest.get(request.requestId) ?? [], + request.seconds ); - for (let i = 0; i < itemCount; i += 1) { - const productId = `product-${intBetween(opts.rng, 0, 11)}`; - const product = await queryOne( - database, - "SELECT price FROM fuzz_rel_products WHERE id = ?", - productId - ); - const quantity = intBetween(opts.rng, 1, 5); - const price = product?.price ?? 0; - total += price * quantity; + return { + requestId: request.requestId, + ...result + }; + }); + const unexpectedRequestIds = [...rowsByRequest.keys()].filter((requestId) => !expectedByRequest.has(requestId)).sort(); + const expectedTotalRows = expectedRequests.reduce( + (total, request) => total + request.seconds, + 0 + ); + const ok = unexpectedRequestIds.length === 0 && rows.length === expectedTotalRows && requests.every((request) => request.ok); + return { + type: "verifiedAll", + expectedRequests: expectedRequests.length, + expectedTotalRows, + totalRows: rows.length, + rows, + unexpectedRequestIds, + requests, + ok + }; +} +var mockAgenticLoop = actor41({ + options: { + canHibernateWebSocket: false, + sleepGracePeriod: DEFAULT_SLEEP_GRACE_PERIOD_MS + }, + db: db7({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS mock_agentic_entries ( + request_id TEXT NOT NULL, + idx INTEGER NOT NULL, + created_at INTEGER NOT NULL, + PRIMARY KEY (request_id, idx) + ) + `); await database.execute( - `INSERT INTO fuzz_order_items ( - order_id, product_id, quantity, price - ) VALUES (?, ?, ?, ?)`, - orderId, - productId, - quantity, - price + "CREATE INDEX IF NOT EXISTS idx_mock_agentic_entries_created_at ON mock_agentic_entries(created_at)" ); + await database.execute(` + CREATE TABLE IF NOT EXISTS mock_agentic_sleep_state ( + id INTEGER PRIMARY KEY CHECK (id = 1), + sleep_started_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS mock_agentic_debug_events ( + event_id TEXT PRIMARY KEY, + name TEXT NOT NULL, + actor_id TEXT NOT NULL, + connection_id TEXT, + request_id TEXT, + details_json TEXT NOT NULL, + created_at INTEGER NOT NULL + ) + `); await database.execute( - `UPDATE fuzz_inventory - SET sold_qty = sold_qty + ?, stock_qty = stock_qty - ? - WHERE product_id = ?`, - quantity, - quantity, - productId + "CREATE INDEX IF NOT EXISTS idx_mock_agentic_debug_events_created_at ON mock_agentic_debug_events(created_at)" ); } - await database.execute( - "UPDATE fuzz_orders SET total = ?, status = 'paid' WHERE id = ?", - total, - orderId + }), + async onWake(c) { + await recordDebugEvent(c, { + name: "onWake", + details: { + key: c.key, + name: c.name + } + }); + }, + async onSleep(c) { + const delayMs = numberFromEnv( + "MOCK_AGENTIC_ON_SLEEP_DELAY_MS", + DEFAULT_ON_SLEEP_DELAY_MS ); - await database.execute( - "INSERT INTO fuzz_payments (order_id, amount, status) VALUES (?, ?, 'captured')", - orderId, - total + const sleepStartedAt = Date.now(); + await recordDebugEvent(c, { + name: "onSleepStart", + createdAt: sleepStartedAt, + details: { + delayMs + } + }); + await c.db.execute( + "INSERT OR REPLACE INTO mock_agentic_sleep_state (id, sleep_started_at) VALUES (1, ?)", + sleepStartedAt ); - }); - return itemCount + 4; -} -async function applyRollbackProbe(database, phase, rowCount = 20) { - const before = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?", - `rollback-${phase}-%` - ); - await database.execute("BEGIN"); - try { - for (let i = 0; i < rowCount; i += 1) { - await database.execute( - `INSERT INTO fuzz_items ( - item_key, value, version, update_count, payload, payload_checksum, - payload_bytes, updated_at - ) VALUES (?, ?, 1, 1, ?, ?, ?, ?)`, - `rollback-${phase}-${i}`, - "should-not-survive", - "rollback-payload", - checksum("rollback-payload"), - "rollback-payload".length, - Date.now() + c.log.info({ + msg: "mock agentic loop onSleep delay", + delayMs, + sleepStartedAt + }); + await sleep4(delayMs); + await recordDebugEvent(c, { + name: "onSleepEnd", + details: { + delayMs, + sleepStartedAt, + elapsedMs: Date.now() - sleepStartedAt + } + }); + }, + async onRequest(c, request) { + const url = new URL(request.url); + if (url.pathname === "/bypass" || url.pathname === "/request/bypass") { + const [sleepState] = typedRows( + await c.db.execute( + "SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1" + ) + ); + return new Response( + JSON.stringify({ + type: "bypass", + transport: "http", + sleepStarted: sleepState !== void 0, + sleepStartedAt: sleepState?.sleep_started_at ?? null, + timestamp: Date.now() + }), + { + headers: { + "content-type": "application/json" + } + } ); } - throw new Error("intentional rollback probe"); - } catch { - await database.execute("ROLLBACK"); - } - const after = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?", - `rollback-${phase}-%` - ); - await database.execute( - `INSERT INTO fuzz_constraint_attempts ( - name, expected_failed, actually_failed, before_count, after_count - ) VALUES (?, 1, 1, ?, ?)`, - `rollback-probe-${phase}`, - before?.count ?? 0, - after?.count ?? 0 - ); - return rowCount; -} -async function applyNastyScript(database, opts) { - const growKey = `nasty-grow-${opts.phase}`; - let ops = 0; - const growMax = Math.min(opts.maxPayloadBytes, 131072); - const growSizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= growMax); - if (!growSizes.includes(1)) growSizes.unshift(1); - if (!growSizes.includes(growMax)) growSizes.push(growMax); - for (const size of growSizes) { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 5e4 + size, - kind: "upsert", - itemKey: growKey, - payloadBytes: Math.min(size, opts.maxPayloadBytes) + return new Response("not found", { status: 404 }); + }, + onWebSocket(c, websocket) { + const connectionId = crypto.randomUUID(); + let activeInference; + const removeDebugSocket = addDebugSocket(c.actorId, websocket); + send3(websocket, { + type: "hello", + connectionId, + timestamp: Date.now() }); - ops += 1; - } - const hotUpdates = opts.intense ? 1e4 : 250; - await applyHotUpdates(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 6e4, - itemKey: `nasty-hot-${opts.phase}`, - updates: hotUpdates, - payloadBytes: Math.min(1024, opts.maxPayloadBytes) - }); - ops += hotUpdates; - if (opts.intense) { - await database.execute("CREATE INDEX IF NOT EXISTS idx_nasty_heavy_write ON fuzz_items(value, version)"); - for (let i = 0; i < 1e4; i += 1) { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 12e4 + i, - kind: "upsert", - itemKey: `nasty-bulk-${opts.phase}-${i}`, - payloadBytes: Math.min(256, opts.maxPayloadBytes) + void (async () => { + try { + await replayDebugEvents(c.db, websocket); + } catch (error) { + c.log.warn({ + msg: "mock agentic debug replay failed", + err: error instanceof Error ? error.message : String(error) + }); + } + await recordDebugEvent(c, { + name: "webSocketOpen", + connectionId }); - ops += 1; - } - for (let i = 0; i < 1e4; i += 2) { - await applyItemOperation(database, { - seed: opts.seed, - phase: opts.phase, - localIndex: 14e4 + i, - kind: "delete", - itemKey: `nasty-bulk-${opts.phase}-${i}`, - payloadBytes: 1 + })(); + const verify = async (requestId, expectedSeconds) => { + const rows = typedRows( + await c.db.execute( + "SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC", + requestId + ) + ); + return { + type: "verified", + requestId, + ...verifyEntryRows(rows, expectedSeconds) + }; + }; + const sleepStatus = async () => { + const [sleepState] = typedRows( + await c.db.execute( + "SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1" + ) + ); + return { + sleepStarted: sleepState !== void 0, + sleepStartedAt: sleepState?.sleep_started_at ?? null + }; + }; + const runInference = async (requestId, seconds) => { + send3(websocket, { + type: "started", + requestId, + seconds, + timestamp: Date.now() }); - ops += 1; - } - await database.execute("DROP INDEX IF EXISTS idx_nasty_heavy_write"); - } - const rollbackRows = opts.intense ? 1e3 : 20; - await applyRollbackProbe(database, opts.phase, rollbackRows); - ops += rollbackRows; - return ops; -} -async function applyDeterministicNastyScript(database, opts) { - let ops = 0; - const growId = `nasty-script-grow-${opts.phase}`; - const maxGrowBytes = Math.min(opts.maxPayloadBytes, 131072); - let finalGrowPayload = ""; - await transaction(database, async () => { - for (let i = 0; i < 256; i += 1) { - const size = Math.max(1, Math.floor(1 + (maxGrowBytes - 1) * i / 255)); - const payload2 = payloadFor(opts.seed, opts.phase, 16e4 + i, size); - finalGrowPayload = payload2; - await database.execute( - `INSERT INTO fuzz_edge_payloads ( - id, kind, payload, payload_checksum, payload_bytes, updated_at - ) VALUES (?, 'nasty-grow', ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET - payload = excluded.payload, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes, - updated_at = excluded.updated_at`, - growId, - payload2, - checksum(payload2), - payload2.length, - Date.now() + await c.db.execute( + "DELETE FROM mock_agentic_entries WHERE request_id = ?", + requestId ); - ops += 1; - } - await database.execute( - `INSERT INTO fuzz_edge_expectations ( - id, present, payload_checksum, payload_bytes - ) VALUES (?, 1, ?, ?) - ON CONFLICT(id) DO UPDATE SET - present = 1, - payload_checksum = excluded.payload_checksum, - payload_bytes = excluded.payload_bytes`, - growId, - checksum(finalGrowPayload), - finalGrowPayload.length + for (let idx = 1; idx <= seconds; idx += 1) { + await sleep4(1e3); + const createdAt = Date.now(); + await c.db.execute( + "INSERT INTO mock_agentic_entries (request_id, idx, created_at) VALUES (?, ?, ?)", + requestId, + idx, + createdAt + ); + send3(websocket, { + type: "progress", + requestId, + idx, + seconds, + createdAt + }); + } + const verification = await verify(requestId, seconds); + send3(websocket, { + type: "done", + requestId, + seconds, + timestamp: Date.now(), + verification + }); + }; + websocket.addEventListener( + "message", + async (event19) => { + try { + if (typeof event19.data !== "string") { + throw new Error("message data must be a JSON string"); + } + const message = JSON.parse(event19.data); + const type = stringValue(message.type, "type"); + if (type === "history") { + const rows = typedRows( + await c.db.execute( + "SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY created_at ASC, request_id ASC, idx ASC" + ) + ); + const [count] = typedRows( + await c.db.execute( + "SELECT COUNT(*) AS count FROM mock_agentic_entries" + ) + ); + send3(websocket, { + type: "history", + totalRows: count?.count ?? rows.length, + entries: rows, + timestamp: Date.now() + }); + return; + } + if (type === "ping") { + send3(websocket, { + type: "pong", + probeId: stringValue(message.probeId, "probeId"), + ...await sleepStatus(), + timestamp: Date.now() + }); + return; + } + if (type === "verify") { + const requestId = stringValue( + message.requestId, + "requestId" + ); + const expectedSeconds = positiveInteger( + message.expectedSeconds, + "expectedSeconds" + ); + send3( + websocket, + await verify(requestId, expectedSeconds) + ); + return; + } + if (type === "infer") { + const requestId = stringValue( + message.requestId, + "requestId" + ); + const seconds = positiveInteger( + message.seconds, + "seconds" + ); + await recordDebugEvent(c, { + name: "inferenceRequested", + connectionId, + requestId, + details: { + seconds + } + }); + const previousInference = activeInference; + const inference = (async () => { + await previousInference?.catch(() => void 0); + await runInference(requestId, seconds); + })(); + activeInference = inference; + await c.keepAwake(inference); + if (activeInference === inference) { + activeInference = void 0; + } + return; + } + throw new Error(`unknown message type: ${type}`); + } catch (error) { + send3(websocket, { + type: "error", + message: error instanceof Error ? error.message : "unknown websocket error", + timestamp: Date.now() + }); + } + } ); - }); - const counterId = `nasty-counter-${opts.phase}`; - await database.execute( - "INSERT INTO fuzz_nasty_counter (id, value) VALUES (?, 0) ON CONFLICT(id) DO UPDATE SET value = 0", - counterId - ); - await transaction(database, async () => { - for (let i = 0; i < 1e4; i += 1) { - await database.execute( - "UPDATE fuzz_nasty_counter SET value = value + 1 WHERE id = ?", - counterId - ); - ops += 1; - } - }); - const counter2 = await queryOne( - database, - "SELECT value FROM fuzz_nasty_counter WHERE id = ?", - counterId - ); - await recordProbe( - database, - opts.phase, - "nasty-script", - "same-row-10k-updates", - 1e4, - counter2?.value ?? -1, - (counter2?.value ?? -1) !== 1e4 - ); - const groupId = `nasty-bulk-${opts.phase}`; - await database.execute("CREATE INDEX IF NOT EXISTS idx_fuzz_nasty_rows_group_n ON fuzz_nasty_rows(group_id, n)"); - await transaction(database, async () => { - for (let i = 0; i < 1e4; i += 1) { - await database.execute( - `INSERT INTO fuzz_nasty_rows (group_id, n, payload) - VALUES (?, ?, ?) - ON CONFLICT(group_id, n) DO UPDATE SET payload = excluded.payload`, - groupId, - i, - payloadFor(opts.seed, opts.phase, 17e4 + i, 64) + websocket.addEventListener("close", async () => { + removeDebugSocket(); + await recordDebugEvent(c, { + name: "webSocketClose", + connectionId + }); + }); + }, + actions: { + verify: async (c, requestId, expectedSeconds) => { + const rows = typedRows( + await c.db.execute( + "SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC", + requestId + ) ); - ops += 1; + return { + requestId, + expectedSeconds, + count: rows.length, + indexes: rows.map((row) => row.idx) + }; + }, + verifyAll: async (c, expectedRequests) => { + if (!Array.isArray(expectedRequests)) { + throw new Error("expectedRequests must be an array"); + } + for (const request of expectedRequests) { + stringValue(request.requestId, "requestId"); + positiveInteger(request.seconds, "seconds"); + } + const rows = typedRows( + await c.db.execute( + "SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY request_id ASC, idx ASC" + ) + ); + return verifyAllRows(rows, expectedRequests); } - await database.execute("DELETE FROM fuzz_nasty_rows WHERE group_id = ? AND n % 2 = 0", groupId); - ops += 1; - }); - await database.execute("DROP INDEX IF EXISTS idx_fuzz_nasty_rows_group_n"); - const remaining = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", - groupId - ); - await recordProbe( - database, - opts.phase, - "nasty-script", - "insert-10k-delete-every-other", - 5e3, - remaining?.count ?? -1, - (remaining?.count ?? -1) !== 5e3 - ); - const indexLeft = await queryOne( - database, - "SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = 'idx_fuzz_nasty_rows_group_n'" - ); - await recordProbe( - database, - opts.phase, - "nasty-script", - "create-drop-index-around-heavy-writes", - 0, - indexLeft?.count ?? -1, - (indexLeft?.count ?? -1) !== 0 - ); - const rollbackGroupId = `nasty-rollback-${opts.phase}`; - const beforeRollback = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", - rollbackGroupId - ); + } +}); + +// src/actors/testing/raw-sqlite-fuzzer.ts +import { actor as actor42 } from "rivetkit"; +import { db as db8 } from "rivetkit/db"; +var ACCOUNT_COUNT = 8; +var ACCOUNT_INITIAL_BALANCE = 1e5; +var DEFAULT_KEY_SPACE = 64; +var DEFAULT_MAX_PAYLOAD_BYTES = 8 * 1024; +var DEFAULT_GROWTH_TARGET_BYTES = 1024 * 1024; +var LARGE_WRITE_CHUNK_BYTES = 96 * 1024; +var PAGE_BOUNDARY_SIZES = [ + 1, + 4095, + 4096, + 4097, + 8191, + 8192, + 8193, + 32768, + 65535, + 65536, + 98304, + 131072 +]; +function hashSeed(input) { + let hash = 2166136261; + for (let i = 0; i < input.length; i += 1) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return hash >>> 0; +} +function makeRng(seed) { + let state = hashSeed(seed) || 2654435769; + return () => { + state = state + 1831565813 >>> 0; + let t = state; + t = Math.imul(t ^ t >>> 15, t | 1); + t ^= t + Math.imul(t ^ t >>> 7, t | 61); + return ((t ^ t >>> 14) >>> 0) / 4294967296; + }; +} +function intBetween(rng, min, max) { + return min + Math.floor(rng() * (max - min + 1)); +} +function checksum(input) { + let hash = 2166136261; + for (let i = 0; i < input.length; i += 1) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return hash >>> 0; +} +function payloadFor(seed, phase, index, bytes) { + const prefix = `${seed}:${phase}:${index}:`; + if (bytes <= prefix.length) return prefix.slice(0, bytes); + return prefix + "x".repeat(bytes - prefix.length); +} +async function queryOne2(database, sql, ...args) { + const rows = await database.execute(sql, ...args); + return rows[0]; +} +async function transaction(database, fn) { await database.execute("BEGIN"); try { - for (let i = 0; i < 1e3; i += 1) { - await database.execute( - "INSERT INTO fuzz_nasty_rows (group_id, n, payload) VALUES (?, ?, ?)", - rollbackGroupId, - i, - "rollback" - ); - ops += 1; - } - await database.execute("ROLLBACK"); + const result = await fn(); + await database.execute("COMMIT"); + return result; } catch (err) { await database.execute("ROLLBACK").catch(() => void 0); throw err; } - const afterRollback = await queryOne( - database, - "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", - rollbackGroupId - ); - await recordProbe( - database, - opts.phase, - "nasty-script", - "rollback-1k-inserts", - beforeRollback?.count ?? 0, - afterRollback?.count ?? -1, - (afterRollback?.count ?? -1) !== (beforeRollback?.count ?? 0) - ); - return ops; -} -function shouldRunDeepScenario(mode2, scenario) { - return mode2 === scenario || mode2 === "kitchen-sink" || mode2 === "nasty"; } -async function applyDeepScenarios(database, opts) { - const runScenario = async (name, fn) => { - try { - return await fn(); - } catch (error) { - const detail = error instanceof Error ? error.message : typeof error === "string" ? error : String(error); - throw new Error( - `deep scenario ${name} failed in mode ${opts.mode} during phase ${opts.phase}: ${detail}`, - { cause: error } - ); - } - }; - if (shouldRunDeepScenario(opts.mode, "edge") || opts.mode === "payloads") { - opts.ops.edgePayload = (opts.ops.edgePayload ?? 0) + await runScenario("edge", () => applyEdgePayloads(database, opts)); - } - if (opts.mode === "actual-nul") { - opts.ops.actualNul = (opts.ops.actualNul ?? 0) + await runScenario("actual-nul", () => applyActualNulPayload(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "fragmentation")) { - opts.ops.fragmentation = (opts.ops.fragmentation ?? 0) + await runScenario("fragmentation", () => applyFragmentationChurn(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "schema")) { - opts.ops.schema = (opts.ops.schema ?? 0) + await runScenario("schema", () => applySchemaChurn(database, opts.phase)); - } - if (shouldRunDeepScenario(opts.mode, "index")) { - opts.ops.index = (opts.ops.index ?? 0) + await runScenario("index", () => applyIndexProbe(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "constraints")) { - opts.ops.constraints = (opts.ops.constraints ?? 0) + await runScenario("constraints", () => applyConstraintChaos(database, opts.phase)); - } - if (shouldRunDeepScenario(opts.mode, "savepoints")) { - opts.ops.savepoints = (opts.ops.savepoints ?? 0) + await runScenario("savepoints", () => applySavepointScenario(database, opts.phase)); - } - if (shouldRunDeepScenario(opts.mode, "pragma")) { - opts.ops.pragma = (opts.ops.pragma ?? 0) + await runScenario("pragma", () => applyPragmaProbe(database, opts.phase)); - } - if (shouldRunDeepScenario(opts.mode, "prepared")) { - opts.ops.prepared = (opts.ops.prepared ?? 0) + await runScenario("prepared", () => applyPreparedChurn(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "growth")) { - opts.ops.growth = (opts.ops.growth ?? 0) + await runScenario("growth", () => applyGrowthProbe(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "readwrite")) { - opts.ops.readwrite = (opts.ops.readwrite ?? 0) + await runScenario("readwrite", () => applyReadWriteProbe(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "truncate")) { - opts.ops.truncate = (opts.ops.truncate ?? 0) + await runScenario("truncate", () => applyTruncateRecreateProbe(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "boundary-keys")) { - opts.ops.boundaryKeys = (opts.ops.boundaryKeys ?? 0) + await runScenario("boundary-keys", () => applyBoundaryKeys(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "relational")) { - const orders = Math.max(1, Math.floor(opts.iterations / 20)); - for (let i = 0; i < orders; i += 1) { - opts.ops.relational = (opts.ops.relational ?? 0) + await runScenario( - "relational", - () => applyRelationalOrder(database, { - phase: opts.phase, - localIndex: i, - rng: opts.rng - }) - ); - } - } - if (opts.mode === "kitchen-sink" || opts.mode === "nasty") { - opts.ops.idempotent = (opts.ops.idempotent ?? 0) + await runScenario("idempotent", () => applyIdempotentReplay(database, opts.phase)); - opts.ops.nasty = (opts.ops.nasty ?? 0) + await runScenario( - "nasty", - () => applyNastyScript(database, { ...opts, intense: opts.mode === "nasty" }) - ); - } - if (opts.mode === "nasty-script") { - opts.ops.nasty = (opts.ops.nasty ?? 0) + await runScenario("nasty-script", () => applyDeterministicNastyScript(database, opts)); - } - if (shouldRunDeepScenario(opts.mode, "shadow")) { - opts.ops.shadow = (opts.ops.shadow ?? 0) + await runScenario("shadow", () => updateShadowChecksums(database, opts.phase)); - } +async function recordProbe(database, phase, scenario, name, expected, actual, mismatch) { + await database.execute( + `INSERT INTO fuzz_probe_results ( + phase, scenario, name, expected, actual, mismatch, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?)`, + phase, + scenario, + name, + String(expected), + String(actual), + mismatch ? 1 : 0, + Date.now() + ); } -function chooseKind(mode2, rng) { - const roll = rng(); - if (mode2 === "transactions") { - if (roll < 0.55) return "transfer"; - if (roll < 0.75) return "upsert"; - if (roll < 0.9) return "update"; - return "delete"; - } - if (mode2 === "hot") { - if (roll < 0.6) return "hot"; - if (roll < 0.75) return "upsert"; - if (roll < 0.9) return "update"; - return "delete"; - } - if (mode2 === "payloads") { - if (roll < 0.4) return "upsert"; - if (roll < 0.7) return "insert"; - if (roll < 0.9) return "update"; - return "delete"; +function firstColumn(row) { + if (!row || typeof row !== "object") return void 0; + const values = Object.values(row); + return values[0]; +} +async function ensureAccounts(database) { + await database.execute("BEGIN"); + try { + for (let i = 0; i < ACCOUNT_COUNT; i += 1) { + await database.execute( + "INSERT OR IGNORE INTO fuzz_accounts (id, balance) VALUES (?, ?)", + `acct-${i}`, + ACCOUNT_INITIAL_BALANCE + ); + } + await database.execute("COMMIT"); + } catch (err) { + await database.execute("ROLLBACK").catch(() => void 0); + throw err; } - if (roll < 0.2) return "insert"; - if (roll < 0.45) return "update"; - if (roll < 0.65) return "delete"; - if (roll < 0.85) return "upsert"; - if (roll < 0.95) return "hot"; - return "transfer"; } -async function validate(database) { - const integrity = await queryOne( - database, - "PRAGMA integrity_check" - ); - const quick = await queryOne( - database, - "PRAGMA quick_check" - ); - const totals = await queryOne( - database, - `WITH latest AS ( - SELECT e.* - FROM fuzz_item_events e - JOIN ( - SELECT item_key, MAX(seq) AS seq - FROM fuzz_item_events - GROUP BY item_key - ) m ON m.item_key = e.item_key AND m.seq = e.seq - ) - SELECT - (SELECT COUNT(*) FROM fuzz_item_events) AS total_events, - (SELECT COUNT(*) FROM fuzz_items) AS active_rows, - (SELECT COUNT(*) FROM latest WHERE present = 1) AS expected_rows, - (SELECT COALESCE(SUM(version), 0) FROM fuzz_items) AS actual_version_sum, - (SELECT COALESCE(SUM(version), 0) FROM latest WHERE present = 1) AS expected_version_sum, - (SELECT COALESCE(SUM(payload_checksum), 0) FROM fuzz_items) AS actual_payload_checksum_sum, - (SELECT COALESCE(SUM(payload_checksum), 0) FROM latest WHERE present = 1) AS expected_payload_checksum_sum` - ); - const mismatches = await queryOne( - database, - `WITH latest AS ( - SELECT e.* - FROM fuzz_item_events e - JOIN ( - SELECT item_key, MAX(seq) AS seq - FROM fuzz_item_events - GROUP BY item_key - ) m ON m.item_key = e.item_key AND m.seq = e.seq - ) - SELECT - ( - SELECT COUNT(*) - FROM latest l - LEFT JOIN fuzz_items i ON i.item_key = l.item_key - WHERE l.present = 1 AND i.item_key IS NULL - ) AS missing_rows, - ( - SELECT COUNT(*) - FROM fuzz_items i - LEFT JOIN latest l ON l.item_key = i.item_key - WHERE l.item_key IS NULL OR l.present = 0 - ) AS extra_rows, - ( - SELECT COUNT(*) - FROM latest l - JOIN fuzz_items i ON i.item_key = l.item_key - WHERE l.present = 1 - AND ( - i.value != l.value OR - i.version != l.version OR - i.update_count != l.update_count OR - i.payload_checksum != l.payload_checksum OR - i.payload_bytes != l.payload_bytes - ) - ) AS mismatched_rows, - ( - SELECT COUNT(*) - FROM ( - SELECT item_key - FROM fuzz_items - GROUP BY item_key - HAVING COUNT(*) > 1 - ) - ) AS duplicate_keys` - ); - const accounts = await queryOne( - database, - `SELECT - COUNT(*) AS account_count, - COALESCE(SUM(balance), 0) AS account_balance_sum, - ( - SELECT COUNT(*) - FROM fuzz_transfer_events - WHERE balance_sum_before != ? OR balance_sum_after != ? - ) AS account_balance_mismatch - FROM fuzz_accounts`, - ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE, - ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE - ); - const edge = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_edge_payloads) AS edge_rows, - (SELECT COUNT(*) FROM fuzz_edge_expectations WHERE present = 1) AS edge_expected_rows, - ( - SELECT COUNT(*) - FROM fuzz_edge_expectations e - LEFT JOIN fuzz_edge_payloads p ON p.id = e.id - WHERE - (e.present = 1 AND p.id IS NULL) OR - (e.present = 0 AND p.id IS NOT NULL) OR - (e.present = 1 AND ( - p.payload_checksum != e.payload_checksum OR - p.payload_bytes != e.payload_bytes - )) - ) AS edge_mismatches` - ); - const indexProbe = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_indexed) AS index_rows, - ( - SELECT COUNT(*) - FROM ( - SELECT id FROM fuzz_indexed - WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 - EXCEPT - SELECT id FROM fuzz_indexed NOT INDEXED - WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 - ) - ) + ( - SELECT COUNT(*) - FROM ( - SELECT id FROM fuzz_indexed NOT INDEXED - WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 - EXCEPT - SELECT id FROM fuzz_indexed - WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 - ) - ) AS index_mismatches` - ); - const relational = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_orders) AS relational_orders, - ( - SELECT COUNT(*) - FROM fuzz_orders o - LEFT JOIN ( - SELECT order_id, COALESCE(SUM(quantity * price), 0) AS item_total - FROM fuzz_order_items - GROUP BY order_id - ) i ON i.order_id = o.id - WHERE o.total != COALESCE(i.item_total, 0) - ) + ( - SELECT COUNT(*) - FROM fuzz_orders o - LEFT JOIN ( - SELECT order_id, COALESCE(SUM(amount), 0) AS payment_total - FROM fuzz_payments - WHERE status = 'captured' - GROUP BY order_id - ) p ON p.order_id = o.id - WHERE o.status = 'paid' AND o.total != COALESCE(p.payment_total, 0) - ) + ( - SELECT COUNT(*) - FROM fuzz_inventory - WHERE initial_qty != sold_qty + stock_qty OR stock_qty < 0 - ) AS relational_mismatches` - ); - const constraints = await queryOne( - database, - `SELECT - COUNT(*) AS constraint_attempts, - COALESCE(SUM( - CASE - WHEN expected_failed = 1 AND (actually_failed != 1 OR before_count != after_count) THEN 1 - WHEN expected_failed = 0 AND after_count != before_count + 1 THEN 1 - ELSE 0 - END - ), 0) AS constraint_leaks - FROM fuzz_constraint_attempts` - ); - const savepoints = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_savepoints) AS savepoint_rows, - ( - SELECT COUNT(*) - FROM fuzz_savepoint_expectations e - LEFT JOIN fuzz_savepoints s ON s.id = e.id - WHERE - (e.present = 1 AND s.id IS NULL) OR - (e.present = 0 AND s.id IS NOT NULL) OR - (e.present = 1 AND s.value != e.value) - ) AS savepoint_mismatches` - ); - const idempotency = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_idempotent_ops) AS idempotent_ops, - ( - SELECT COUNT(*) - FROM fuzz_idempotent_targets t - LEFT JOIN ( - SELECT target_id, COALESCE(SUM(amount), 0) AS expected - FROM fuzz_idempotent_ops - GROUP BY target_id - ) o ON o.target_id = t.id - WHERE t.value != COALESCE(o.expected, 0) - ) AS idempotent_mismatches` - ); - const schema = await queryOne( - database, - `SELECT - COUNT(*) AS schema_objects, - COALESCE(SUM(CASE WHEN m.name IS NULL THEN 1 ELSE 0 END), 0) AS schema_missing_objects - FROM fuzz_schema_registry r - LEFT JOIN sqlite_master m ON m.name = r.name AND m.type = r.type` - ); - const probes = await queryOne( - database, - `SELECT - COUNT(*) AS probe_rows, - COALESCE(SUM(mismatch), 0) AS probe_mismatches - FROM fuzz_probe_results` - ); - const prepared = await queryOne( - database, - `SELECT - (SELECT COUNT(*) FROM fuzz_prepared_churn) AS prepared_rows, - ( - SELECT COUNT(*) - FROM fuzz_prepared_expectations e - LEFT JOIN fuzz_prepared_churn p ON p.id = e.id - WHERE - p.id IS NULL OR - p.value != e.value OR - p.payload_checksum != e.payload_checksum - ) AS prepared_mismatches` +async function recordItemEvent(database, phase, localIndex, kind, itemKey, present, value, version, updateCount, payload2, applied) { + await database.execute( + `INSERT INTO fuzz_item_events ( + phase, local_index, kind, item_key, present, value, version, + update_count, payload_checksum, payload_bytes, applied, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + phase, + localIndex, + kind, + itemKey, + present ? 1 : 0, + value, + version, + updateCount, + checksum(payload2), + payload2.length, + applied ? 1 : 0, + Date.now() ); - const shadow = await queryOne( - database, - `WITH recomputed AS ( - SELECT 'items' AS name, - COUNT(*) AS row_count, - COALESCE(SUM(payload_checksum + version + update_count), 0) AS value - FROM fuzz_items - UNION ALL - SELECT 'edge' AS name, - COUNT(*) AS row_count, - COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value - FROM fuzz_edge_payloads - ) - SELECT - (SELECT COUNT(*) FROM fuzz_shadow_checksums) AS shadow_rows, - ( - SELECT COUNT(*) - FROM fuzz_shadow_checksums s - JOIN recomputed r ON r.name = s.name - WHERE s.value != r.value OR s.row_count != r.row_count - ) AS shadow_mismatches` +} +async function upsertLiveItem(database, row, payload2) { + await database.execute( + `INSERT INTO fuzz_items ( + item_key, value, version, update_count, payload, payload_checksum, + payload_bytes, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(item_key) DO UPDATE SET + value = excluded.value, + version = excluded.version, + update_count = excluded.update_count, + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + row.item_key, + row.value, + row.version, + row.update_count, + payload2, + row.payload_checksum, + row.payload_bytes, + Date.now() ); - const summary = { - totalEvents: totals?.total_events ?? 0, - activeRows: totals?.active_rows ?? 0, - expectedRows: totals?.expected_rows ?? 0, - missingRows: mismatches?.missing_rows ?? 0, - extraRows: mismatches?.extra_rows ?? 0, - mismatchedRows: mismatches?.mismatched_rows ?? 0, - duplicateKeys: mismatches?.duplicate_keys ?? 0, - actualVersionSum: totals?.actual_version_sum ?? 0, - expectedVersionSum: totals?.expected_version_sum ?? 0, - actualPayloadChecksumSum: totals?.actual_payload_checksum_sum ?? 0, - expectedPayloadChecksumSum: totals?.expected_payload_checksum_sum ?? 0, - accountCount: accounts?.account_count ?? 0, - accountBalanceSum: accounts?.account_balance_sum ?? 0, - expectedAccountBalanceSum: ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE, - accountBalanceMismatch: accounts?.account_balance_mismatch ?? 0, - integrityCheck: integrity?.integrity_check ?? "missing", - quickCheck: quick?.quick_check ?? "missing", - edgeRows: edge?.edge_rows ?? 0, - edgeExpectedRows: edge?.edge_expected_rows ?? 0, - edgeMismatches: edge?.edge_mismatches ?? 0, - indexRows: indexProbe?.index_rows ?? 0, - indexMismatches: indexProbe?.index_mismatches ?? 0, - relationalOrders: relational?.relational_orders ?? 0, - relationalMismatches: relational?.relational_mismatches ?? 0, - constraintAttempts: constraints?.constraint_attempts ?? 0, - constraintLeaks: constraints?.constraint_leaks ?? 0, - savepointRows: savepoints?.savepoint_rows ?? 0, - savepointMismatches: savepoints?.savepoint_mismatches ?? 0, - idempotentOps: idempotency?.idempotent_ops ?? 0, - idempotentMismatches: idempotency?.idempotent_mismatches ?? 0, - schemaObjects: schema?.schema_objects ?? 0, - schemaMissingObjects: schema?.schema_missing_objects ?? 0, - probeRows: probes?.probe_rows ?? 0, - probeMismatches: probes?.probe_mismatches ?? 0, - preparedRows: prepared?.prepared_rows ?? 0, - preparedMismatches: prepared?.prepared_mismatches ?? 0, - shadowRows: shadow?.shadow_rows ?? 0, - shadowMismatches: shadow?.shadow_mismatches ?? 0 +} +async function applyItemOperation(database, opts) { + let current; + try { + current = await queryOne2( + database, + "SELECT item_key, value, version, update_count, payload, payload_checksum, payload_bytes FROM fuzz_items WHERE item_key = ?", + opts.itemKey + ); + } catch (error) { + throw new Error( + `item operation select failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, + { cause: error } + ); + } + const payload2 = payloadFor( + opts.seed, + opts.phase, + opts.localIndex, + opts.payloadBytes + ); + const nextVersion = (current?.version ?? 0) + 1; + const nextUpdateCount = (current?.update_count ?? 0) + 1; + const nextValue = `${opts.kind}:${opts.phase}:${opts.localIndex}:${nextVersion}`; + if (opts.kind === "delete") { + try { + await recordItemEvent( + database, + opts.phase, + opts.localIndex, + opts.kind, + opts.itemKey, + false, + null, + nextVersion, + nextUpdateCount, + "", + current !== void 0 + ); + } catch (error) { + throw new Error( + `item operation event insert failed for delete key ${JSON.stringify(opts.itemKey)}`, + { + cause: error + } + ); + } + try { + await database.execute( + "DELETE FROM fuzz_items WHERE item_key = ?", + opts.itemKey + ); + } catch (error) { + throw new Error( + `item operation delete failed for key ${JSON.stringify(opts.itemKey)}`, + { + cause: error + } + ); + } + return; + } + if (opts.kind === "insert" && current) { + try { + await recordItemEvent( + database, + opts.phase, + opts.localIndex, + opts.kind, + opts.itemKey, + true, + current.value, + current.version, + current.update_count, + current.payload ?? "", + false + ); + } catch (error) { + throw new Error( + `item operation event insert failed for noop insert key ${JSON.stringify(opts.itemKey)}`, + { cause: error } + ); + } + return; + } + const row = { + item_key: opts.itemKey, + value: nextValue, + version: nextVersion, + update_count: nextUpdateCount, + payload_checksum: checksum(payload2), + payload_bytes: payload2.length + }; + try { + await recordItemEvent( + database, + opts.phase, + opts.localIndex, + opts.kind, + opts.itemKey, + true, + row.value, + row.version, + row.update_count, + payload2, + true + ); + } catch (error) { + throw new Error( + `item operation event insert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, + { cause: error } + ); + } + try { + await upsertLiveItem(database, row, payload2); + } catch (error) { + throw new Error( + `item operation live-row upsert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`, + { cause: error } + ); + } +} +async function applyHotUpdates(database, opts) { + for (let i = 0; i < opts.updates; i += 1) { + try { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: opts.localIndex * 1e3 + i, + kind: "update", + itemKey: opts.itemKey, + payloadBytes: opts.payloadBytes + }); + } catch (error) { + throw new Error( + `hot update failed for ${opts.itemKey} at sub-update ${i + 1}/${opts.updates} with payloadBytes ${opts.payloadBytes}`, + { cause: error } + ); + } + } +} +async function applyTransfer(database, opts) { + await transaction(database, async () => { + const before = await queryOne2( + database, + "SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts" + ); + await database.execute( + "UPDATE fuzz_accounts SET balance = balance - ? WHERE id = ?", + opts.amount, + opts.fromAccount + ); + await database.execute( + "UPDATE fuzz_accounts SET balance = balance + ? WHERE id = ?", + opts.amount, + opts.toAccount + ); + const after = await queryOne2( + database, + "SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts" + ); + await database.execute( + `INSERT INTO fuzz_transfer_events ( + phase, local_index, from_account, to_account, amount, + balance_sum_before, balance_sum_after, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + opts.phase, + opts.localIndex, + opts.fromAccount, + opts.toAccount, + opts.amount, + before?.total ?? 0, + after?.total ?? 0, + Date.now() + ); + }); +} +async function applyEdgePayloads(database, opts) { + const writeEdgePayload = async (id, kind, payload2, sizeLabel) => { + const payloadChecksum = checksum(payload2); + const payloadBytes = payload2.length; + try { + await database.execute("BEGIN"); + } catch (error) { + throw new Error(`edge payload begin failed for ${sizeLabel}`, { + cause: error + }); + } + try { + try { + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + kind = excluded.kind, + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + id, + kind, + payload2, + payloadChecksum, + payloadBytes, + Date.now() + ); + } catch (error) { + throw new Error( + `edge payload row upsert failed for ${sizeLabel}`, + { + cause: error + } + ); + } + try { + await database.execute( + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = excluded.present, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + id, + payloadChecksum, + payloadBytes + ); + } catch (error) { + throw new Error( + `edge payload expectation upsert failed for ${sizeLabel}`, + { + cause: error + } + ); + } + try { + await database.execute("COMMIT"); + } catch (error) { + throw new Error(`edge payload commit failed for ${sizeLabel}`, { + cause: error + }); + } + } catch (error) { + await database.execute("ROLLBACK").catch(() => void 0); + throw error; + } }; - return summary; -} -async function debugItemMismatches(database, limit = 5) { - const rows = await database.execute( - `WITH latest AS ( - SELECT e.* - FROM fuzz_item_events e - JOIN ( - SELECT item_key, MAX(seq) AS seq - FROM fuzz_item_events - GROUP BY item_key - ) m ON m.item_key = e.item_key AND m.seq = e.seq - ) - SELECT - COALESCE(l.item_key, i.item_key) AS item_key, - i.value AS actual_value, - l.value AS expected_value, - i.version AS actual_version, - l.version AS expected_version, - i.update_count AS actual_update_count, - l.update_count AS expected_update_count, - i.payload_checksum AS actual_payload_checksum, - l.payload_checksum AS expected_payload_checksum, - i.payload_bytes AS actual_payload_bytes, - l.payload_bytes AS expected_payload_bytes - FROM latest l - FULL OUTER JOIN fuzz_items i ON i.item_key = l.item_key - WHERE - (l.present = 1 AND i.item_key IS NULL) OR - ((l.item_key IS NULL OR l.present = 0) AND i.item_key IS NOT NULL) OR - ( - l.present = 1 AND i.item_key IS NOT NULL AND ( - i.value != l.value OR - i.version != l.version OR - i.update_count != l.update_count OR - i.payload_checksum != l.payload_checksum OR - i.payload_bytes != l.payload_bytes - ) - ) - ORDER BY COALESCE(l.item_key, i.item_key) - LIMIT ?`, - limit + const sizes = PAGE_BOUNDARY_SIZES.filter( + (size) => size <= opts.maxPayloadBytes ); - const itemMismatches = rows.map((row) => ({ - itemKey: row.item_key, - actualValue: row.actual_value, - expectedValue: row.expected_value, - actualVersion: row.actual_version, - expectedVersion: row.expected_version, - actualUpdateCount: row.actual_update_count, - expectedUpdateCount: row.expected_update_count, - actualPayloadChecksum: row.actual_payload_checksum, - expectedPayloadChecksum: row.expected_payload_checksum, - actualPayloadBytes: row.actual_payload_bytes, - expectedPayloadBytes: row.expected_payload_bytes - })); - const recentEventsByKey = {}; - for (const row of itemMismatches) { - const events = await database.execute( - `SELECT - seq, - phase, - local_index, - kind, - present, - value, - version, - update_count, - payload_checksum, - payload_bytes, - applied - FROM fuzz_item_events - WHERE item_key = ? - ORDER BY seq DESC - LIMIT 10`, - row.itemKey + if (!sizes.includes(opts.maxPayloadBytes)) sizes.push(opts.maxPayloadBytes); + let ops = 0; + for (const size of sizes) { + const id = `edge-${opts.phase}-${size}`; + const payload2 = payloadFor(opts.seed, opts.phase, size, size); + try { + await writeEdgePayload(id, "boundary", payload2, `size ${size}`); + } catch (error) { + throw new Error(`edge payload write failed for size ${size}`, { + cause: error + }); + } + ops += 1; + } + const unicodePayload = `escaped-nul:\\0 unicode:\u2603\uFE0F phase:${opts.phase} seed:${opts.seed}`; + const unicodeId = `edge-${opts.phase}-unicode-nul`; + try { + await writeEdgePayload( + unicodeId, + "unicode-nul", + unicodePayload, + "unicode escaped-nul payload" + ); + } catch (error) { + throw new Error( + "edge payload write failed for unicode escaped-nul payload", + { + cause: error + } ); - recentEventsByKey[row.itemKey] = events.map((event21) => ({ - seq: event21.seq, - phase: event21.phase, - localIndex: event21.local_index, - kind: event21.kind, - present: event21.present, - value: event21.value, - version: event21.version, - updateCount: event21.update_count, - payloadChecksum: event21.payload_checksum, - payloadBytes: event21.payload_bytes, - applied: event21.applied - })); } - return { itemMismatches, recentEventsByKey }; + return ops + 1; } -var rawSqliteFuzzer = actor52({ - options: { - actionTimeout: 3e5 - }, - db: db9({ - onMigrate: async (database) => { - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_items ( - item_key TEXT PRIMARY KEY, - value TEXT NOT NULL, - version INTEGER NOT NULL, - update_count INTEGER NOT NULL, - payload TEXT NOT NULL, - payload_checksum INTEGER NOT NULL, - payload_bytes INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_item_events ( - seq INTEGER PRIMARY KEY AUTOINCREMENT, - phase INTEGER NOT NULL, - local_index INTEGER NOT NULL, - kind TEXT NOT NULL, - item_key TEXT NOT NULL, - present INTEGER NOT NULL, - value TEXT, - version INTEGER NOT NULL, - update_count INTEGER NOT NULL, - payload_checksum INTEGER NOT NULL, - payload_bytes INTEGER NOT NULL, - applied INTEGER NOT NULL, - created_at INTEGER NOT NULL - ) - `); +async function applyActualNulPayload(database, opts) { + const payload2 = `actual-nul:\0 phase:${opts.phase} seed:${opts.seed}`; + const id = `actual-nul-${opts.phase}`; + await transaction(database, async () => { + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'actual-nul', ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + id, + payload2, + checksum(payload2), + payload2.length, + Date.now() + ); + await database.execute( + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = 1, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + id, + checksum(payload2), + payload2.length + ); + }); + return 1; +} +async function applyFragmentationChurn(database, opts) { + const rows = Math.max(12, Math.floor(opts.iterations / 2)); + let ops = 0; + for (let i = 0; i < rows; i += 1) { + const size = intBetween( + opts.rng, + 32, + Math.max(32, opts.maxPayloadBytes) + ); + const id = `frag-${opts.phase}-${i}`; + const payload2 = payloadFor(opts.seed, opts.phase, 1e4 + i, size); + try { + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'fragment', ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + id, + payload2, + checksum(payload2), + payload2.length, + Date.now() + ); + } catch (error) { + throw new Error( + `fragmentation payload upsert failed for ${id} at size ${size}`, + { + cause: error + } + ); + } + try { await database.execute( - "CREATE INDEX IF NOT EXISTS idx_fuzz_item_events_key_seq ON fuzz_item_events(item_key, seq)" + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = 1, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + id, + checksum(payload2), + payload2.length ); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_accounts ( - id TEXT PRIMARY KEY, - balance INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_transfer_events ( - seq INTEGER PRIMARY KEY AUTOINCREMENT, - phase INTEGER NOT NULL, - local_index INTEGER NOT NULL, - from_account TEXT NOT NULL, - to_account TEXT NOT NULL, - amount INTEGER NOT NULL, - balance_sum_before INTEGER NOT NULL, - balance_sum_after INTEGER NOT NULL, - created_at INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_edge_payloads ( - id TEXT PRIMARY KEY, - kind TEXT NOT NULL, - payload TEXT NOT NULL, - payload_checksum INTEGER NOT NULL, - payload_bytes INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_edge_expectations ( - id TEXT PRIMARY KEY, - present INTEGER NOT NULL, - payload_checksum INTEGER NOT NULL, - payload_bytes INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_trigger_audit ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - payload_id TEXT NOT NULL, - old_checksum INTEGER NOT NULL, - new_checksum INTEGER NOT NULL, - created_at INTEGER NOT NULL - ) - `); + } catch (error) { + throw new Error( + `fragmentation expectation upsert failed for ${id} at size ${size}`, + { + cause: error + } + ); + } + ops += 1; + } + for (let i = 0; i < rows; i += 3) { + const id = `frag-${opts.phase}-${i}`; + try { await database.execute( - "CREATE INDEX IF NOT EXISTS idx_fuzz_edge_kind_size ON fuzz_edge_payloads(kind, payload_bytes)" + "DELETE FROM fuzz_edge_payloads WHERE id = ?", + id ); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_indexed ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - tenant TEXT NOT NULL, - bucket INTEGER NOT NULL, - score INTEGER NOT NULL, - label TEXT NOT NULL, - payload TEXT NOT NULL - ) - `); + } catch (error) { + throw new Error(`fragmentation delete failed for ${id}`, { + cause: error + }); + } + try { await database.execute( - "CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_tenant_bucket_score ON fuzz_indexed(tenant, bucket, score)" + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 0, 0, 0) + ON CONFLICT(id) DO UPDATE SET + present = 0, + payload_checksum = 0, + payload_bytes = 0`, + id + ); + } catch (error) { + throw new Error( + `fragmentation tombstone expectation failed for ${id}`, + { + cause: error + } + ); + } + ops += 1; + } + for (let i = 1; i < rows; i += 4) { + const size = intBetween(opts.rng, 1, Math.max(1, opts.maxPayloadBytes)); + const id = `frag-${opts.phase}-${i}`; + const payload2 = payloadFor(opts.seed, opts.phase, 2e4 + i, size); + try { + await database.execute( + `UPDATE fuzz_edge_payloads + SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? + WHERE id = ?`, + payload2, + checksum(payload2), + payload2.length, + Date.now(), + id + ); + } catch (error) { + throw new Error( + `fragmentation payload rewrite failed for ${id} at size ${size}`, + { + cause: error + } + ); + } + try { + await database.execute( + `UPDATE fuzz_edge_expectations + SET payload_checksum = ?, payload_bytes = ? + WHERE id = ? AND present = 1`, + checksum(payload2), + payload2.length, + id + ); + } catch (error) { + throw new Error( + `fragmentation expectation rewrite failed for ${id} at size ${size}`, + { + cause: error + } + ); + } + ops += 1; + } + if (opts.phase % 2 === 1) { + try { + await database.execute("VACUUM"); + } catch (error) { + throw new Error( + `fragmentation vacuum failed for phase ${opts.phase}`, + { + cause: error + } ); + } + ops += 1; + } + return ops; +} +async function applySchemaChurn(database, phase) { + const table = `fuzz_schema_phase_${phase}`; + const index = `idx_fuzz_schema_phase_${phase}_name`; + const view = `view_fuzz_schema_phase_${phase}`; + const dropIndex = `idx_fuzz_schema_drop_probe_${phase}`; + await database.execute( + `CREATE TABLE IF NOT EXISTS ${table} ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + value INTEGER NOT NULL DEFAULT 0, + extra TEXT + )` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS ${index} ON ${table}(name, value)` + ); + try { + await database.execute( + `ALTER TABLE ${table} ADD COLUMN altered_${phase} TEXT DEFAULT 'altered'` + ); + } catch { + const column = await queryOne2( + database, + `SELECT COUNT(*) AS count FROM pragma_table_info('${table}') WHERE name = ?`, + `altered_${phase}` + ); + if ((column?.count ?? 0) !== 1) + throw new Error(`failed to add altered_${phase}`); + } + await database.execute( + `CREATE VIEW IF NOT EXISTS ${view} AS SELECT id, name, value FROM ${table}` + ); + await database.execute( + `INSERT INTO ${table} (name, value, extra) + VALUES (?, ?, ?) + ON CONFLICT(name) DO UPDATE SET + value = excluded.value, + extra = excluded.extra`, + `schema-${phase}`, + phase, + `extra-${phase}` + ); + await database.execute( + `CREATE TABLE IF NOT EXISTS fuzz_without_rowid ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL + ) WITHOUT ROWID` + ); + await database.execute(` + CREATE TRIGGER IF NOT EXISTS trg_fuzz_edge_payload_update + AFTER UPDATE ON fuzz_edge_payloads + BEGIN + INSERT INTO fuzz_trigger_audit ( + payload_id, old_checksum, new_checksum, created_at + ) VALUES ( + new.id, old.payload_checksum, new.payload_checksum, strftime('%s', 'now') * 1000 + ); + END + `); + await database.execute( + `INSERT INTO fuzz_without_rowid (id, value) + VALUES (?, ?) + ON CONFLICT(id) DO UPDATE SET value = excluded.value`, + `phase-${phase}`, + phase + ); + for (const [name, type] of [ + [table, "table"], + [index, "index"], + [view, "view"], + ["trg_fuzz_edge_payload_update", "trigger"], + ["fuzz_without_rowid", "table"] + ]) { + await database.execute( + `INSERT INTO fuzz_schema_registry (name, type) + VALUES (?, ?) + ON CONFLICT(name) DO UPDATE SET type = excluded.type`, + name, + type + ); + } + await database.execute( + "CREATE TEMP TABLE IF NOT EXISTS fuzz_temp_probe (id INTEGER PRIMARY KEY, value TEXT)" + ); + await database.execute( + "INSERT INTO fuzz_temp_probe (value) VALUES (?)", + `temp-${phase}` + ); + await database.execute("DROP TABLE fuzz_temp_probe"); + await database.execute( + `CREATE INDEX IF NOT EXISTS ${dropIndex} ON fuzz_schema_registry(type)` + ); + await database.execute(`DROP INDEX IF EXISTS ${dropIndex}`); + const dropped = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = ?", + dropIndex + ); + await recordProbe( + database, + phase, + "schema", + "drop-index", + 0, + dropped?.count ?? -1, + (dropped?.count ?? -1) !== 0 + ); + return 13; +} +async function applyIndexProbe(database, opts) { + const rows = Math.max(20, opts.iterations); + await transaction(database, async () => { + for (let i = 0; i < rows; i += 1) { + const tenant = `tenant-${intBetween(opts.rng, 0, 5)}`; + const bucket = intBetween(opts.rng, 0, 12); + const score = intBetween(opts.rng, -500, 500); + const label = `${opts.seed}:${opts.phase}:${i}`; await database.execute( - "CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_score_label ON fuzz_indexed(score, label)" + `INSERT INTO fuzz_indexed (tenant, bucket, score, label, payload) + VALUES (?, ?, ?, ?, ?)`, + tenant, + bucket, + score, + label, + payloadFor( + opts.seed, + opts.phase, + 3e4 + i, + intBetween(opts.rng, 8, 256) + ) + ); + } + }); + return rows; +} +async function applyPreparedChurn(database, opts) { + const rows = Math.max(32, opts.iterations); + for (let i = 0; i < rows; i += 1) { + const id = `prep-${opts.phase}-${i}`; + const payload2 = payloadFor( + opts.seed, + opts.phase, + 7e4 + i, + Math.min(opts.maxPayloadBytes, 64 + i % 257) + ); + await database.execute( + `INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum) + VALUES (?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + value = excluded.value, + payload = excluded.payload, + payload_checksum = excluded.payload_checksum + /* unique-prepared-${opts.phase}-${i} */`, + id, + i, + payload2, + checksum(payload2) + ); + await database.execute( + `INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum) + VALUES (?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + value = excluded.value, + payload_checksum = excluded.payload_checksum`, + id, + i, + checksum(payload2) + ); + } + const repeatedId = `prep-repeat-${opts.phase}`; + await database.execute( + `INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum) + VALUES (?, 0, '', 0) + ON CONFLICT(id) DO UPDATE SET value = 0, payload = '', payload_checksum = 0`, + repeatedId + ); + for (let i = 0; i < rows; i += 1) { + const payload2 = payloadFor( + opts.seed, + opts.phase, + 8e4 + i, + Math.min(512, opts.maxPayloadBytes) + ); + await database.execute( + `UPDATE fuzz_prepared_churn + SET value = value + ?, payload = ?, payload_checksum = ? + WHERE id = ?`, + 1, + payload2, + checksum(payload2), + repeatedId + ); + } + const finalPayload = payloadFor( + opts.seed, + opts.phase, + 8e4 + rows - 1, + Math.min(512, opts.maxPayloadBytes) + ); + await database.execute( + `INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum) + VALUES (?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + value = excluded.value, + payload_checksum = excluded.payload_checksum`, + repeatedId, + rows, + checksum(finalPayload) + ); + return rows * 2 + 1; +} +async function applyReadWriteProbe(database, opts) { + if ((await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_indexed" + ))?.count === 0) { + await applyIndexProbe(database, opts); + } + const read = database.execute( + `SELECT + COUNT(*) AS joined_rows, + COALESCE(SUM(a.score + b.score), 0) AS score_sum + FROM fuzz_indexed a + JOIN fuzz_indexed b ON b.bucket = a.bucket + WHERE a.tenant <= 'tenant-3'` + ); + const write = applyIndexProbe(database, { + seed: opts.seed, + phase: opts.phase, + rng: opts.rng, + iterations: Math.max(10, Math.floor(opts.iterations / 2)) + }); + const [readRows, writeOps] = await Promise.all([read, write]); + const row = readRows[0]; + const joinedRows = Number(row?.joined_rows ?? -1); + const scoreSum = Number(row?.score_sum ?? Number.NaN); + await recordProbe( + database, + opts.phase, + "readwrite", + "long-read-while-write", + "nonnegative-finite", + `${joinedRows}:${scoreSum}`, + joinedRows < 0 || !Number.isFinite(scoreSum) + ); + return writeOps + 1; +} +async function applyBoundaryKeys(database, opts) { + const keys = [ + "", + " ", + `long-${"k".repeat(2048)}`, + "slash/key", + "comma,key", + "percent%key", + "CaseKey", + "casekey" + ]; + let ops = 0; + for (const [index, key] of keys.entries()) { + try { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 9e4 + index, + kind: "upsert", + itemKey: key, + payloadBytes: Math.min(opts.maxPayloadBytes, 128 + index) + }); + } catch (error) { + throw new Error( + `boundary key write failed for literal key ${JSON.stringify(key)} at index ${index}`, + { cause: error } ); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_rel_users ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_rel_products ( - id TEXT PRIMARY KEY, - price INTEGER NOT NULL CHECK (price >= 0) - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_orders ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - total INTEGER NOT NULL, - status TEXT NOT NULL, - FOREIGN KEY (user_id) REFERENCES fuzz_rel_users(id) - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_order_items ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id TEXT NOT NULL, - product_id TEXT NOT NULL, - quantity INTEGER NOT NULL CHECK (quantity > 0), - price INTEGER NOT NULL CHECK (price >= 0), - FOREIGN KEY (order_id) REFERENCES fuzz_orders(id), - FOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id) - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_payments ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - order_id TEXT NOT NULL, - amount INTEGER NOT NULL, - status TEXT NOT NULL, - FOREIGN KEY (order_id) REFERENCES fuzz_orders(id) - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_inventory ( - product_id TEXT PRIMARY KEY, - initial_qty INTEGER NOT NULL, - sold_qty INTEGER NOT NULL, - stock_qty INTEGER NOT NULL, - FOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id) - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_constraints ( - id TEXT PRIMARY KEY, - must_not_null TEXT NOT NULL, - qty INTEGER NOT NULL CHECK (qty >= 0), - uniq TEXT NOT NULL UNIQUE - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_constraint_attempts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - expected_failed INTEGER NOT NULL, - actually_failed INTEGER NOT NULL, - before_count INTEGER NOT NULL, - after_count INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_fk_parent ( - id TEXT PRIMARY KEY - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_fk_child ( - id TEXT PRIMARY KEY, - parent_id TEXT NOT NULL, - FOREIGN KEY (parent_id) REFERENCES fuzz_fk_parent(id) ON DELETE CASCADE - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_savepoints ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_savepoint_expectations ( - id TEXT PRIMARY KEY, - present INTEGER NOT NULL, - value INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_idempotent_ops ( - op_id TEXT PRIMARY KEY, - target_id TEXT NOT NULL, - amount INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_idempotent_targets ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_schema_registry ( - name TEXT PRIMARY KEY, - type TEXT NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_probe_results ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - phase INTEGER NOT NULL, - scenario TEXT NOT NULL, - name TEXT NOT NULL, - expected TEXT NOT NULL, - actual TEXT NOT NULL, - mismatch INTEGER NOT NULL, - created_at INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_prepared_churn ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL, - payload TEXT NOT NULL, - payload_checksum INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_prepared_expectations ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL, - payload_checksum INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_shadow_checksums ( - name TEXT PRIMARY KEY, - value INTEGER NOT NULL, - row_count INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_nasty_counter ( - id TEXT PRIMARY KEY, - value INTEGER NOT NULL - ) - `); - await database.execute(` - CREATE TABLE IF NOT EXISTS fuzz_nasty_rows ( - group_id TEXT NOT NULL, - n INTEGER NOT NULL, - payload TEXT NOT NULL, - PRIMARY KEY (group_id, n) - ) - `); } - }), - actions: { - reset: async (c) => { - await c.db.execute("DELETE FROM fuzz_nasty_rows"); - await c.db.execute("DELETE FROM fuzz_nasty_counter"); - await c.db.execute("DELETE FROM fuzz_shadow_checksums"); - await c.db.execute("DELETE FROM fuzz_prepared_expectations"); - await c.db.execute("DELETE FROM fuzz_prepared_churn"); - await c.db.execute("DELETE FROM fuzz_probe_results"); - await c.db.execute("DELETE FROM fuzz_schema_registry"); - await c.db.execute("DELETE FROM fuzz_idempotent_targets"); - await c.db.execute("DELETE FROM fuzz_idempotent_ops"); - await c.db.execute("DELETE FROM fuzz_savepoint_expectations"); - await c.db.execute("DELETE FROM fuzz_savepoints"); - await c.db.execute("DELETE FROM fuzz_fk_child"); - await c.db.execute("DELETE FROM fuzz_fk_parent"); - await c.db.execute("DELETE FROM fuzz_constraint_attempts"); - await c.db.execute("DELETE FROM fuzz_constraints"); - await c.db.execute("DELETE FROM fuzz_payments"); - await c.db.execute("DELETE FROM fuzz_order_items"); - await c.db.execute("DELETE FROM fuzz_orders"); - await c.db.execute("DELETE FROM fuzz_inventory"); - await c.db.execute("DELETE FROM fuzz_rel_products"); - await c.db.execute("DELETE FROM fuzz_rel_users"); - await c.db.execute("DELETE FROM fuzz_indexed"); - await c.db.execute("DELETE FROM fuzz_trigger_audit"); - await c.db.execute("DELETE FROM fuzz_edge_expectations"); - await c.db.execute("DELETE FROM fuzz_edge_payloads"); - await c.db.execute("DELETE FROM fuzz_transfer_events"); - await c.db.execute("DELETE FROM fuzz_accounts"); - await c.db.execute("DELETE FROM fuzz_item_events"); - await c.db.execute("DELETE FROM fuzz_items"); - await ensureAccounts(c.db); - return await validate(c.db); - }, - runPhase: async (c, input) => { - const mode2 = input.mode ?? "balanced"; - const iterations = Math.max(1, Math.floor(input.iterations)); - const keySpace = Math.max(1, Math.floor(input.keySpace ?? DEFAULT_KEY_SPACE)); - const maxPayloadBytes = Math.max( - 1, - Math.floor(input.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES) + ops += 1; + } + for (let i = 0; i < 128; i += 1) { + const itemKey = `seq-${opts.phase}-${i.toString().padStart(4, "0")}`; + try { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 91e3 + i, + kind: i % 4 === 0 ? "delete" : "upsert", + itemKey, + payloadBytes: Math.min(opts.maxPayloadBytes, 32 + i % 97) + }); + } catch (error) { + throw new Error( + `boundary key write failed for sequential key ${JSON.stringify(itemKey)} at index ${i}`, + { cause: error } + ); + } + ops += 1; + } + await recordProbe( + database, + opts.phase, + "boundary-keys", + "keys-written", + 136, + ops, + ops !== 136 + ); + return ops; +} +async function applyGrowthProbe(database, opts) { + const chunkBytes = Math.max( + 1, + Math.min(LARGE_WRITE_CHUNK_BYTES, opts.maxPayloadBytes) + ); + const rows = Math.max(1, Math.ceil(opts.growthTargetBytes / chunkBytes)); + let written = 0; + for (let i = 0; i < rows; i += 1) { + const size = Math.min(chunkBytes, opts.growthTargetBytes - written); + const id = `growth-${opts.phase}-${opts.growthTargetBytes}-${i}`; + const payload2 = payloadFor(opts.seed, opts.phase, 1e5 + i, size); + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'growth', ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + id, + payload2, + checksum(payload2), + payload2.length, + Date.now() + ); + await database.execute( + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = 1, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + id, + checksum(payload2), + payload2.length + ); + written += size; + } + await recordProbe( + database, + opts.phase, + "growth", + "target-bytes-written", + opts.growthTargetBytes, + written, + written !== opts.growthTargetBytes + ); + return rows; +} +async function applyTruncateRecreateProbe(database, opts) { + const id = `truncate-${opts.phase}`; + const largeSize = Math.max(1, Math.min(opts.maxPayloadBytes, 131072)); + const largePayload = payloadFor(opts.seed, opts.phase, 11e4, largeSize); + const tinyPayload = payloadFor(opts.seed, opts.phase, 110001, 1); + const recreatedPayload = payloadFor( + opts.seed, + opts.phase, + 110002, + Math.min(4096, largeSize) + ); + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'truncate', ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + id, + largePayload, + checksum(largePayload), + largePayload.length, + Date.now() + ); + await database.execute( + "UPDATE fuzz_edge_payloads SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? WHERE id = ?", + tinyPayload, + checksum(tinyPayload), + tinyPayload.length, + Date.now(), + id + ); + await database.execute("DELETE FROM fuzz_edge_payloads WHERE id = ?", id); + await database.execute("VACUUM"); + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'truncate', ?, ?, ?, ?)`, + id, + recreatedPayload, + checksum(recreatedPayload), + recreatedPayload.length, + Date.now() + ); + await database.execute( + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = 1, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + id, + checksum(recreatedPayload), + recreatedPayload.length + ); + return 5; +} +async function updateShadowChecksums(database, phase) { + const item = await queryOne2( + database, + `SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + version + update_count), 0) AS value + FROM fuzz_items` + ); + const edge = await queryOne2( + database, + `SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value + FROM fuzz_edge_payloads` + ); + await transaction(database, async () => { + for (const [name, rows, value] of [ + ["items", item?.rows ?? 0, item?.value ?? 0], + ["edge", edge?.rows ?? 0, edge?.value ?? 0] + ]) { + await database.execute( + `INSERT INTO fuzz_shadow_checksums (name, value, row_count) + VALUES (?, ?, ?) + ON CONFLICT(name) DO UPDATE SET + value = excluded.value, + row_count = excluded.row_count`, + name, + value, + rows ); - const growthTargetBytes = Math.max( + } + }); + await recordProbe(database, phase, "shadow", "shadow-updated", 2, 2, false); + return 2; +} +async function applyConstraintChaos(database, phase) { + await database.execute("PRAGMA foreign_keys = ON"); + const validPrefix = `valid-${phase}`; + const existingValidRows = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_constraints WHERE id LIKE ?", + `${validPrefix}-%` + ); + const runSeq = existingValidRows?.count ?? 0; + const validId = `${validPrefix}-${runSeq}`; + const uniqValue = `uniq-${phase}-${runSeq}`; + const before = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_constraints" + ); + await database.execute( + `INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) + VALUES (?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + must_not_null = excluded.must_not_null, + qty = excluded.qty, + uniq = excluded.uniq`, + validId, + "ok", + phase, + uniqValue + ); + const attempts = [ + { + name: `not-null-${phase}-${runSeq}`, + sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", + args: [ + `bad-null-${phase}-${runSeq}`, + null, 1, - Math.floor(input.growthTargetBytes ?? DEFAULT_GROWTH_TARGET_BYTES) - ); - const rng = makeRng(`${input.seed}:${input.phase}:${mode2}`); - const ops = {}; - let stage = "ensureAccounts"; - try { - await ensureAccounts(c.db); - for (let i = 0; i < iterations; i += 1) { - const kind = chooseKind(mode2, rng); - ops[kind] = (ops[kind] ?? 0) + 1; - stage = `base:${kind}:iteration:${i}`; - if (kind === "transfer") { - const fromIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1); - let toIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1); - if (toIndex === fromIndex) toIndex = (toIndex + 1) % ACCOUNT_COUNT; - const fromAccount = `acct-${fromIndex}`; - const toAccount = `acct-${toIndex}`; - try { - await applyTransfer(c.db, { - phase: input.phase, - localIndex: i, - fromAccount, - toAccount, - amount: intBetween(rng, 1, 500) - }); - } catch (error) { - throw new Error( - `base operation transfer failed at iteration ${i} from ${fromAccount} to ${toAccount}`, - { cause: error } - ); - } - } else if (kind === "hot") { - const itemKey = `hot-${intBetween(rng, 0, 3)}`; - const updates = intBetween(rng, 2, mode2 === "hot" ? 12 : 5); - try { - await applyHotUpdates(c.db, { - seed: input.seed, - phase: input.phase, - localIndex: i, - itemKey, - updates, - payloadBytes: intBetween(rng, 1, maxPayloadBytes) - }); - } catch (error) { - throw new Error( - `base operation hot failed at iteration ${i} for ${itemKey} with ${updates} updates`, - { cause: error } - ); - } - } else { - const itemKey = mode2 === "hot" && rng() < 0.6 ? `hot-${intBetween(rng, 0, 3)}` : `item-${intBetween(rng, 0, keySpace - 1)}`; - const payloadBytes = mode2 === "payloads" ? intBetween(rng, Math.min(256, maxPayloadBytes), maxPayloadBytes) : intBetween(rng, 1, maxPayloadBytes); - try { - await applyItemOperation(c.db, { - seed: input.seed, - phase: input.phase, - localIndex: i, - kind, - itemKey, - payloadBytes - }); - } catch (error) { - throw new Error( - `base operation ${kind} failed at iteration ${i} for ${JSON.stringify(itemKey)} with payloadBytes ${payloadBytes}`, - { cause: error } - ); - } - } - } - stage = "deep-scenarios"; - await applyDeepScenarios(c.db, { - seed: input.seed, - phase: input.phase, - mode: mode2, - iterations, - rng, - maxPayloadBytes, - growthTargetBytes, - ops - }); - stage = "validate"; - return { - seed: input.seed, - phase: input.phase, - mode: mode2, - iterations, - ops, - validation: await validate(c.db) - }; - } catch (error) { - const detail = error instanceof Error ? error.message : typeof error === "string" ? error : String(error); - throw new Error( - `runPhase failed during ${stage} for mode ${mode2} phase ${input.phase} seed ${input.seed}: ${detail}`, - { cause: error } - ); - } - }, - validate: async (c) => { - await ensureAccounts(c.db); - return await validate(c.db); + `bad-null-${phase}-${runSeq}` + ] }, - debugItemMismatches: async (c, limit) => { - await ensureAccounts(c.db); - return await debugItemMismatches(c.db, limit ?? 5); + { + name: `check-${phase}-${runSeq}`, + sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", + args: [ + `bad-check-${phase}-${runSeq}`, + "ok", + -1, + `bad-check-${phase}-${runSeq}` + ] }, - goToSleep: (c) => { - c.sleep(); - return { ok: true }; + { + name: `unique-${phase}-${runSeq}`, + sql: "INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)", + args: [`bad-unique-${phase}-${runSeq}`, "ok", 1, uniqValue] + } + ]; + for (const attempt of attempts) { + const attemptBefore = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_constraints" + ); + let failed = false; + try { + await database.execute(attempt.sql, ...attempt.args); + } catch { + failed = true; + } + const attemptAfter = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_constraints" + ); + await database.execute( + `INSERT INTO fuzz_constraint_attempts ( + name, expected_failed, actually_failed, before_count, after_count + ) VALUES (?, 1, ?, ?, ?)`, + attempt.name, + failed ? 1 : 0, + attemptBefore?.count ?? 0, + attemptAfter?.count ?? 0 + ); + } + const after = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_constraints" + ); + if ((after?.count ?? 0) !== (before?.count ?? 0) + 1) { + await database.execute( + `INSERT INTO fuzz_constraint_attempts ( + name, expected_failed, actually_failed, before_count, after_count + ) VALUES (?, 0, 0, ?, ?)`, + `valid-count-${phase}-${runSeq}`, + before?.count ?? 0, + after?.count ?? 0 + ); + } + const parentId = `fk-parent-${phase}-${runSeq}`; + const childId = `fk-child-${phase}-${runSeq}`; + await database.execute( + "INSERT INTO fuzz_fk_parent (id) VALUES (?) ON CONFLICT(id) DO NOTHING", + parentId + ); + await database.execute( + `INSERT INTO fuzz_fk_child (id, parent_id) + VALUES (?, ?) + ON CONFLICT(id) DO UPDATE SET parent_id = excluded.parent_id`, + childId, + parentId + ); + const childBeforeDelete = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?", + parentId + ); + await database.execute("DELETE FROM fuzz_fk_parent WHERE id = ?", parentId); + const childAfterDelete = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?", + parentId + ); + await recordProbe( + database, + phase, + "constraints", + "fk-cascade-delete", + 0, + childAfterDelete?.count ?? -1, + (childBeforeDelete?.count ?? 0) !== 1 || (childAfterDelete?.count ?? -1) !== 0 + ); + const fkBefore = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_fk_child" + ); + let fkFailed = false; + try { + await database.execute( + "INSERT INTO fuzz_fk_child (id, parent_id) VALUES (?, ?)", + `fk-orphan-${phase}-${runSeq}`, + `missing-parent-${phase}-${runSeq}` + ); + } catch { + fkFailed = true; + } + const fkAfter = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_fk_child" + ); + await recordProbe( + database, + phase, + "constraints", + "fk-failure-isolation", + `${fkBefore?.count ?? 0}:failed`, + `${fkAfter?.count ?? 0}:${fkFailed ? "failed" : "inserted"}`, + !fkFailed || (fkAfter?.count ?? 0) !== (fkBefore?.count ?? 0) + ); + return attempts.length + 3; +} +async function applyPragmaProbe(database, phase) { + let ops = 0; + for (const [name, setupSql, checkSql, expected] of [ + [ + "journal_mode", + "PRAGMA journal_mode = DELETE", + "PRAGMA journal_mode", + "nonempty" + ], + [ + "synchronous", + "PRAGMA synchronous = NORMAL", + "PRAGMA synchronous", + "nonempty" + ], + [ + "cache_size", + "PRAGMA cache_size = -2000", + "PRAGMA cache_size", + "-2000" + ], + [ + "foreign_keys", + "PRAGMA foreign_keys = ON", + "PRAGMA foreign_keys", + "1" + ], + ["auto_vacuum", "PRAGMA auto_vacuum", "PRAGMA auto_vacuum", "nonempty"] + ]) { + try { + await database.execute(setupSql); + const rows = await database.execute(checkSql); + const actual = String(firstColumn(rows[0]) ?? ""); + await recordProbe( + database, + phase, + "pragma", + name, + expected, + actual, + expected === "nonempty" ? actual.length === 0 : actual !== expected + ); + } catch (err) { + await recordProbe( + database, + phase, + "pragma", + name, + expected, + err instanceof Error ? err.message : "unknown error", + true + ); } + ops += 1; } -}); - -// src/actors/testing/sqlite-memory-pressure.ts -import { actor as actor53 } from "rivetkit"; -import { db as db10 } from "rivetkit/db"; -var DEFAULT_INSERT_ROWS = 128; -var DEFAULT_ROW_BYTES3 = 16 * 1024; -var DEFAULT_SCAN_ROWS = 512; -var INSERT_BATCH_ROWS = 32; -function finiteInt(value, fallback) { - if (value === void 0) return fallback; - if (!Number.isFinite(value) || value < 0) { - throw new Error(`expected a non-negative finite number, got ${value}`); - } - return Math.floor(value); + return ops; } -function copyNativeMetrics(metrics) { - if (!metrics) return null; - const raw = metrics; - const numberField3 = (camel, snake) => Number(raw[camel] ?? raw[snake] ?? 0); - return { - requestBuildNs: numberField3("requestBuildNs", "request_build_ns"), - serializeNs: numberField3("serializeNs", "serialize_ns"), - transportNs: numberField3("transportNs", "transport_ns"), - stateUpdateNs: numberField3("stateUpdateNs", "state_update_ns"), - totalNs: numberField3("totalNs", "total_ns"), - commitCount: numberField3("commitCount", "commit_count"), - pageCacheEntries: numberField3("pageCacheEntries", "page_cache_entries"), - pageCacheWeightedSize: numberField3( - "pageCacheWeightedSize", - "page_cache_weighted_size" - ), - pageCacheCapacityPages: numberField3( - "pageCacheCapacityPages", - "page_cache_capacity_pages" - ), - writeBufferDirtyPages: numberField3( - "writeBufferDirtyPages", - "write_buffer_dirty_pages" - ), - dbSizePages: numberField3("dbSizePages", "db_size_pages") - }; +async function applySavepointScenario(database, phase) { + const keepId = `save-keep-${phase}`; + const rolledBackId = `save-rolled-back-${phase}`; + await database.execute("BEGIN"); + try { + await database.execute( + "INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value", + keepId, + phase + ); + await database.execute("SAVEPOINT sp_rollback_probe"); + await database.execute( + "INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value", + rolledBackId, + 999e3 + phase + ); + await database.execute( + "UPDATE fuzz_savepoints SET value = value + 1000 WHERE id = ?", + keepId + ); + await database.execute("ROLLBACK TO sp_rollback_probe"); + await database.execute("RELEASE sp_rollback_probe"); + await database.execute("COMMIT"); + } catch (err) { + await database.execute("ROLLBACK").catch(() => void 0); + throw err; + } + await database.execute( + `INSERT INTO fuzz_savepoint_expectations (id, present, value) + VALUES (?, 1, ?) + ON CONFLICT(id) DO UPDATE SET present = 1, value = excluded.value`, + keepId, + phase + ); + await database.execute( + `INSERT INTO fuzz_savepoint_expectations (id, present, value) + VALUES (?, 0, 0) + ON CONFLICT(id) DO UPDATE SET present = 0, value = 0`, + rolledBackId + ); + return 5; } -async function queryOne2(database, sql, ...args) { - const rows = await database.execute(sql, ...args); - if (!rows[0]) throw new Error(`query returned no rows: ${sql}`); - return rows[0]; +async function applyIdempotentReplay(database, phase) { + const targetId = `idem-target-${phase % 3}`; + await database.execute( + "INSERT OR IGNORE INTO fuzz_idempotent_targets (id, value) VALUES (?, 0)", + targetId + ); + for (let i = 0; i < 8; i += 1) { + const opId = `idem-${phase}-${i}`; + const amount = phase + i + 1; + for (let attempt = 0; attempt < 3; attempt += 1) { + await transaction(database, async () => { + const existing = await queryOne2( + database, + "SELECT op_id FROM fuzz_idempotent_ops WHERE op_id = ?", + opId + ); + if (!existing) { + await database.execute( + "INSERT INTO fuzz_idempotent_ops (op_id, target_id, amount) VALUES (?, ?, ?)", + opId, + targetId, + amount + ); + await database.execute( + "UPDATE fuzz_idempotent_targets SET value = value + ? WHERE id = ?", + amount, + targetId + ); + } + }); + } + } + return 24; } -async function storageStats(database) { - const [pageCount, freelistCount, pageSize] = await Promise.all([ - queryOne2(database, "PRAGMA page_count"), - queryOne2(database, "PRAGMA freelist_count"), - queryOne2(database, "PRAGMA page_size") - ]); - const nativeMetrics = await database.nativeMetrics?.(); - const copiedMetrics = copyNativeMetrics(nativeMetrics); - return { - page_count: pageCount.page_count, - freelist_count: freelistCount.freelist_count, - page_size: pageSize.page_size, - vfs: copiedMetrics - }; +async function ensureRelationalSeed(database) { + await transaction(database, async () => { + for (let i = 0; i < 8; i += 1) { + await database.execute( + "INSERT OR IGNORE INTO fuzz_rel_users (id, name) VALUES (?, ?)", + `user-${i}`, + `User ${i}` + ); + } + for (let i = 0; i < 12; i += 1) { + const productId = `product-${i}`; + const initialQty = 1e4; + await database.execute( + "INSERT OR IGNORE INTO fuzz_rel_products (id, price) VALUES (?, ?)", + productId, + (i + 1) * 7 + ); + await database.execute( + `INSERT OR IGNORE INTO fuzz_inventory ( + product_id, initial_qty, sold_qty, stock_qty + ) VALUES (?, ?, 0, ?)`, + productId, + initialQty, + initialQty + ); + } + }); } -var sqliteMemoryPressure = actor53({ - options: { - actionTimeout: 3e5 - }, - state: { - sleepCount: 0 - }, - db: db10({ - onMigrate: async (database) => { - await database.execute(` - CREATE TABLE IF NOT EXISTS pressure_rows ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - seed TEXT NOT NULL, - cycle INTEGER NOT NULL, - bucket INTEGER NOT NULL, - payload BLOB NOT NULL, - touched_count INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL - ) - `); +async function applyRelationalOrder(database, opts) { + await ensureRelationalSeed(database); + const orderPrefix = `order-${opts.phase}-${opts.localIndex}`; + const existingOrders = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_orders WHERE id LIKE ?", + `${orderPrefix}-%` + ); + const orderId = `${orderPrefix}-${existingOrders?.count ?? 0}`; + const userId = `user-${intBetween(opts.rng, 0, 7)}`; + const itemCount = intBetween(opts.rng, 1, 4); + let total = 0; + await transaction(database, async () => { + await database.execute( + "INSERT INTO fuzz_orders (id, user_id, total, status) VALUES (?, ?, 0, 'open')", + orderId, + userId + ); + for (let i = 0; i < itemCount; i += 1) { + const productId = `product-${intBetween(opts.rng, 0, 11)}`; + const product = await queryOne2( + database, + "SELECT price FROM fuzz_rel_products WHERE id = ?", + productId + ); + const quantity = intBetween(opts.rng, 1, 5); + const price = product?.price ?? 0; + total += price * quantity; await database.execute( - "CREATE INDEX IF NOT EXISTS idx_pressure_rows_seed_cycle ON pressure_rows(seed, cycle)" + `INSERT INTO fuzz_order_items ( + order_id, product_id, quantity, price + ) VALUES (?, ?, ?, ?)`, + orderId, + productId, + quantity, + price + ); + await database.execute( + `UPDATE fuzz_inventory + SET sold_qty = sold_qty + ?, stock_qty = stock_qty - ? + WHERE product_id = ?`, + quantity, + quantity, + productId ); + } + await database.execute( + "UPDATE fuzz_orders SET total = ?, status = 'paid' WHERE id = ?", + total, + orderId + ); + await database.execute( + "INSERT INTO fuzz_payments (order_id, amount, status) VALUES (?, ?, 'captured')", + orderId, + total + ); + }); + return itemCount + 4; +} +async function applyRollbackProbe(database, phase, rowCount = 20) { + const before = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?", + `rollback-${phase}-%` + ); + await database.execute("BEGIN"); + try { + for (let i = 0; i < rowCount; i += 1) { await database.execute( - "CREATE INDEX IF NOT EXISTS idx_pressure_rows_bucket ON pressure_rows(bucket)" + `INSERT INTO fuzz_items ( + item_key, value, version, update_count, payload, payload_checksum, + payload_bytes, updated_at + ) VALUES (?, ?, 1, 1, ?, ?, ?, ?)`, + `rollback-${phase}-${i}`, + "should-not-survive", + "rollback-payload", + checksum("rollback-payload"), + "rollback-payload".length, + Date.now() ); - await database.execute(` - CREATE TABLE IF NOT EXISTS pressure_cycles ( - cycle INTEGER PRIMARY KEY, - seed TEXT NOT NULL, - inserted_rows INTEGER NOT NULL, - deleted_rows INTEGER NOT NULL, - active_rows INTEGER NOT NULL, - active_bytes INTEGER NOT NULL, - duration_ms REAL NOT NULL, - created_at INTEGER NOT NULL - ) - `); } - }), - onSleep: (c) => { - c.state.sleepCount += 1; - console.log( - JSON.stringify({ - kind: "sqlite_memory_pressure_on_sleep", - actorId: c.actorId, - sleepCount: c.state.sleepCount, - timestamp: (/* @__PURE__ */ new Date()).toISOString() - }) + throw new Error("intentional rollback probe"); + } catch { + await database.execute("ROLLBACK"); + } + const after = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?", + `rollback-${phase}-%` + ); + await database.execute( + `INSERT INTO fuzz_constraint_attempts ( + name, expected_failed, actually_failed, before_count, after_count + ) VALUES (?, 1, 1, ?, ?)`, + `rollback-probe-${phase}`, + before?.count ?? 0, + after?.count ?? 0 + ); + return rowCount; +} +async function applyNastyScript(database, opts) { + const growKey = `nasty-grow-${opts.phase}`; + let ops = 0; + const growMax = Math.min(opts.maxPayloadBytes, 131072); + const growSizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= growMax); + if (!growSizes.includes(1)) growSizes.unshift(1); + if (!growSizes.includes(growMax)) growSizes.push(growMax); + for (const size of growSizes) { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 5e4 + size, + kind: "upsert", + itemKey: growKey, + payloadBytes: Math.min(size, opts.maxPayloadBytes) + }); + ops += 1; + } + const hotUpdates = opts.intense ? 1e4 : 250; + await applyHotUpdates(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 6e4, + itemKey: `nasty-hot-${opts.phase}`, + updates: hotUpdates, + payloadBytes: Math.min(1024, opts.maxPayloadBytes) + }); + ops += hotUpdates; + if (opts.intense) { + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_nasty_heavy_write ON fuzz_items(value, version)" ); - }, - actions: { - reset: async (c) => { - await c.db.execute("DELETE FROM pressure_cycles"); - await c.db.execute("DELETE FROM pressure_rows"); - await c.db.execute("VACUUM"); - return { - ok: true, - storage: await storageStats(c.db) - }; - }, - goToSleep: (c) => { - c.sleep(); - return { ok: true }; - }, - releaseStorage: async (c) => { - const before = await storageStats(c.db); - return { - ok: true, - before, - after: await storageStats(c.db) - }; - }, - stats: async (c) => { - const rowStats = await queryOne2( - c.db, - "SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes, COALESCE(SUM(touched_count), 0) AS touched_sum FROM pressure_rows" - ); - const cycles = await queryOne2( - c.db, - "SELECT COUNT(*) AS count FROM pressure_cycles" + for (let i = 0; i < 1e4; i += 1) { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 12e4 + i, + kind: "upsert", + itemKey: `nasty-bulk-${opts.phase}-${i}`, + payloadBytes: Math.min(256, opts.maxPayloadBytes) + }); + ops += 1; + } + for (let i = 0; i < 1e4; i += 2) { + await applyItemOperation(database, { + seed: opts.seed, + phase: opts.phase, + localIndex: 14e4 + i, + kind: "delete", + itemKey: `nasty-bulk-${opts.phase}-${i}`, + payloadBytes: 1 + }); + ops += 1; + } + await database.execute("DROP INDEX IF EXISTS idx_nasty_heavy_write"); + } + const rollbackRows = opts.intense ? 1e3 : 20; + await applyRollbackProbe(database, opts.phase, rollbackRows); + ops += rollbackRows; + return ops; +} +async function applyDeterministicNastyScript(database, opts) { + let ops = 0; + const growId = `nasty-script-grow-${opts.phase}`; + const maxGrowBytes = Math.min(opts.maxPayloadBytes, 131072); + let finalGrowPayload = ""; + await transaction(database, async () => { + for (let i = 0; i < 256; i += 1) { + const size = Math.max( + 1, + Math.floor(1 + (maxGrowBytes - 1) * i / 255) ); - const integrity = await queryOne2( - c.db, - "PRAGMA integrity_check" + const payload2 = payloadFor( + opts.seed, + opts.phase, + 16e4 + i, + size ); - return { - activeRows: rowStats.active_rows, - activeBytes: rowStats.active_bytes ?? 0, - touchedCount: rowStats.touched_sum ?? 0, - cycles: cycles.count, - integrityCheck: integrity.integrity_check, - storage: await storageStats(c.db) - }; - }, - runCycle: async (c, input) => { - const startedAt = performance.now(); - const insertRows = finiteInt(input.insertRows, DEFAULT_INSERT_ROWS); - const rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES3); - const scanRows = Math.max(1, finiteInt(input.scanRows, DEFAULT_SCAN_ROWS)); - const now = Date.now(); - let insertedRows = 0; - const logStage = (stage, phase, fields = {}) => { - console.log( - JSON.stringify({ - kind: "sqlite_memory_pressure_run_cycle_stage", - actorId: c.actorId, - seed: input.seed, - cycle: input.cycle, - stage, - phase, - elapsedMs: performance.now() - startedAt, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - ...fields - }) - ); - }; - const executeTimed = async (stage, sql, ...args) => { - const stageStartedAt = performance.now(); - logStage(stage, "start", { argCount: args.length }); - try { - const rows = await c.db.execute(sql, ...args); - logStage(stage, "end", { - durationMs: performance.now() - stageStartedAt, - rowCount: rows.length - }); - return rows; - } catch (err) { - logStage(stage, "error", { - durationMs: performance.now() - stageStartedAt, - error: err instanceof Error ? err.message : String(err) - }); - throw err; - } - }; - logStage("run_cycle", "start", { - insertRows, - rowBytes, - scanRows - }); - await executeTimed("begin", "BEGIN"); - try { - while (insertedRows < insertRows) { - const batchRows = Math.min( - INSERT_BATCH_ROWS, - insertRows - insertedRows - ); - const placeholders = []; - const args = []; - for (let i = 0; i < batchRows; i += 1) { - const rowIndex = insertedRows + i; - placeholders.push("(?, ?, ?, randomblob(?), 0, ?)"); - args.push( - input.seed, - input.cycle, - (input.cycle + rowIndex) % 32, - rowBytes, - now + rowIndex - ); - } - await executeTimed( - "insert_batch", - `INSERT INTO pressure_rows (seed, cycle, bucket, payload, touched_count, created_at) VALUES ${placeholders.join(", ")}`, - ...args - ); - insertedRows += batchRows; - logStage("insert_batch_progress", "end", { - insertedRows, - batchRows - }); - } - await executeTimed("commit", "COMMIT"); - } catch (err) { - await executeTimed("rollback", "ROLLBACK").catch(() => void 0); - throw err; - } - const scan = await executeTimed( - "scan_recent", - "SELECT id, length(payload) AS payload_bytes FROM pressure_rows ORDER BY id DESC LIMIT ?", - scanRows + finalGrowPayload = payload2; + await database.execute( + `INSERT INTO fuzz_edge_payloads ( + id, kind, payload, payload_checksum, payload_bytes, updated_at + ) VALUES (?, 'nasty-grow', ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + payload = excluded.payload, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes, + updated_at = excluded.updated_at`, + growId, + payload2, + checksum(payload2), + payload2.length, + Date.now() ); - const bucketAgg = await executeTimed( - "bucket_agg", - "SELECT bucket, COUNT(*) AS rows, SUM(length(payload)) AS bytes FROM pressure_rows WHERE bucket BETWEEN ? AND ? GROUP BY bucket ORDER BY bucket", - input.cycle % 16, - input.cycle % 16 + 15 + ops += 1; + } + await database.execute( + `INSERT INTO fuzz_edge_expectations ( + id, present, payload_checksum, payload_bytes + ) VALUES (?, 1, ?, ?) + ON CONFLICT(id) DO UPDATE SET + present = 1, + payload_checksum = excluded.payload_checksum, + payload_bytes = excluded.payload_bytes`, + growId, + checksum(finalGrowPayload), + finalGrowPayload.length + ); + }); + const counterId = `nasty-counter-${opts.phase}`; + await database.execute( + "INSERT INTO fuzz_nasty_counter (id, value) VALUES (?, 0) ON CONFLICT(id) DO UPDATE SET value = 0", + counterId + ); + await transaction(database, async () => { + for (let i = 0; i < 1e4; i += 1) { + await database.execute( + "UPDATE fuzz_nasty_counter SET value = value + 1 WHERE id = ?", + counterId ); - await executeTimed( - "touch_recent", - "UPDATE pressure_rows SET touched_count = touched_count + 1 WHERE id IN (SELECT id FROM pressure_rows ORDER BY id DESC LIMIT ?)", - Math.min(scanRows, insertRows) + ops += 1; + } + }); + const counter2 = await queryOne2( + database, + "SELECT value FROM fuzz_nasty_counter WHERE id = ?", + counterId + ); + await recordProbe( + database, + opts.phase, + "nasty-script", + "same-row-10k-updates", + 1e4, + counter2?.value ?? -1, + (counter2?.value ?? -1) !== 1e4 + ); + const groupId = `nasty-bulk-${opts.phase}`; + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_fuzz_nasty_rows_group_n ON fuzz_nasty_rows(group_id, n)" + ); + await transaction(database, async () => { + for (let i = 0; i < 1e4; i += 1) { + await database.execute( + `INSERT INTO fuzz_nasty_rows (group_id, n, payload) + VALUES (?, ?, ?) + ON CONFLICT(group_id, n) DO UPDATE SET payload = excluded.payload`, + groupId, + i, + payloadFor(opts.seed, opts.phase, 17e4 + i, 64) ); - let deletedRows = 0; - const rowStatsRows = await executeTimed( - "row_stats", - "SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes FROM pressure_rows" + ops += 1; + } + await database.execute( + "DELETE FROM fuzz_nasty_rows WHERE group_id = ? AND n % 2 = 0", + groupId + ); + ops += 1; + }); + await database.execute("DROP INDEX IF EXISTS idx_fuzz_nasty_rows_group_n"); + const remaining = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", + groupId + ); + await recordProbe( + database, + opts.phase, + "nasty-script", + "insert-10k-delete-every-other", + 5e3, + remaining?.count ?? -1, + (remaining?.count ?? -1) !== 5e3 + ); + const indexLeft = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = 'idx_fuzz_nasty_rows_group_n'" + ); + await recordProbe( + database, + opts.phase, + "nasty-script", + "create-drop-index-around-heavy-writes", + 0, + indexLeft?.count ?? -1, + (indexLeft?.count ?? -1) !== 0 + ); + const rollbackGroupId = `nasty-rollback-${opts.phase}`; + const beforeRollback = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", + rollbackGroupId + ); + await database.execute("BEGIN"); + try { + for (let i = 0; i < 1e3; i += 1) { + await database.execute( + "INSERT INTO fuzz_nasty_rows (group_id, n, payload) VALUES (?, ?, ?)", + rollbackGroupId, + i, + "rollback" ); - const rowStats = rowStatsRows[0]; - if (!rowStats) throw new Error("query returned no rows: row_stats"); - const integrityRows = await executeTimed( - "integrity_check", - "PRAGMA integrity_check" + ops += 1; + } + await database.execute("ROLLBACK"); + } catch (err) { + await database.execute("ROLLBACK").catch(() => void 0); + throw err; + } + const afterRollback = await queryOne2( + database, + "SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?", + rollbackGroupId + ); + await recordProbe( + database, + opts.phase, + "nasty-script", + "rollback-1k-inserts", + beforeRollback?.count ?? 0, + afterRollback?.count ?? -1, + (afterRollback?.count ?? -1) !== (beforeRollback?.count ?? 0) + ); + return ops; +} +function shouldRunDeepScenario(mode2, scenario) { + return mode2 === scenario || mode2 === "kitchen-sink" || mode2 === "nasty"; +} +async function applyDeepScenarios(database, opts) { + const runScenario = async (name, fn) => { + try { + return await fn(); + } catch (error) { + const detail = error instanceof Error ? error.message : typeof error === "string" ? error : String(error); + throw new Error( + `deep scenario ${name} failed in mode ${opts.mode} during phase ${opts.phase}: ${detail}`, + { cause: error } ); - const integrity = integrityRows[0]; - if (!integrity) { - throw new Error("query returned no rows: integrity_check"); - } - const durationMs = performance.now() - startedAt; - await executeTimed( - "record_cycle", - "INSERT OR REPLACE INTO pressure_cycles (cycle, seed, inserted_rows, deleted_rows, active_rows, active_bytes, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - input.cycle, - input.seed, - insertedRows, - deletedRows, - rowStats.active_rows, - rowStats.active_bytes ?? 0, - durationMs, - now + } + }; + if (shouldRunDeepScenario(opts.mode, "edge") || opts.mode === "payloads") { + opts.ops.edgePayload = (opts.ops.edgePayload ?? 0) + await runScenario( + "edge", + () => applyEdgePayloads(database, opts) + ); + } + if (opts.mode === "actual-nul") { + opts.ops.actualNul = (opts.ops.actualNul ?? 0) + await runScenario( + "actual-nul", + () => applyActualNulPayload(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "fragmentation")) { + opts.ops.fragmentation = (opts.ops.fragmentation ?? 0) + await runScenario( + "fragmentation", + () => applyFragmentationChurn(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "schema")) { + opts.ops.schema = (opts.ops.schema ?? 0) + await runScenario( + "schema", + () => applySchemaChurn(database, opts.phase) + ); + } + if (shouldRunDeepScenario(opts.mode, "index")) { + opts.ops.index = (opts.ops.index ?? 0) + await runScenario("index", () => applyIndexProbe(database, opts)); + } + if (shouldRunDeepScenario(opts.mode, "constraints")) { + opts.ops.constraints = (opts.ops.constraints ?? 0) + await runScenario( + "constraints", + () => applyConstraintChaos(database, opts.phase) + ); + } + if (shouldRunDeepScenario(opts.mode, "savepoints")) { + opts.ops.savepoints = (opts.ops.savepoints ?? 0) + await runScenario( + "savepoints", + () => applySavepointScenario(database, opts.phase) + ); + } + if (shouldRunDeepScenario(opts.mode, "pragma")) { + opts.ops.pragma = (opts.ops.pragma ?? 0) + await runScenario( + "pragma", + () => applyPragmaProbe(database, opts.phase) + ); + } + if (shouldRunDeepScenario(opts.mode, "prepared")) { + opts.ops.prepared = (opts.ops.prepared ?? 0) + await runScenario( + "prepared", + () => applyPreparedChurn(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "growth")) { + opts.ops.growth = (opts.ops.growth ?? 0) + await runScenario( + "growth", + () => applyGrowthProbe(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "readwrite")) { + opts.ops.readwrite = (opts.ops.readwrite ?? 0) + await runScenario( + "readwrite", + () => applyReadWriteProbe(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "truncate")) { + opts.ops.truncate = (opts.ops.truncate ?? 0) + await runScenario( + "truncate", + () => applyTruncateRecreateProbe(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "boundary-keys")) { + opts.ops.boundaryKeys = (opts.ops.boundaryKeys ?? 0) + await runScenario( + "boundary-keys", + () => applyBoundaryKeys(database, opts) + ); + } + if (shouldRunDeepScenario(opts.mode, "relational")) { + const orders = Math.max(1, Math.floor(opts.iterations / 20)); + for (let i = 0; i < orders; i += 1) { + opts.ops.relational = (opts.ops.relational ?? 0) + await runScenario( + "relational", + () => applyRelationalOrder(database, { + phase: opts.phase, + localIndex: i, + rng: opts.rng + }) ); - const storageStartedAt = performance.now(); - logStage("storage_stats", "start"); - const storage = await storageStats(c.db); - logStage("storage_stats", "end", { - durationMs: performance.now() - storageStartedAt, - pageCount: storage.page_count, - dbSizePages: storage.vfs?.dbSizePages ?? null, - pageCacheEntries: storage.vfs?.pageCacheEntries ?? null - }); - logStage("run_cycle", "end", { - durationMs, - activeRows: rowStats.active_rows, - activeBytes: rowStats.active_bytes ?? 0, - pageCount: storage.page_count - }); - return { - seed: input.seed, - cycle: input.cycle, - insertedRows, - deletedRows, - activeRows: rowStats.active_rows, - activeBytes: rowStats.active_bytes ?? 0, - scannedRows: scan.length, - bucketsRead: bucketAgg.length, - integrityCheck: integrity.integrity_check, - storage, - durationMs - }; } } -}); - -// src/actors/testing/mock-agentic-loop.ts -import { - actor as actor54 -} from "rivetkit"; -import { db as db11 } from "rivetkit/db"; -var DEFAULT_SLEEP_GRACE_PERIOD_MS = 12e4; -var DEFAULT_ON_SLEEP_DELAY_MS = 0; -var debugSocketsByActorId = /* @__PURE__ */ new Map(); -function sleep3(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} -function positiveInteger3(value, name) { - if (!Number.isInteger(value) || value < 1) { - throw new Error(`${name} must be a positive integer`); + if (opts.mode === "kitchen-sink" || opts.mode === "nasty") { + opts.ops.idempotent = (opts.ops.idempotent ?? 0) + await runScenario( + "idempotent", + () => applyIdempotentReplay(database, opts.phase) + ); + opts.ops.nasty = (opts.ops.nasty ?? 0) + await runScenario( + "nasty", + () => applyNastyScript(database, { + ...opts, + intense: opts.mode === "nasty" + }) + ); } - return value; -} -function stringValue(value, name) { - if (typeof value !== "string" || value.length === 0) { - throw new Error(`${name} must be a non-empty string`); + if (opts.mode === "nasty-script") { + opts.ops.nasty = (opts.ops.nasty ?? 0) + await runScenario( + "nasty-script", + () => applyDeterministicNastyScript(database, opts) + ); } - return value; -} -function typedRows2(rows) { - return rows; -} -function numberFromEnv(name, fallback) { - const value = process.env[name]; - if (value === void 0 || value === "") return fallback; - const parsed = Number(value); - if (!Number.isFinite(parsed) || parsed < 0) { - throw new Error(`${name} must be a finite non-negative number`); + if (shouldRunDeepScenario(opts.mode, "shadow")) { + opts.ops.shadow = (opts.ops.shadow ?? 0) + await runScenario( + "shadow", + () => updateShadowChecksums(database, opts.phase) + ); } - return parsed; -} -function send(websocket, payload2) { - if (websocket.readyState !== 1) return; - websocket.send(JSON.stringify(payload2)); } -function debugPayload(row, replayed) { - return { - type: "debugEvent", - eventId: row.event_id, - name: row.name, - actorId: row.actor_id, - connectionId: row.connection_id, - requestId: row.request_id, - details: JSON.parse(row.details_json), - createdAt: row.created_at, - replayed - }; -} -function publishDebugEvent(row) { - const sockets = debugSocketsByActorId.get(row.actor_id); - if (!sockets) return; - for (const socket of sockets) { - send(socket, debugPayload(row, false)); +function chooseKind(mode2, rng) { + const roll = rng(); + if (mode2 === "transactions") { + if (roll < 0.55) return "transfer"; + if (roll < 0.75) return "upsert"; + if (roll < 0.9) return "update"; + return "delete"; } -} -function addDebugSocket(actorId, websocket) { - const sockets = debugSocketsByActorId.get(actorId) ?? /* @__PURE__ */ new Set(); - sockets.add(websocket); - debugSocketsByActorId.set(actorId, sockets); - return () => { - sockets.delete(websocket); - if (sockets.size === 0) { - debugSocketsByActorId.delete(actorId); - } - }; -} -async function recordDebugEvent(c, input) { - const row = { - event_id: crypto.randomUUID(), - name: input.name, - actor_id: c.actorId, - connection_id: input.connectionId ?? null, - request_id: input.requestId ?? null, - details_json: JSON.stringify(input.details ?? {}), - created_at: input.createdAt ?? Date.now() - }; - try { - await c.db.execute( - "INSERT INTO mock_agentic_debug_events (event_id, name, actor_id, connection_id, request_id, details_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", - row.event_id, - row.name, - row.actor_id, - row.connection_id, - row.request_id, - row.details_json, - row.created_at - ); - publishDebugEvent(row); - } catch (error) { - c.log.warn({ - msg: "mock agentic debug event failed", - name: input.name, - err: error instanceof Error ? error.message : String(error) - }); + if (mode2 === "hot") { + if (roll < 0.6) return "hot"; + if (roll < 0.75) return "upsert"; + if (roll < 0.9) return "update"; + return "delete"; + } + if (mode2 === "payloads") { + if (roll < 0.4) return "upsert"; + if (roll < 0.7) return "insert"; + if (roll < 0.9) return "update"; + return "delete"; } + if (roll < 0.2) return "insert"; + if (roll < 0.45) return "update"; + if (roll < 0.65) return "delete"; + if (roll < 0.85) return "upsert"; + if (roll < 0.95) return "hot"; + return "transfer"; } -async function replayDebugEvents(database, websocket) { - const rows = typedRows2( - await database.execute(` - SELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at - FROM ( - SELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at - FROM mock_agentic_debug_events - ORDER BY created_at DESC - LIMIT 200 - ) - ORDER BY created_at ASC - `) +async function validate(database) { + const integrity = await queryOne2( + database, + "PRAGMA integrity_check" + ); + const quick = await queryOne2( + database, + "PRAGMA quick_check" + ); + const totals = await queryOne2( + database, + `WITH latest AS ( + SELECT e.* + FROM fuzz_item_events e + JOIN ( + SELECT item_key, MAX(seq) AS seq + FROM fuzz_item_events + GROUP BY item_key + ) m ON m.item_key = e.item_key AND m.seq = e.seq + ) + SELECT + (SELECT COUNT(*) FROM fuzz_item_events) AS total_events, + (SELECT COUNT(*) FROM fuzz_items) AS active_rows, + (SELECT COUNT(*) FROM latest WHERE present = 1) AS expected_rows, + (SELECT COALESCE(SUM(version), 0) FROM fuzz_items) AS actual_version_sum, + (SELECT COALESCE(SUM(version), 0) FROM latest WHERE present = 1) AS expected_version_sum, + (SELECT COALESCE(SUM(payload_checksum), 0) FROM fuzz_items) AS actual_payload_checksum_sum, + (SELECT COALESCE(SUM(payload_checksum), 0) FROM latest WHERE present = 1) AS expected_payload_checksum_sum` + ); + const mismatches = await queryOne2( + database, + `WITH latest AS ( + SELECT e.* + FROM fuzz_item_events e + JOIN ( + SELECT item_key, MAX(seq) AS seq + FROM fuzz_item_events + GROUP BY item_key + ) m ON m.item_key = e.item_key AND m.seq = e.seq + ) + SELECT + ( + SELECT COUNT(*) + FROM latest l + LEFT JOIN fuzz_items i ON i.item_key = l.item_key + WHERE l.present = 1 AND i.item_key IS NULL + ) AS missing_rows, + ( + SELECT COUNT(*) + FROM fuzz_items i + LEFT JOIN latest l ON l.item_key = i.item_key + WHERE l.item_key IS NULL OR l.present = 0 + ) AS extra_rows, + ( + SELECT COUNT(*) + FROM latest l + JOIN fuzz_items i ON i.item_key = l.item_key + WHERE l.present = 1 + AND ( + i.value != l.value OR + i.version != l.version OR + i.update_count != l.update_count OR + i.payload_checksum != l.payload_checksum OR + i.payload_bytes != l.payload_bytes + ) + ) AS mismatched_rows, + ( + SELECT COUNT(*) + FROM ( + SELECT item_key + FROM fuzz_items + GROUP BY item_key + HAVING COUNT(*) > 1 + ) + ) AS duplicate_keys` + ); + const accounts = await queryOne2( + database, + `SELECT + COUNT(*) AS account_count, + COALESCE(SUM(balance), 0) AS account_balance_sum, + ( + SELECT COUNT(*) + FROM fuzz_transfer_events + WHERE balance_sum_before != ? OR balance_sum_after != ? + ) AS account_balance_mismatch + FROM fuzz_accounts`, + ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE, + ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE + ); + const edge = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_edge_payloads) AS edge_rows, + (SELECT COUNT(*) FROM fuzz_edge_expectations WHERE present = 1) AS edge_expected_rows, + ( + SELECT COUNT(*) + FROM fuzz_edge_expectations e + LEFT JOIN fuzz_edge_payloads p ON p.id = e.id + WHERE + (e.present = 1 AND p.id IS NULL) OR + (e.present = 0 AND p.id IS NOT NULL) OR + (e.present = 1 AND ( + p.payload_checksum != e.payload_checksum OR + p.payload_bytes != e.payload_bytes + )) + ) AS edge_mismatches` + ); + const indexProbe = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_indexed) AS index_rows, + ( + SELECT COUNT(*) + FROM ( + SELECT id FROM fuzz_indexed + WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 + EXCEPT + SELECT id FROM fuzz_indexed NOT INDEXED + WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 + ) + ) + ( + SELECT COUNT(*) + FROM ( + SELECT id FROM fuzz_indexed NOT INDEXED + WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 + EXCEPT + SELECT id FROM fuzz_indexed + WHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250 + ) + ) AS index_mismatches` + ); + const relational = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_orders) AS relational_orders, + ( + SELECT COUNT(*) + FROM fuzz_orders o + LEFT JOIN ( + SELECT order_id, COALESCE(SUM(quantity * price), 0) AS item_total + FROM fuzz_order_items + GROUP BY order_id + ) i ON i.order_id = o.id + WHERE o.total != COALESCE(i.item_total, 0) + ) + ( + SELECT COUNT(*) + FROM fuzz_orders o + LEFT JOIN ( + SELECT order_id, COALESCE(SUM(amount), 0) AS payment_total + FROM fuzz_payments + WHERE status = 'captured' + GROUP BY order_id + ) p ON p.order_id = o.id + WHERE o.status = 'paid' AND o.total != COALESCE(p.payment_total, 0) + ) + ( + SELECT COUNT(*) + FROM fuzz_inventory + WHERE initial_qty != sold_qty + stock_qty OR stock_qty < 0 + ) AS relational_mismatches` ); - for (const row of rows) { - send(websocket, debugPayload(row, true)); - } -} -function verifyEntryRows(rows, expectedSeconds) { - const seen = /* @__PURE__ */ new Set(); - const indexes = rows.map((row) => row.idx).sort((a, b) => a - b); - for (const idx of indexes) seen.add(idx); - const missing = []; - for (let idx = 1; idx <= expectedSeconds; idx += 1) { - if (!seen.has(idx)) missing.push(idx); - } - const contiguous = rows.length === expectedSeconds && missing.length === 0 && indexes.every((idx, offset) => idx === offset + 1); - return { - expectedSeconds, - count: rows.length, - contiguous, - missing, - indexes, - ok: contiguous - }; -} -function verifyAllRows(rows, expectedRequests) { - const expectedByRequest = new Map( - expectedRequests.map((request) => [request.requestId, request.seconds]) + const constraints = await queryOne2( + database, + `SELECT + COUNT(*) AS constraint_attempts, + COALESCE(SUM( + CASE + WHEN expected_failed = 1 AND (actually_failed != 1 OR before_count != after_count) THEN 1 + WHEN expected_failed = 0 AND after_count != before_count + 1 THEN 1 + ELSE 0 + END + ), 0) AS constraint_leaks + FROM fuzz_constraint_attempts` ); - const rowsByRequest = /* @__PURE__ */ new Map(); - for (const row of rows) { - const requestRows = rowsByRequest.get(row.request_id) ?? []; - requestRows.push(row); - rowsByRequest.set(row.request_id, requestRows); - } - const requests = expectedRequests.map((request) => { - const result = verifyEntryRows( - rowsByRequest.get(request.requestId) ?? [], - request.seconds - ); - return { - requestId: request.requestId, - ...result - }; - }); - const unexpectedRequestIds = [...rowsByRequest.keys()].filter((requestId) => !expectedByRequest.has(requestId)).sort(); - const expectedTotalRows = expectedRequests.reduce( - (total, request) => total + request.seconds, - 0 + const savepoints = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_savepoints) AS savepoint_rows, + ( + SELECT COUNT(*) + FROM fuzz_savepoint_expectations e + LEFT JOIN fuzz_savepoints s ON s.id = e.id + WHERE + (e.present = 1 AND s.id IS NULL) OR + (e.present = 0 AND s.id IS NOT NULL) OR + (e.present = 1 AND s.value != e.value) + ) AS savepoint_mismatches` ); - const ok = unexpectedRequestIds.length === 0 && rows.length === expectedTotalRows && requests.every((request) => request.ok); - return { - type: "verifiedAll", - expectedRequests: expectedRequests.length, - expectedTotalRows, - totalRows: rows.length, - rows, - unexpectedRequestIds, - requests, - ok + const idempotency = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_idempotent_ops) AS idempotent_ops, + ( + SELECT COUNT(*) + FROM fuzz_idempotent_targets t + LEFT JOIN ( + SELECT target_id, COALESCE(SUM(amount), 0) AS expected + FROM fuzz_idempotent_ops + GROUP BY target_id + ) o ON o.target_id = t.id + WHERE t.value != COALESCE(o.expected, 0) + ) AS idempotent_mismatches` + ); + const schema = await queryOne2( + database, + `SELECT + COUNT(*) AS schema_objects, + COALESCE(SUM(CASE WHEN m.name IS NULL THEN 1 ELSE 0 END), 0) AS schema_missing_objects + FROM fuzz_schema_registry r + LEFT JOIN sqlite_master m ON m.name = r.name AND m.type = r.type` + ); + const probes = await queryOne2( + database, + `SELECT + COUNT(*) AS probe_rows, + COALESCE(SUM(mismatch), 0) AS probe_mismatches + FROM fuzz_probe_results` + ); + const prepared = await queryOne2( + database, + `SELECT + (SELECT COUNT(*) FROM fuzz_prepared_churn) AS prepared_rows, + ( + SELECT COUNT(*) + FROM fuzz_prepared_expectations e + LEFT JOIN fuzz_prepared_churn p ON p.id = e.id + WHERE + p.id IS NULL OR + p.value != e.value OR + p.payload_checksum != e.payload_checksum + ) AS prepared_mismatches` + ); + const shadow = await queryOne2( + database, + `WITH recomputed AS ( + SELECT 'items' AS name, + COUNT(*) AS row_count, + COALESCE(SUM(payload_checksum + version + update_count), 0) AS value + FROM fuzz_items + UNION ALL + SELECT 'edge' AS name, + COUNT(*) AS row_count, + COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value + FROM fuzz_edge_payloads + ) + SELECT + (SELECT COUNT(*) FROM fuzz_shadow_checksums) AS shadow_rows, + ( + SELECT COUNT(*) + FROM fuzz_shadow_checksums s + JOIN recomputed r ON r.name = s.name + WHERE s.value != r.value OR s.row_count != r.row_count + ) AS shadow_mismatches` + ); + const summary = { + totalEvents: totals?.total_events ?? 0, + activeRows: totals?.active_rows ?? 0, + expectedRows: totals?.expected_rows ?? 0, + missingRows: mismatches?.missing_rows ?? 0, + extraRows: mismatches?.extra_rows ?? 0, + mismatchedRows: mismatches?.mismatched_rows ?? 0, + duplicateKeys: mismatches?.duplicate_keys ?? 0, + actualVersionSum: totals?.actual_version_sum ?? 0, + expectedVersionSum: totals?.expected_version_sum ?? 0, + actualPayloadChecksumSum: totals?.actual_payload_checksum_sum ?? 0, + expectedPayloadChecksumSum: totals?.expected_payload_checksum_sum ?? 0, + accountCount: accounts?.account_count ?? 0, + accountBalanceSum: accounts?.account_balance_sum ?? 0, + expectedAccountBalanceSum: ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE, + accountBalanceMismatch: accounts?.account_balance_mismatch ?? 0, + integrityCheck: integrity?.integrity_check ?? "missing", + quickCheck: quick?.quick_check ?? "missing", + edgeRows: edge?.edge_rows ?? 0, + edgeExpectedRows: edge?.edge_expected_rows ?? 0, + edgeMismatches: edge?.edge_mismatches ?? 0, + indexRows: indexProbe?.index_rows ?? 0, + indexMismatches: indexProbe?.index_mismatches ?? 0, + relationalOrders: relational?.relational_orders ?? 0, + relationalMismatches: relational?.relational_mismatches ?? 0, + constraintAttempts: constraints?.constraint_attempts ?? 0, + constraintLeaks: constraints?.constraint_leaks ?? 0, + savepointRows: savepoints?.savepoint_rows ?? 0, + savepointMismatches: savepoints?.savepoint_mismatches ?? 0, + idempotentOps: idempotency?.idempotent_ops ?? 0, + idempotentMismatches: idempotency?.idempotent_mismatches ?? 0, + schemaObjects: schema?.schema_objects ?? 0, + schemaMissingObjects: schema?.schema_missing_objects ?? 0, + probeRows: probes?.probe_rows ?? 0, + probeMismatches: probes?.probe_mismatches ?? 0, + preparedRows: prepared?.prepared_rows ?? 0, + preparedMismatches: prepared?.prepared_mismatches ?? 0, + shadowRows: shadow?.shadow_rows ?? 0, + shadowMismatches: shadow?.shadow_mismatches ?? 0 }; + return summary; +} +async function debugItemMismatches(database, limit = 5) { + const rows = await database.execute( + `WITH latest AS ( + SELECT e.* + FROM fuzz_item_events e + JOIN ( + SELECT item_key, MAX(seq) AS seq + FROM fuzz_item_events + GROUP BY item_key + ) m ON m.item_key = e.item_key AND m.seq = e.seq + ) + SELECT + COALESCE(l.item_key, i.item_key) AS item_key, + i.value AS actual_value, + l.value AS expected_value, + i.version AS actual_version, + l.version AS expected_version, + i.update_count AS actual_update_count, + l.update_count AS expected_update_count, + i.payload_checksum AS actual_payload_checksum, + l.payload_checksum AS expected_payload_checksum, + i.payload_bytes AS actual_payload_bytes, + l.payload_bytes AS expected_payload_bytes + FROM latest l + FULL OUTER JOIN fuzz_items i ON i.item_key = l.item_key + WHERE + (l.present = 1 AND i.item_key IS NULL) OR + ((l.item_key IS NULL OR l.present = 0) AND i.item_key IS NOT NULL) OR + ( + l.present = 1 AND i.item_key IS NOT NULL AND ( + i.value != l.value OR + i.version != l.version OR + i.update_count != l.update_count OR + i.payload_checksum != l.payload_checksum OR + i.payload_bytes != l.payload_bytes + ) + ) + ORDER BY COALESCE(l.item_key, i.item_key) + LIMIT ?`, + limit + ); + const itemMismatches = rows.map((row) => ({ + itemKey: row.item_key, + actualValue: row.actual_value, + expectedValue: row.expected_value, + actualVersion: row.actual_version, + expectedVersion: row.expected_version, + actualUpdateCount: row.actual_update_count, + expectedUpdateCount: row.expected_update_count, + actualPayloadChecksum: row.actual_payload_checksum, + expectedPayloadChecksum: row.expected_payload_checksum, + actualPayloadBytes: row.actual_payload_bytes, + expectedPayloadBytes: row.expected_payload_bytes + })); + const recentEventsByKey = {}; + for (const row of itemMismatches) { + const events = await database.execute( + `SELECT + seq, + phase, + local_index, + kind, + present, + value, + version, + update_count, + payload_checksum, + payload_bytes, + applied + FROM fuzz_item_events + WHERE item_key = ? + ORDER BY seq DESC + LIMIT 10`, + row.itemKey + ); + recentEventsByKey[row.itemKey] = events.map((event19) => ({ + seq: event19.seq, + phase: event19.phase, + localIndex: event19.local_index, + kind: event19.kind, + present: event19.present, + value: event19.value, + version: event19.version, + updateCount: event19.update_count, + payloadChecksum: event19.payload_checksum, + payloadBytes: event19.payload_bytes, + applied: event19.applied + })); + } + return { itemMismatches, recentEventsByKey }; } -var mockAgenticLoop = actor54({ +var rawSqliteFuzzer = actor42({ options: { - canHibernateWebSocket: false, - sleepGracePeriod: DEFAULT_SLEEP_GRACE_PERIOD_MS + actionTimeout: 3e5 }, - db: db11({ + db: db8({ onMigrate: async (database) => { await database.execute(` - CREATE TABLE IF NOT EXISTS mock_agentic_entries ( - request_id TEXT NOT NULL, - idx INTEGER NOT NULL, - created_at INTEGER NOT NULL, - PRIMARY KEY (request_id, idx) + CREATE TABLE IF NOT EXISTS fuzz_items ( + item_key TEXT PRIMARY KEY, + value TEXT NOT NULL, + version INTEGER NOT NULL, + update_count INTEGER NOT NULL, + payload TEXT NOT NULL, + payload_checksum INTEGER NOT NULL, + payload_bytes INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_item_events ( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + phase INTEGER NOT NULL, + local_index INTEGER NOT NULL, + kind TEXT NOT NULL, + item_key TEXT NOT NULL, + present INTEGER NOT NULL, + value TEXT, + version INTEGER NOT NULL, + update_count INTEGER NOT NULL, + payload_checksum INTEGER NOT NULL, + payload_bytes INTEGER NOT NULL, + applied INTEGER NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_fuzz_item_events_key_seq ON fuzz_item_events(item_key, seq)" + ); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_accounts ( + id TEXT PRIMARY KEY, + balance INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_transfer_events ( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + phase INTEGER NOT NULL, + local_index INTEGER NOT NULL, + from_account TEXT NOT NULL, + to_account TEXT NOT NULL, + amount INTEGER NOT NULL, + balance_sum_before INTEGER NOT NULL, + balance_sum_after INTEGER NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_edge_payloads ( + id TEXT PRIMARY KEY, + kind TEXT NOT NULL, + payload TEXT NOT NULL, + payload_checksum INTEGER NOT NULL, + payload_bytes INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_edge_expectations ( + id TEXT PRIMARY KEY, + present INTEGER NOT NULL, + payload_checksum INTEGER NOT NULL, + payload_bytes INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_trigger_audit ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload_id TEXT NOT NULL, + old_checksum INTEGER NOT NULL, + new_checksum INTEGER NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_fuzz_edge_kind_size ON fuzz_edge_payloads(kind, payload_bytes)" + ); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_indexed ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant TEXT NOT NULL, + bucket INTEGER NOT NULL, + score INTEGER NOT NULL, + label TEXT NOT NULL, + payload TEXT NOT NULL + ) + `); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_tenant_bucket_score ON fuzz_indexed(tenant, bucket, score)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_score_label ON fuzz_indexed(score, label)" + ); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_rel_users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_rel_products ( + id TEXT PRIMARY KEY, + price INTEGER NOT NULL CHECK (price >= 0) + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_orders ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + total INTEGER NOT NULL, + status TEXT NOT NULL, + FOREIGN KEY (user_id) REFERENCES fuzz_rel_users(id) + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_order_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id TEXT NOT NULL, + product_id TEXT NOT NULL, + quantity INTEGER NOT NULL CHECK (quantity > 0), + price INTEGER NOT NULL CHECK (price >= 0), + FOREIGN KEY (order_id) REFERENCES fuzz_orders(id), + FOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id) + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_payments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id TEXT NOT NULL, + amount INTEGER NOT NULL, + status TEXT NOT NULL, + FOREIGN KEY (order_id) REFERENCES fuzz_orders(id) + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_inventory ( + product_id TEXT PRIMARY KEY, + initial_qty INTEGER NOT NULL, + sold_qty INTEGER NOT NULL, + stock_qty INTEGER NOT NULL, + FOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id) + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_constraints ( + id TEXT PRIMARY KEY, + must_not_null TEXT NOT NULL, + qty INTEGER NOT NULL CHECK (qty >= 0), + uniq TEXT NOT NULL UNIQUE + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_constraint_attempts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + expected_failed INTEGER NOT NULL, + actually_failed INTEGER NOT NULL, + before_count INTEGER NOT NULL, + after_count INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_fk_parent ( + id TEXT PRIMARY KEY + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_fk_child ( + id TEXT PRIMARY KEY, + parent_id TEXT NOT NULL, + FOREIGN KEY (parent_id) REFERENCES fuzz_fk_parent(id) ON DELETE CASCADE + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_savepoints ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_savepoint_expectations ( + id TEXT PRIMARY KEY, + present INTEGER NOT NULL, + value INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_idempotent_ops ( + op_id TEXT PRIMARY KEY, + target_id TEXT NOT NULL, + amount INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_idempotent_targets ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_schema_registry ( + name TEXT PRIMARY KEY, + type TEXT NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_probe_results ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + phase INTEGER NOT NULL, + scenario TEXT NOT NULL, + name TEXT NOT NULL, + expected TEXT NOT NULL, + actual TEXT NOT NULL, + mismatch INTEGER NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_prepared_churn ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL, + payload TEXT NOT NULL, + payload_checksum INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_prepared_expectations ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL, + payload_checksum INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS fuzz_shadow_checksums ( + name TEXT PRIMARY KEY, + value INTEGER NOT NULL, + row_count INTEGER NOT NULL ) `); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_mock_agentic_entries_created_at ON mock_agentic_entries(created_at)" - ); await database.execute(` - CREATE TABLE IF NOT EXISTS mock_agentic_sleep_state ( - id INTEGER PRIMARY KEY CHECK (id = 1), - sleep_started_at INTEGER NOT NULL + CREATE TABLE IF NOT EXISTS fuzz_nasty_counter ( + id TEXT PRIMARY KEY, + value INTEGER NOT NULL ) `); await database.execute(` - CREATE TABLE IF NOT EXISTS mock_agentic_debug_events ( - event_id TEXT PRIMARY KEY, - name TEXT NOT NULL, - actor_id TEXT NOT NULL, - connection_id TEXT, - request_id TEXT, - details_json TEXT NOT NULL, - created_at INTEGER NOT NULL + CREATE TABLE IF NOT EXISTS fuzz_nasty_rows ( + group_id TEXT NOT NULL, + n INTEGER NOT NULL, + payload TEXT NOT NULL, + PRIMARY KEY (group_id, n) ) `); - await database.execute( - "CREATE INDEX IF NOT EXISTS idx_mock_agentic_debug_events_created_at ON mock_agentic_debug_events(created_at)" - ); } }), - async onWake(c) { - await recordDebugEvent(c, { - name: "onWake", - details: { - key: c.key, - name: c.name - } - }); - }, - async onSleep(c) { - const delayMs = numberFromEnv( - "MOCK_AGENTIC_ON_SLEEP_DELAY_MS", - DEFAULT_ON_SLEEP_DELAY_MS - ); - const sleepStartedAt = Date.now(); - await recordDebugEvent(c, { - name: "onSleepStart", - createdAt: sleepStartedAt, - details: { - delayMs - } - }); - await c.db.execute( - "INSERT OR REPLACE INTO mock_agentic_sleep_state (id, sleep_started_at) VALUES (1, ?)", - sleepStartedAt - ); - c.log.info({ - msg: "mock agentic loop onSleep delay", - delayMs, - sleepStartedAt - }); - await sleep3(delayMs); - await recordDebugEvent(c, { - name: "onSleepEnd", - details: { - delayMs, - sleepStartedAt, - elapsedMs: Date.now() - sleepStartedAt + actions: { + reset: async (c) => { + await c.db.execute("DELETE FROM fuzz_nasty_rows"); + await c.db.execute("DELETE FROM fuzz_nasty_counter"); + await c.db.execute("DELETE FROM fuzz_shadow_checksums"); + await c.db.execute("DELETE FROM fuzz_prepared_expectations"); + await c.db.execute("DELETE FROM fuzz_prepared_churn"); + await c.db.execute("DELETE FROM fuzz_probe_results"); + await c.db.execute("DELETE FROM fuzz_schema_registry"); + await c.db.execute("DELETE FROM fuzz_idempotent_targets"); + await c.db.execute("DELETE FROM fuzz_idempotent_ops"); + await c.db.execute("DELETE FROM fuzz_savepoint_expectations"); + await c.db.execute("DELETE FROM fuzz_savepoints"); + await c.db.execute("DELETE FROM fuzz_fk_child"); + await c.db.execute("DELETE FROM fuzz_fk_parent"); + await c.db.execute("DELETE FROM fuzz_constraint_attempts"); + await c.db.execute("DELETE FROM fuzz_constraints"); + await c.db.execute("DELETE FROM fuzz_payments"); + await c.db.execute("DELETE FROM fuzz_order_items"); + await c.db.execute("DELETE FROM fuzz_orders"); + await c.db.execute("DELETE FROM fuzz_inventory"); + await c.db.execute("DELETE FROM fuzz_rel_products"); + await c.db.execute("DELETE FROM fuzz_rel_users"); + await c.db.execute("DELETE FROM fuzz_indexed"); + await c.db.execute("DELETE FROM fuzz_trigger_audit"); + await c.db.execute("DELETE FROM fuzz_edge_expectations"); + await c.db.execute("DELETE FROM fuzz_edge_payloads"); + await c.db.execute("DELETE FROM fuzz_transfer_events"); + await c.db.execute("DELETE FROM fuzz_accounts"); + await c.db.execute("DELETE FROM fuzz_item_events"); + await c.db.execute("DELETE FROM fuzz_items"); + await ensureAccounts(c.db); + return await validate(c.db); + }, + runPhase: async (c, input) => { + const mode2 = input.mode ?? "balanced"; + const iterations = Math.max(1, Math.floor(input.iterations)); + const keySpace = Math.max( + 1, + Math.floor(input.keySpace ?? DEFAULT_KEY_SPACE) + ); + const maxPayloadBytes = Math.max( + 1, + Math.floor(input.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES) + ); + const growthTargetBytes = Math.max( + 1, + Math.floor( + input.growthTargetBytes ?? DEFAULT_GROWTH_TARGET_BYTES + ) + ); + const rng = makeRng(`${input.seed}:${input.phase}:${mode2}`); + const ops = {}; + let stage = "ensureAccounts"; + try { + await ensureAccounts(c.db); + for (let i = 0; i < iterations; i += 1) { + const kind = chooseKind(mode2, rng); + ops[kind] = (ops[kind] ?? 0) + 1; + stage = `base:${kind}:iteration:${i}`; + if (kind === "transfer") { + const fromIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1); + let toIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1); + if (toIndex === fromIndex) + toIndex = (toIndex + 1) % ACCOUNT_COUNT; + const fromAccount = `acct-${fromIndex}`; + const toAccount = `acct-${toIndex}`; + try { + await applyTransfer(c.db, { + phase: input.phase, + localIndex: i, + fromAccount, + toAccount, + amount: intBetween(rng, 1, 500) + }); + } catch (error) { + throw new Error( + `base operation transfer failed at iteration ${i} from ${fromAccount} to ${toAccount}`, + { cause: error } + ); + } + } else if (kind === "hot") { + const itemKey = `hot-${intBetween(rng, 0, 3)}`; + const updates = intBetween( + rng, + 2, + mode2 === "hot" ? 12 : 5 + ); + try { + await applyHotUpdates(c.db, { + seed: input.seed, + phase: input.phase, + localIndex: i, + itemKey, + updates, + payloadBytes: intBetween( + rng, + 1, + maxPayloadBytes + ) + }); + } catch (error) { + throw new Error( + `base operation hot failed at iteration ${i} for ${itemKey} with ${updates} updates`, + { cause: error } + ); + } + } else { + const itemKey = mode2 === "hot" && rng() < 0.6 ? `hot-${intBetween(rng, 0, 3)}` : `item-${intBetween(rng, 0, keySpace - 1)}`; + const payloadBytes = mode2 === "payloads" ? intBetween( + rng, + Math.min(256, maxPayloadBytes), + maxPayloadBytes + ) : intBetween(rng, 1, maxPayloadBytes); + try { + await applyItemOperation(c.db, { + seed: input.seed, + phase: input.phase, + localIndex: i, + kind, + itemKey, + payloadBytes + }); + } catch (error) { + throw new Error( + `base operation ${kind} failed at iteration ${i} for ${JSON.stringify(itemKey)} with payloadBytes ${payloadBytes}`, + { cause: error } + ); + } + } + } + stage = "deep-scenarios"; + await applyDeepScenarios(c.db, { + seed: input.seed, + phase: input.phase, + mode: mode2, + iterations, + rng, + maxPayloadBytes, + growthTargetBytes, + ops + }); + stage = "validate"; + return { + seed: input.seed, + phase: input.phase, + mode: mode2, + iterations, + ops, + validation: await validate(c.db) + }; + } catch (error) { + const detail = error instanceof Error ? error.message : typeof error === "string" ? error : String(error); + throw new Error( + `runPhase failed during ${stage} for mode ${mode2} phase ${input.phase} seed ${input.seed}: ${detail}`, + { cause: error } + ); } - }); + }, + validate: async (c) => { + await ensureAccounts(c.db); + return await validate(c.db); + }, + debugItemMismatches: async (c, limit) => { + await ensureAccounts(c.db); + return await debugItemMismatches(c.db, limit ?? 5); + }, + goToSleep: (c) => { + c.sleep(); + return { ok: true }; + } + } +}); + +// src/actors/testing/sigterm-sleep-probe.ts +import { + actor as actor43 +} from "rivetkit"; +import { db as db9 } from "rivetkit/db"; +var DEFAULT_ON_SLEEP_DURATION_MS = 5e3; +var DEFAULT_ON_SLEEP_TICK_MS = 1e3; +var SLEEP_TIMEOUT_MS = 10 * 60 * 1e3; +var SLEEP_GRACE_PERIOD_MS = 30 * 60 * 1e3; +var ACTOR_STOPPED_CLOSE_CODE = 1e3; +var ACTOR_STOPPED_CLOSE_REASON = "actor stopped"; +function sleep5(ms) { + return new Promise((resolve2) => setTimeout(resolve2, ms)); +} +function formatError(error) { + if (error instanceof Error) return error.stack ?? error.message; + return String(error); +} +var sigtermSleepProbe = actor43({ + state: { + label: "unprepared", + wakeCount: 0, + sleepCount: 0, + onSleepDurationMs: DEFAULT_ON_SLEEP_DURATION_MS, + onSleepTickMs: DEFAULT_ON_SLEEP_TICK_MS, + connectionCount: 0, + messageCount: 0, + onSleepStartedAt: null, + onSleepAsyncFinishedAt: null, + onSleepFinishedAt: null, + onSleepLastError: null }, - async onRequest(c, request) { - const url = new URL(request.url); - if (url.pathname === "/bypass" || url.pathname === "/request/bypass") { - const [sleepState] = typedRows2( - await c.db.execute( - "SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1" - ) - ); - return new Response(JSON.stringify({ - type: "bypass", - transport: "http", - sleepStarted: sleepState !== void 0, - sleepStartedAt: sleepState?.sleep_started_at ?? null, - timestamp: Date.now() - }), { - headers: { - "content-type": "application/json" - } - }); + createVars: () => ({ + websockets: /* @__PURE__ */ new Set() + }), + db: db9({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS sigterm_sleep_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + event TEXT NOT NULL, + sleep_count INTEGER NOT NULL, + detail TEXT, + created_at INTEGER NOT NULL + ) + `); } - return new Response("not found", { status: 404 }); - }, - onWebSocket(c, websocket) { + }), + onWebSocket: (c, websocket) => { + c.vars.websockets.add(websocket); + c.state.connectionCount += 1; const connectionId = crypto.randomUUID(); - let activeInference; - const removeDebugSocket = addDebugSocket(c.actorId, websocket); - send(websocket, { - type: "hello", + c.log.info({ + msg: "sigterm sleep probe websocket connected", + label: c.state.label, connectionId, - timestamp: Date.now() + connectionCount: c.state.connectionCount }); - void (async () => { + websocket.send( + JSON.stringify({ + type: "welcome", + connectionId, + label: c.state.label, + connectionCount: c.state.connectionCount + }) + ); + websocket.addEventListener("message", (event19) => { + c.state.messageCount += 1; + const data = event19.data; + if (typeof data !== "string") return; try { - await replayDebugEvents(c.db, websocket); - } catch (error) { - c.log.warn({ - msg: "mock agentic debug replay failed", - err: error instanceof Error ? error.message : String(error) - }); + const parsed = JSON.parse(data); + if (parsed.type === "ping") { + websocket.send( + JSON.stringify({ + type: "pong", + connectionId, + messageCount: c.state.messageCount, + timestamp: Date.now() + }) + ); + return; + } + } catch { } - await recordDebugEvent(c, { - name: "webSocketOpen", - connectionId - }); - })(); - const verify = async (requestId, expectedSeconds) => { - const rows = typedRows2( - await c.db.execute( - "SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC", - requestId - ) - ); - return { - type: "verified", - requestId, - ...verifyEntryRows(rows, expectedSeconds) - }; - }; - const sleepStatus = async () => { - const [sleepState] = typedRows2( - await c.db.execute( - "SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1" - ) + websocket.send( + JSON.stringify({ + type: "echo", + connectionId, + received: data, + messageCount: c.state.messageCount, + timestamp: Date.now() + }) ); - return { - sleepStarted: sleepState !== void 0, - sleepStartedAt: sleepState?.sleep_started_at ?? null - }; - }; - const runInference = async (requestId, seconds) => { - send(websocket, { - type: "started", - requestId, - seconds, - timestamp: Date.now() + }); + websocket.addEventListener("close", (event19) => { + c.vars.websockets.delete(websocket); + c.state.connectionCount -= 1; + c.log.info({ + msg: "sigterm sleep probe websocket closed", + label: c.state.label, + connectionId, + connectionCount: c.state.connectionCount, + code: event19.code, + reason: event19.reason }); + }); + }, + onWake: async (c) => { + c.state.wakeCount += 1; + c.log.info({ + msg: "sigterm sleep probe onWake", + label: c.state.label, + wakeCount: c.state.wakeCount, + sleepCount: c.state.sleepCount + }); + await c.db.execute( + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "wake", + c.state.sleepCount, + `wake-${c.state.wakeCount}`, + Date.now() + ); + }, + onSleep: async (c) => { + const sleepCount = c.state.sleepCount + 1; + const startedAt = Date.now(); + c.state.sleepCount = sleepCount; + c.state.onSleepStartedAt = startedAt; + c.state.onSleepAsyncFinishedAt = null; + c.state.onSleepFinishedAt = null; + c.state.onSleepLastError = null; + c.log.info({ + msg: "sigterm sleep probe onSleep start", + label: c.state.label, + sleepCount, + onSleepDurationMs: c.state.onSleepDurationMs, + onSleepTickMs: c.state.onSleepTickMs + }); + try { + for (const websocket of c.vars.websockets) { + if (websocket.readyState !== 1) continue; + websocket.send( + JSON.stringify({ + type: "onSleepStarted", + sleepCount, + onSleepDurationMs: c.state.onSleepDurationMs, + onSleepTickMs: c.state.onSleepTickMs, + timestamp: startedAt + }) + ); + } await c.db.execute( - "DELETE FROM mock_agentic_entries WHERE request_id = ?", - requestId + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "on-sleep-start", + sleepCount, + c.state.label, + startedAt ); - for (let idx = 1; idx <= seconds; idx += 1) { - await sleep3(1e3); - const createdAt = Date.now(); + const deadline = startedAt + c.state.onSleepDurationMs; + let tickIndex = 0; + while (Date.now() < deadline) { + const waitMs = Math.min( + c.state.onSleepTickMs, + Math.max(0, deadline - Date.now()) + ); + if (waitMs > 0) await sleep5(waitMs); + tickIndex += 1; + const tickAt = Date.now(); + const detail = `tick=${tickIndex} elapsed-ms=${tickAt - startedAt}`; await c.db.execute( - "INSERT INTO mock_agentic_entries (request_id, idx, created_at) VALUES (?, ?, ?)", - requestId, - idx, - createdAt + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "on-sleep-tick", + sleepCount, + detail, + tickAt ); - send(websocket, { - type: "progress", - requestId, - idx, - seconds, - createdAt + c.log.info({ + msg: "sigterm sleep probe onSleep tick", + label: c.state.label, + sleepCount, + tickIndex, + elapsedMs: tickAt - startedAt }); - } - const verification = await verify(requestId, seconds); - send(websocket, { - type: "done", - requestId, - seconds, - timestamp: Date.now(), - verification - }); - }; - websocket.addEventListener("message", async (event21) => { - try { - if (typeof event21.data !== "string") { - throw new Error("message data must be a JSON string"); - } - const message = JSON.parse(event21.data); - const type = stringValue(message.type, "type"); - if (type === "history") { - const rows = typedRows2( - await c.db.execute( - "SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY created_at ASC, request_id ASC, idx ASC" - ) - ); - const [count] = typedRows2( - await c.db.execute( - "SELECT COUNT(*) AS count FROM mock_agentic_entries" - ) - ); - send(websocket, { - type: "history", - totalRows: count?.count ?? rows.length, - entries: rows, - timestamp: Date.now() - }); - return; - } - if (type === "ping") { - send(websocket, { - type: "pong", - probeId: stringValue(message.probeId, "probeId"), - ...await sleepStatus(), - timestamp: Date.now() - }); - return; - } - if (type === "verify") { - const requestId = stringValue(message.requestId, "requestId"); - const expectedSeconds = positiveInteger3( - message.expectedSeconds, - "expectedSeconds" - ); - send(websocket, await verify(requestId, expectedSeconds)); - return; - } - if (type === "infer") { - const requestId = stringValue(message.requestId, "requestId"); - const seconds = positiveInteger3(message.seconds, "seconds"); - await recordDebugEvent(c, { - name: "inferenceRequested", - connectionId, - requestId, - details: { - seconds - } - }); - const previousInference = activeInference; - const inference = (async () => { - await previousInference?.catch(() => void 0); - await runInference(requestId, seconds); - })(); - activeInference = inference; - await c.keepAwake(inference); - if (activeInference === inference) { - activeInference = void 0; - } - return; + for (const websocket of c.vars.websockets) { + if (websocket.readyState !== 1) continue; + websocket.send( + JSON.stringify({ + type: "onSleepTick", + sleepCount, + tickIndex, + elapsedMs: tickAt - startedAt, + timestamp: tickAt + }) + ); } - throw new Error(`unknown message type: ${type}`); - } catch (error) { - send(websocket, { - type: "error", - message: error instanceof Error ? error.message : "unknown websocket error", - timestamp: Date.now() - }); } - }); - websocket.addEventListener("close", async () => { - removeDebugSocket(); - await recordDebugEvent(c, { - name: "webSocketClose", - connectionId + const asyncFinishedAt = Date.now(); + c.state.onSleepAsyncFinishedAt = asyncFinishedAt; + await c.db.execute( + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "on-sleep-after-await", + sleepCount, + `delay-ms=${asyncFinishedAt - startedAt}`, + asyncFinishedAt + ); + const finishedAt = Date.now(); + c.state.onSleepFinishedAt = finishedAt; + await c.db.execute( + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "on-sleep-finish", + sleepCount, + c.state.label, + finishedAt + ); + for (const websocket of c.vars.websockets) { + if (websocket.readyState !== 1) continue; + websocket.send( + JSON.stringify({ + type: "onSleepFinished", + sleepCount, + elapsedMs: finishedAt - startedAt, + timestamp: finishedAt + }) + ); + websocket.close( + ACTOR_STOPPED_CLOSE_CODE, + ACTOR_STOPPED_CLOSE_REASON + ); + } + c.log.info({ + msg: "sigterm sleep probe onSleep finish", + label: c.state.label, + sleepCount, + elapsedMs: finishedAt - startedAt }); - }); + } catch (error) { + const message = formatError(error); + c.state.onSleepLastError = message; + c.log.error({ + msg: "sigterm sleep probe onSleep error", + label: c.state.label, + sleepCount, + error: message + }); + throw error; + } }, actions: { - verify: async (c, requestId, expectedSeconds) => { - const rows = typedRows2( - await c.db.execute( - "SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC", - requestId - ) + prepare: async (c, label = `sigterm-sleep-probe-${Date.now()}`, onSleepDurationMs = DEFAULT_ON_SLEEP_DURATION_MS, onSleepTickMs = DEFAULT_ON_SLEEP_TICK_MS) => { + if (!Number.isFinite(onSleepDurationMs) || onSleepDurationMs < 0) { + throw new Error( + "onSleepDurationMs must be a finite non-negative number" + ); + } + if (!Number.isFinite(onSleepTickMs) || onSleepTickMs <= 0) { + throw new Error( + "onSleepTickMs must be a finite positive number" + ); + } + c.state.label = label; + c.state.onSleepDurationMs = onSleepDurationMs; + c.state.onSleepTickMs = onSleepTickMs; + await c.db.execute( + "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", + "prepared", + c.state.sleepCount, + label, + Date.now() ); return { - requestId, - expectedSeconds, - count: rows.length, - indexes: rows.map((row) => row.idx) + label: c.state.label, + onSleepDurationMs: c.state.onSleepDurationMs, + onSleepTickMs: c.state.onSleepTickMs, + wakeCount: c.state.wakeCount, + sleepCount: c.state.sleepCount, + connectionCount: c.state.connectionCount, + messageCount: c.state.messageCount }; }, - verifyAll: async (c, expectedRequests) => { - if (!Array.isArray(expectedRequests)) { - throw new Error("expectedRequests must be an array"); - } - for (const request of expectedRequests) { - stringValue(request.requestId, "requestId"); - positiveInteger3(request.seconds, "seconds"); - } - const rows = typedRows2( - await c.db.execute( - "SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY request_id ASC, idx ASC" - ) - ); - return verifyAllRows(rows, expectedRequests); + getProof: async (c) => { + const rows = await c.db.execute("SELECT * FROM sigterm_sleep_log ORDER BY id"); + return { + state: { + label: c.state.label, + wakeCount: c.state.wakeCount, + sleepCount: c.state.sleepCount, + onSleepDurationMs: c.state.onSleepDurationMs, + onSleepTickMs: c.state.onSleepTickMs, + connectionCount: c.state.connectionCount, + messageCount: c.state.messageCount, + onSleepStartedAt: c.state.onSleepStartedAt, + onSleepAsyncFinishedAt: c.state.onSleepAsyncFinishedAt, + onSleepFinishedAt: c.state.onSleepFinishedAt, + onSleepLastError: c.state.onSleepLastError + }, + rows + }; } + }, + options: { + canHibernateWebSocket: false, + sleepTimeout: SLEEP_TIMEOUT_MS, + sleepGracePeriod: SLEEP_GRACE_PERIOD_MS } }); // src/actors/testing/sleep-close-fuzz.ts -import { actor as actor55 } from "rivetkit"; -var sleepCloseFuzz = actor55({ +import { + actor as actor44 +} from "rivetkit"; +var sleepCloseFuzz = actor44({ options: { canHibernateWebSocket: false }, @@ -10102,13 +7675,13 @@ var sleepCloseFuzz = actor55({ }) ); }, 500); - websocket.addEventListener("message", (event21) => { + websocket.addEventListener("message", (event19) => { c.state.messageCount += 1; websocket.send( JSON.stringify({ type: "echo", connectionId, - received: event21.data + received: event19.data }) ); }); @@ -10127,183 +7700,10 @@ var sleepCloseFuzz = actor55({ } }); -// src/actors/testing/load-test-agent.ts -import { actor as actor56 } from "rivetkit"; -import { db as db12 } from "rivetkit/db"; -var DEFAULT_TOKENS_PER_SECOND = 20; -var DEFAULT_DURATION_MS = 5e3; -function send2(websocket, payload2) { - if (websocket.readyState !== 1) return; - websocket.send(JSON.stringify(payload2)); -} -function parsePositiveNumber(value, name, fallback) { - if (value === void 0 || value === null) return fallback; - const parsed = Number(value); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new Error(`${name} must be a positive number`); - } - return parsed; -} -function sleep4(ms, signal) { - if (signal.aborted) return Promise.resolve(); - return new Promise((resolve) => { - const timeout = setTimeout(resolve, ms); - signal.addEventListener( - "abort", - () => { - clearTimeout(timeout); - resolve(); - }, - { once: true } - ); - }); -} -var loadTestAgent = actor56({ - options: { - canHibernateWebSocket: false, - sleepGracePeriod: 3e4 - }, - db: db12({ - onMigrate: async (db16) => { - await db16.execute(` - CREATE TABLE IF NOT EXISTS messages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - connection_id TEXT NOT NULL, - request_id TEXT NOT NULL, - token_index INTEGER NOT NULL, - token TEXT NOT NULL, - created_at INTEGER NOT NULL - ) - `); - await db16.execute(` - CREATE INDEX IF NOT EXISTS messages_request_idx - ON messages (request_id, token_index) - `); - } - }), - state: { - connectionCount: 0, - inferenceCount: 0, - tokenCount: 0 - }, - onWebSocket(c, websocket) { - c.state.connectionCount += 1; - const connectionId = crypto.randomUUID(); - send2(websocket, { - type: "connected", - connectionId, - connectionCount: c.state.connectionCount, - timestamp: Date.now() - }); - websocket.addEventListener("message", async (event21) => { - try { - const message = typeof event21.data === "string" ? JSON.parse(event21.data) : void 0; - if (message && message.type === "ping") { - send2(websocket, { - type: "pong", - connectionId, - id: message.id, - timestamp: Date.now() - }); - return; - } - if (!message || message.type !== "inference") { - throw new Error("expected inference message"); - } - const requestId = typeof message.requestId === "string" && message.requestId ? message.requestId : crypto.randomUUID(); - const tokensPerSecond = parsePositiveNumber( - message.tokensPerSecond, - "tokensPerSecond", - DEFAULT_TOKENS_PER_SECOND - ); - const durationMs = parsePositiveNumber( - message.durationMs, - "durationMs", - DEFAULT_DURATION_MS - ); - const intervalMs = 1e3 / tokensPerSecond; - const targetTokens = Math.max( - 1, - Math.floor(durationMs / 1e3 * tokensPerSecond) - ); - const inference = (async () => { - c.state.inferenceCount += 1; - send2(websocket, { - type: "inference-start", - connectionId, - requestId, - tokensPerSecond, - durationMs, - targetTokens, - timestamp: Date.now() - }); - const startedAt = performance.now(); - for (let i = 0; i < targetTokens; i++) { - if (c.abortSignal.aborted || websocket.readyState !== 1) { - break; - } - const tokenIndex = i + 1; - const token = `token-${tokenIndex}`; - const createdAt = Date.now(); - await c.db.execute( - "INSERT INTO messages (connection_id, request_id, token_index, token, created_at) VALUES (?, ?, ?, ?, ?)", - connectionId, - requestId, - tokenIndex, - token, - createdAt - ); - c.state.tokenCount += 1; - send2(websocket, { - type: "token", - connectionId, - requestId, - tokenIndex, - token, - timestamp: createdAt - }); - const nextAt = startedAt + tokenIndex * intervalMs; - const delayMs = Math.max(0, nextAt - performance.now()); - if (delayMs > 0) { - await sleep4(delayMs, c.abortSignal); - } - } - send2(websocket, { - type: "inference-complete", - connectionId, - requestId, - tokenCount: targetTokens, - timestamp: Date.now() - }); - })(); - await c.keepAwake(inference); - } catch (error) { - send2(websocket, { - type: "error", - message: error instanceof Error ? error.message : "unknown websocket error", - timestamp: Date.now() - }); - } - }); - websocket.addEventListener("close", () => { - c.state.connectionCount -= 1; - }); - }, - actions: { - getStats(c) { - return { - connectionCount: c.state.connectionCount, - inferenceCount: c.state.inferenceCount, - tokenCount: c.state.tokenCount - }; - } - } -}); - -// src/actors/testing/load-test-agent-2.ts -import { actor as actor57 } from "rivetkit"; -import { db as db13 } from "rivetkit/db"; -var AsyncMutex = class { +// src/actors/testing/slow-reconnect-actor.ts +import { actor as actor45, setup } from "rivetkit"; +import { db as db10 } from "rivetkit/db"; +var AsyncMutex2 = class { locked = false; waiters = []; async acquire() { @@ -10311,7 +7711,7 @@ var AsyncMutex = class { this.locked = true; return; } - await new Promise((resolve) => this.waiters.push(resolve)); + await new Promise((resolve2) => this.waiters.push(resolve2)); this.locked = true; } release() { @@ -10323,49 +7723,47 @@ var AsyncMutex = class { this.locked = false; } }; -function createSerializedDb(execute) { - const mutex = new AsyncMutex(); +function createDb(execute) { + const mutex = new AsyncMutex2(); let activeTransaction = null; const createTransactionDb = () => { const tx = Object.assign( (query, ...values) => execute(query, ...values), { - withTransaction: async (_stats, fn) => fn(tx) + withTransaction: async (fn) => fn(tx) } ); return tx; }; const queryWithMutex = async (query, ...values) => { + if (activeTransaction) { + return activeTransaction(query, ...values); + } await mutex.acquire(); try { return await execute(query, ...values); } finally { mutex.release(); } - }; - return Object.assign(queryWithMutex, { - withTransaction: async (stats, fn) => { + }; + const sql = Object.assign(queryWithMutex, { + withTransaction: async (fn) => { if (activeTransaction) { return fn(activeTransaction); } await mutex.acquire(); const tx = createTransactionDb(); try { - await executeTrackedQuery(execute, stats, "transaction-begin", "BEGIN"); + await execute("BEGIN"); activeTransaction = tx; try { const result = await fn(tx); activeTransaction = null; - await executeTrackedQuery(execute, stats, "transaction-commit", "COMMIT"); + await execute("COMMIT"); return result; } catch (error) { activeTransaction = null; - await executeTrackedQuery( - execute, - stats, - "transaction-rollback", - "ROLLBACK" - ); + await execute("ROLLBACK"); throw error; } } finally { @@ -10374,75 +7772,52 @@ function createSerializedDb(execute) { } } }); + return sql; } -var MESSAGE_COUNT = 84; -var MESSAGE_TOOL_REF_COUNT = 122; -var TOOL_CALL_COUNT = 61; -var EXECUTOR_TOOL_COUNT = 42; -var THREAD_EVENT_COUNT = 233; -var MESSAGE_CONTENT_BYTES = 2600; -var THREAD_EVENT_PAYLOAD_BYTES = 1e3; -var TOOL_CALL_RESULT_BYTES = 2700; -var EXECUTOR_TOOL_SCHEMA_BYTES = 550; -var SLOW_QUERY_MS = 1e3; -function send3(websocket, message) { - if (websocket.readyState === 1) { - websocket.send(JSON.stringify(message)); - } -} -var loadTestAgent2 = actor57({ - options: { - canHibernateWebSocket: false, - sleepGracePeriod: 1e3 - }, - state: { - runCount: 0, - wakeCount: 0, - queryStats: createAgentConcurrent2QueryStats() - }, - db: db13({ +var MESSAGE_COUNT2 = 84; +var MESSAGE_TOOL_REF_COUNT2 = 122; +var TOOL_CALL_COUNT2 = 61; +var EXECUTOR_TOOL_COUNT2 = 42; +var THREAD_EVENT_COUNT2 = 233; +var MESSAGE_CONTENT_BYTES2 = 10620; +var THREAD_EVENT_PAYLOAD_BYTES2 = 4036; +var TOOL_CALL_RESULT_BYTES2 = 10975; +var EXECUTOR_TOOL_SCHEMA_BYTES2 = 2235; +var slowReconnectActor = actor45({ + state: { runCount: 0 }, + db: db10({ onMigrate: async (database) => { - await createAgentConcurrent2Schema(database); - await seedAgentConcurrent2Data(database); + await createSlowReconnectSchema(database); } }), - vars: { - sql: null, - wakeStats: null, - wakeStartedAt: null, - wakeIteration: 0 - }, - onWebSocket: (c, websocket) => { - send3(websocket, { - type: "connected", - timestamp: Date.now() - }); - websocket.addEventListener("message", (event21) => { - const promise = handleAgentConcurrent2Message(c, websocket, event21.data); + vars: { sql: null }, + onWebSocket: (c, ws) => { + const sock = ws; + if (sock.readyState === WebSocket.OPEN) { + sock.send("pong"); + } + ws.addEventListener("message", (event19) => { + const promise = handleSlowReconnectWebSocketMessage( + c, + sock, + event19.data + ); void c.keepAwake(promise); }); }, actions: { - run: async (c, clientId) => { - const runtime = ensureAgentConcurrent2Runtime(c); + prepare: async (c) => { + await createSlowReconnectSchema(c.db); + return await seedSlowReconnectData(c.db); + }, + reproReconnect: async (c, clientId) => { + c.vars.sql ??= createSlowReconnectDb(c.db); c.state.runCount++; - runtime.vars.wakeIteration++; - const cycleStats = createAgentConcurrent2QueryStats(); - const stats = createAgentConcurrent2StatsSet( - cycleStats, - runtime.wakeStats, - c.state.queryStats - ); - const result = await runAgentConcurrent2Workload( - runtime.sql, - clientId ?? `agent2-action-${c.state.runCount}`, - 0, - stats + return await runReconnectRepro( + c.vars.sql, + clientId ?? `action-${c.state.runCount}`, + 0 ); - return { - ...result, - stats: snapshotAgentConcurrent2Stats(c, cycleStats) - }; }, getRunCount: (c) => c.state.runCount, sleep: (c) => { @@ -10451,2337 +7826,5699 @@ var loadTestAgent2 = actor57({ } } }); -async function handleAgentConcurrent2Message(c, websocket, data) { +async function handleSlowReconnectWebSocketMessage(c, sock, data) { + if (data === "ping") { + if (sock.readyState === WebSocket.OPEN) { + sock.send("pong"); + } + return; + } let trigger = "unknown"; - let cycleStats = null; try { - const request = parseAgentConcurrent2Request(data); + const request = parseSlowReconnectRequest(data); trigger = request.type; - if (request.type === "ping") { - send3(websocket, { - type: "pong", - id: request.id, - timestamp: Date.now() - }); - return; - } - if (request.type === "force_sleep") { - send3(websocket, { type: "sleeping", timestamp: Date.now() }); - c.sleep(); - return; - } - const runtime = ensureAgentConcurrent2Runtime(c); + c.vars.sql ??= createSlowReconnectDb(c.db); c.state.runCount++; - runtime.vars.wakeIteration++; - cycleStats = createAgentConcurrent2QueryStats(); - const stats = createAgentConcurrent2StatsSet( - cycleStats, - runtime.wakeStats, - c.state.queryStats - ); - if (request.type === "agent2_resume") { + if (request.type === "client_resume") { const startedAt = performance.now(); - const result2 = await runCatchupSnapshot( - runtime.sql, - request.version, - stats + const result2 = await runCatchupSnapshot2( + c.vars.sql, + request.version ); - send3(websocket, { - type: "agent2_result", + sendJSON(sock, { + type: "slow_reconnect_result", trigger: request.type, totalMs: Math.round(performance.now() - startedAt), - results: [result2], - stats: snapshotAgentConcurrent2Stats(c, cycleStats) + results: [result2] }); return; } - const result = await runAgentConcurrent2Workload( - runtime.sql, - request.clientId, - request.staggerHandleMs ?? 0, - stats + const clientId = request.type === "executor_connect" ? request.clientId : request.clientId ?? `slow-reconnect-${c.state.runCount}`; + const staggerHandleMs = request.type === "repro_reconnect" ? request.staggerHandleMs ?? 0 : 0; + const result = await runReconnectRepro( + c.vars.sql, + clientId, + staggerHandleMs ); - send3(websocket, { - type: "agent2_result", + if (request.type === "executor_connect") { + sendJSON(sock, { + type: "executor_connected", + executorId: clientId, + registeredToolCount: EXECUTOR_TOOL_COUNT2, + guidanceInventory: [], + resumeBootstrap: true + }); + } + sendJSON(sock, { + type: "slow_reconnect_result", trigger: request.type, - ...result, - stats: snapshotAgentConcurrent2Stats(c, cycleStats) + ...result }); } catch (error) { - send3(websocket, { - type: "agent2_error", + sendJSON(sock, { + type: "slow_reconnect_error", trigger, - error: error instanceof Error ? error.message : String(error), - ...cycleStats ? { stats: snapshotAgentConcurrent2Stats(c, cycleStats) } : {} + error: error instanceof Error ? error.message : String(error) }); } } -function parseAgentConcurrent2Request(data) { +function parseSlowReconnectRequest(data) { if (typeof data !== "string") { - throw new Error("agent concurrent 2 request must be a string"); + throw new Error("slowReconnectActor request must be a string"); } const parsed = JSON.parse(data); if (!parsed || typeof parsed !== "object") { - throw new Error("agent concurrent 2 request must be an object"); + throw new Error("slowReconnectActor request must be an object"); } const request = parsed; - if (request.type === "ping") { + if (request.type === "client_resume") { return { - type: "ping", - ...typeof request.id === "number" ? { id: request.id } : {} + type: "client_resume", + version: numberField2(request, "version") }; } - if (request.type === "force_sleep") { - return { type: "force_sleep" }; - } - if (request.type === "agent2_resume") { - return { type: "agent2_resume", version: numberField(request, "version") }; + if (request.type === "executor_connect") { + const executorType = request.executorType; + return { + type: "executor_connect", + clientId: stringField2(request, "clientId"), + ...executorType === "local-client" || executorType === "sandbox" || executorType === "virtual" ? { executorType } : {} + }; } - if (request.type === "agent2_connect") { + if (request.type === "repro_reconnect") { return { - type: "agent2_connect", - clientId: stringField(request, "clientId"), + type: "repro_reconnect", + ...typeof request.clientId === "string" ? { clientId: request.clientId } : {}, ...typeof request.staggerHandleMs === "number" ? { staggerHandleMs: request.staggerHandleMs } : {} }; } - throw new Error(`unknown agent concurrent 2 request type: ${String(request.type)}`); + throw new Error( + `Unknown slowReconnectActor request type: ${String(request.type)}` + ); } -function stringField(record, field) { +function stringField2(record, field) { const value = record[field]; if (typeof value !== "string" || value.length === 0) { - throw new Error(`agent concurrent 2 request ${field} must be a string`); + throw new Error( + `slowReconnectActor request ${field} must be a non-empty string` + ); } return value; } -function numberField(record, field) { +function numberField2(record, field) { const value = record[field]; if (typeof value !== "number" || !Number.isFinite(value)) { - throw new Error(`agent concurrent 2 request ${field} must be a finite number`); + throw new Error( + `slowReconnectActor request ${field} must be a finite number` + ); } return value; } -function createAgentConcurrent2Db(db16) { - return createSerializedDb(async (query, ...values) => { - const converted = values.map( - (value) => typeof value === "boolean" ? value ? 1 : 0 : value - ); - return await db16.execute(query, ...converted); - }); -} -function ensureAgentConcurrent2Runtime(c) { - c.vars.sql ??= createAgentConcurrent2Db(c.db); - c.state.queryStats ??= createAgentConcurrent2QueryStats(); - c.state.wakeCount ??= 0; - if (!c.vars.wakeStats) { - c.vars.wakeStats = createAgentConcurrent2QueryStats(); - c.vars.wakeStartedAt = Date.now(); - c.vars.wakeIteration = 0; - c.state.wakeCount++; +function sendJSON(sock, message) { + if (sock.readyState === WebSocket.OPEN) { + sock.send(JSON.stringify(message)); } - return { - sql: c.vars.sql, - wakeStats: c.vars.wakeStats, - vars: c.vars - }; } -function createAgentConcurrent2QueryStats() { +function createSlowReconnectDb(db17) { + return createDb( + async (query, ...values) => { + const converted = values.map( + (value) => typeof value === "boolean" ? value ? 1 : 0 : value + ); + return await db17.execute(query, ...converted); + } + ); +} +async function runReconnectRepro(sql, clientId, staggerHandleMs) { + const startedAt = performance.now(); + const buildToolPlanContext = await runBuildToolPlanContext2(sql); + const catchupSnapshot = await runCatchupSnapshot2(sql, 0); + const recoverToolCalls = await runRecoverToolCalls2(sql); + const handleExecutorConnect = await delay2(staggerHandleMs).then( + () => runHandleExecutorConnect(sql, clientId) + ); + const results = [ + handleExecutorConnect, + buildToolPlanContext, + catchupSnapshot, + recoverToolCalls + ]; return { - total: 0, - reads: 0, - mutations: 0, - tx: 0, - other: 0, - rows: 0, - errors: 0, - slow: 0, - maxMs: 0, - maxStep: "", - byOperation: {}, - byTable: {} + totalMs: Math.round(performance.now() - startedAt), + results }; } -function createAgentConcurrent2StatsSet(cycle, wake, actor61) { - return { cycle, wake, actor: actor61 }; -} -function snapshotAgentConcurrent2Stats(c, cycle) { +async function runHandleExecutorConnect(sql, clientId) { + const startedAt = performance.now(); + const steps = []; + const nextSeq = await sql.withTransaction(async (tx) => { + const latestExecutor = await timedQuery2( + tx, + steps, + "load-latest-executor-id", + `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` + ); + const latestExecutorId = String( + latestExecutor[0]?.executor_id ?? "seed-executor" + ); + await timedQuery2( + tx, + steps, + "select-cached-executor-tools", + `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, + latestExecutorId + ); + const executorType = await timedQuery2( + tx, + steps, + "select-executor-type", + `SELECT value FROM thread_meta_kv WHERE key = 'executor_type'` + ); + if (!executorType[0]?.value) { + await timedQuery2( + tx, + steps, + "set-executor-type", + `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`, + "local-client", + (/* @__PURE__ */ new Date()).toISOString() + ); + } + const sandboxIntent = await timedQuery2( + tx, + steps, + "select-sandbox-intent", + `SELECT value FROM thread_meta_kv WHERE key = 'sandbox_intent'` + ); + if (hasPendingLaunch2(sandboxIntent[0]?.value)) { + await timedQuery2( + tx, + steps, + "clear-pending-launch", + `UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'sandbox_intent'`, + JSON.stringify({ spec: null, pendingLaunch: null }), + (/* @__PURE__ */ new Date()).toISOString() + ); + } + const seqRows = await timedQuery2( + tx, + steps, + "select-next-thread-event-seq", + `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` + ); + const seq = Number(seqRows[0]?.seq ?? 1); + await timedQuery2( + tx, + steps, + "insert-executor-connected-event", + `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, + seq, + "executor_connected", + JSON.stringify({ + type: "executor_connected", + executorId: clientId + }), + (/* @__PURE__ */ new Date()).toISOString() + ); + return seq; + }); + steps.push({ + name: "transaction-total", + durationMs: Math.round(performance.now() - startedAt), + rowCount: nextSeq + }); return { - wakeIndex: c.state.wakeCount, - actorIteration: c.state.runCount, - wakeIteration: c.vars.wakeIteration, - cycle: cloneAgentConcurrent2QueryStats(cycle), - wake: cloneAgentConcurrent2QueryStats( - c.vars.wakeStats ?? createAgentConcurrent2QueryStats() - ), - actor: cloneAgentConcurrent2QueryStats(c.state.queryStats) + name: "handle-executor-connect", + totalMs: Math.round(performance.now() - startedAt), + steps }; } -function cloneAgentConcurrent2QueryStats(stats) { +async function runBuildToolPlanContext2(sql) { + const startedAt = performance.now(); + const steps = []; + const latestExecutor = await timedQuery2( + sql, + steps, + "load-latest-executor-id", + `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` + ); + const latestExecutorId = String( + latestExecutor[0]?.executor_id ?? "seed-executor" + ); + await timedQuery2( + sql, + steps, + "select-executor-tools", + `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, + latestExecutorId + ); + await timedQuery2( + sql, + steps, + "count-uncancelled-top-level", + `SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL` + ); + const unresolvedRows = await timedQuery2( + sql, + steps, + "find-unresolved-assistant-message", + `SELECT m.* + FROM message_tool_refs AS tool_use + JOIN messages AS m + ON m.message_id = tool_use.assistant_message_id + WHERE tool_use.block_type = 'tool_use' + AND tool_use.cancelled = 0 + AND m.cancelled = 0 + AND m.role = 'assistant' + AND m.parent_tool_use_id IS NULL + AND NOT EXISTS ( + SELECT 1 + FROM message_tool_refs AS tool_result + JOIN messages AS tool_result_message + ON tool_result_message.message_id = tool_result.source_message_id + WHERE tool_result.assistant_message_id = tool_use.assistant_message_id + AND tool_result.block_type = 'tool_result' + AND tool_result.cancelled = 0 + AND tool_result.tool_use_id = tool_use.tool_use_id + AND tool_result_message.parent_tool_use_id IS NULL + ) + GROUP BY m.message_id + ORDER BY m.created_at DESC + LIMIT 1` + ); + const unresolvedMessageId = unresolvedRows[0]?.message_id; + if (typeof unresolvedMessageId === "string") { + await timedQuery2( + sql, + steps, + "get-persisted-tool-result-ids", + `SELECT tool_result.tool_use_id + FROM message_tool_refs AS tool_result + JOIN messages AS tool_result_message + ON tool_result_message.message_id = tool_result.source_message_id + WHERE tool_result.assistant_message_id = ? + AND tool_result.block_type = 'tool_result' + AND tool_result.cancelled = 0 + AND tool_result_message.parent_tool_use_id IS NULL`, + unresolvedMessageId + ); + await timedQuery2( + sql, + steps, + "get-tool-calls-by-message-id", + `SELECT * FROM tool_calls WHERE message_id = ?`, + unresolvedMessageId + ); + } + await timedQuery2( + sql, + steps, + "is-last-message-cancelled-assistant", + `SELECT role, cancelled FROM messages + WHERE parent_tool_use_id IS NULL + ORDER BY created_at DESC + LIMIT 1` + ); + await timedQuery2( + sql, + steps, + "get-last-uncancelled", + `SELECT m.* FROM messages m + WHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL + ORDER BY m.created_at DESC + LIMIT 1` + ); return { - total: stats.total, - reads: stats.reads, - mutations: stats.mutations, - tx: stats.tx, - other: stats.other, - rows: stats.rows, - errors: stats.errors, - slow: stats.slow, - maxMs: stats.maxMs, - maxStep: stats.maxStep, - byOperation: { ...stats.byOperation }, - byTable: { ...stats.byTable } + name: "build-tool-plan-context", + totalMs: Math.round(performance.now() - startedAt), + steps }; } -async function runAgentConcurrent2Workload(sql, clientId, staggerHandleMs, stats) { +async function runCatchupSnapshot2(sql, version) { const startedAt = performance.now(); - const buildToolPlanContext = runBuildToolPlanContext(sql, stats); - const catchupSnapshot = runCatchupSnapshot(sql, 0, stats); - const recoverToolCalls = runRecoverToolCalls(sql, stats); - const mutationMix = runMutationMix(sql, clientId, stats); - const handleExecutorConnect = delay2(staggerHandleMs).then( - () => runHandleClientConnect(sql, clientId, stats) + const steps = []; + await timedQuery2( + sql, + steps, + "thread-events-list-since-version", + `SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`, + version + ); + await timedQuery2( + sql, + steps, + "environment-snapshot", + `SELECT snapshot FROM environment_snapshot WHERE id = 1` + ); + await timedQuery2( + sql, + steps, + "thread-settings-snapshot", + `SELECT settings FROM thread_settings_snapshot WHERE id = 1` + ); + await timedQuery2( + sql, + steps, + "retry-state", + `SELECT * FROM retry_state WHERE id = 1` + ); + await timedQuery2( + sql, + steps, + "queued-messages", + `SELECT * FROM queued_messages ORDER BY created_at ASC` + ); + await timedQuery2( + sql, + steps, + "executor-artifacts", + `SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC` + ); + await timedQuery2( + sql, + steps, + "tool-approvals", + `SELECT * FROM tool_approvals ORDER BY timestamp ASC` + ); + await timedQuery2( + sql, + steps, + "compaction-summaries", + `SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC` + ); + await timedQuery2( + sql, + steps, + "executor-status", + `SELECT value FROM thread_meta_kv WHERE key = 'executor_status'` ); - const results = await Promise.all([ - handleExecutorConnect, - buildToolPlanContext, - catchupSnapshot, - recoverToolCalls, - mutationMix - ]); + steps.sort((a, b) => b.durationMs - a.durationMs); return { + name: "catchup-snapshot", totalMs: Math.round(performance.now() - startedAt), - results + steps }; } -async function runHandleClientConnect(sql, clientId, stats) { +async function runRecoverToolCalls2(sql) { const startedAt = performance.now(); const steps = []; - const nextSeq = await sql.withTransaction(stats, async (tx) => { - const latestExecutor = await timedQuery( - tx, - stats, - steps, - "load-latest-executor-id", - `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` - ); - const latestExecutorId = String( - latestExecutor[0]?.executor_id ?? "seed-executor" - ); - await timedQuery( - tx, - stats, - steps, - "select-cached-executor-tools", - `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, - latestExecutorId - ); - const executorType = await timedQuery( - tx, - stats, - steps, - "select-executor-type", - `SELECT value FROM thread_meta_kv WHERE key = 'executor_type'` - ); - if (!executorType[0]?.value) { - await timedQuery( - tx, - stats, - steps, - "set-executor-type", - `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`, - "local-client", - (/* @__PURE__ */ new Date()).toISOString() - ); - } - const sandboxIntent = await timedQuery( - tx, - stats, - steps, - "select-workspace-intent", - `SELECT value FROM thread_meta_kv WHERE key = 'workspace_intent'` - ); - if (hasPendingLaunch(sandboxIntent[0]?.value)) { - await timedQuery( - tx, - stats, - steps, - "clear-pending-launch", - `UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'workspace_intent'`, - JSON.stringify({ spec: null, pendingLaunch: null }), - (/* @__PURE__ */ new Date()).toISOString() - ); - } - const seqRows = await timedQuery( - tx, - stats, - steps, - "select-next-thread-event-seq", - `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` - ); - const seq = Number(seqRows[0]?.seq ?? 1); - await timedQuery( - tx, - stats, - steps, - "insert-client-connected-event", - `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, - seq, - "client_connected", - JSON.stringify({ type: "client_connected", clientId }), - (/* @__PURE__ */ new Date()).toISOString() - ); - return seq; - }); - steps.push({ - name: "transaction-total", - durationMs: Math.round(performance.now() - startedAt), - rowCount: nextSeq - }); + await timedQuery2( + sql, + steps, + "hydrate-tool-progress", + `SELECT id, progress + FROM tool_calls + WHERE progress IS NOT NULL + AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + ); + await timedQuery2( + sql, + steps, + "get-pending-tool-calls", + `SELECT * FROM tool_calls + WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running') + ORDER BY issued_at ASC` + ); + await timedQuery2( + sql, + steps, + "get-next-tool-expiry", + `SELECT MIN(expires_at) AS expires_at + FROM tool_calls + WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + ); return { - name: "handle-client-connect", + name: "recover-tool-calls", totalMs: Math.round(performance.now() - startedAt), steps }; } -async function runBuildToolPlanContext(sql, stats) { +async function timedQuery2(sql, steps, name, query, ...values) { const startedAt = performance.now(); - const steps = []; - const latestExecutor = await timedQuery( - sql, - stats, - steps, - "load-latest-executor-id", - `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` + const rows = await sql(query, ...values); + steps.push({ + name, + durationMs: Math.round(performance.now() - startedAt), + rowCount: rows.length + }); + return rows; +} +function hasPendingLaunch2(value) { + if (typeof value !== "string" || value.length === 0) { + return false; + } + try { + const parsed = JSON.parse(value); + return parsed.pendingLaunch !== null && parsed.pendingLaunch !== void 0; + } catch { + return false; + } +} +function delay2(ms) { + return new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms))); +} +async function createSlowReconnectSchema(database) { + await database.execute(`CREATE TABLE IF NOT EXISTS executor_tools ( + executor_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + schema TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (executor_id, tool_name) + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv ( + key TEXT PRIMARY KEY, + value TEXT, + updated_at TEXT NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS thread_events ( + seq INTEGER PRIMARY KEY, + event_type TEXT NOT NULL, + payload TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS message_added_events ( + message_id TEXT PRIMARY KEY, + seq INTEGER NOT NULL UNIQUE + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS messages ( + message_id TEXT PRIMARY KEY, + role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')), + content TEXT NOT NULL, + meta TEXT, + user_state TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + cancelled INTEGER NOT NULL DEFAULT 0, + read_at TEXT, + parent_tool_use_id TEXT, + tool_result_for_message_id TEXT + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs ( + source_message_id TEXT NOT NULL, + assistant_message_id TEXT NOT NULL, + tool_use_id TEXT NOT NULL, + block_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')), + cancelled INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (source_message_id, block_type, tool_use_id) + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)` + ); + await database.execute( + `CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0` + ); + await database.execute(`CREATE TABLE IF NOT EXISTS tool_calls ( + id TEXT PRIMARY KEY, + provider_tool_use_id TEXT NOT NULL, + tool_name TEXT NOT NULL, + args TEXT NOT NULL, + executor_id TEXT, + message_id TEXT NOT NULL, + issued_at TEXT NOT NULL, + expires_at TEXT, + state TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')), + result TEXT, + progress TEXT, + completed_at TEXT + )`); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)` + ); + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + ); + await database.execute( + `CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)` + ); + await database.execute( + `CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)` + ); + await database.execute( + `CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)` ); - const latestExecutorId = String(latestExecutor[0]?.executor_id ?? "seed-executor"); - await timedQuery( - sql, - stats, - steps, - "select-executor-tools", - `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, - latestExecutorId + await database.execute( + `CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)` ); - await timedQuery( - sql, - stats, - steps, - "count-uncancelled-top-level", - `SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL` + await database.execute( + `CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)` ); - const unresolvedRows = await timedQuery( - sql, - stats, - steps, - "find-unresolved-assistant-message", - `SELECT m.* - FROM message_tool_refs AS tool_use - JOIN messages AS m - ON m.message_id = tool_use.assistant_message_id - WHERE tool_use.block_type = 'tool_use' - AND tool_use.cancelled = 0 - AND m.cancelled = 0 - AND m.role = 'assistant' - AND m.parent_tool_use_id IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM message_tool_refs AS tool_result - JOIN messages AS tool_result_message - ON tool_result_message.message_id = tool_result.source_message_id - WHERE tool_result.assistant_message_id = tool_use.assistant_message_id - AND tool_result.block_type = 'tool_result' - AND tool_result.cancelled = 0 - AND tool_result.tool_use_id = tool_use.tool_use_id - AND tool_result_message.parent_tool_use_id IS NULL - ) - GROUP BY m.message_id - ORDER BY m.created_at DESC - LIMIT 1` + await database.execute( + `CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))` ); - const unresolvedMessageId = unresolvedRows[0]?.message_id; - if (typeof unresolvedMessageId === "string") { - await timedQuery( - sql, - stats, - steps, - "get-persisted-tool-result-ids", - `SELECT tool_result.tool_use_id - FROM message_tool_refs AS tool_result - JOIN messages AS tool_result_message - ON tool_result_message.message_id = tool_result.source_message_id - WHERE tool_result.assistant_message_id = ? - AND tool_result.block_type = 'tool_result' - AND tool_result.cancelled = 0 - AND tool_result_message.parent_tool_use_id IS NULL`, - unresolvedMessageId + await database.execute( + `CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)` + ); + await database.execute( + `CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)` + ); +} +async function seedSlowReconnectData(database) { + const existing = await database.execute( + `SELECT COUNT(*) AS count FROM messages` + ); + if (Number(existing[0]?.count ?? 0) > 0) { + const [toolCalls] = await database.execute( + `SELECT COUNT(*) AS count FROM tool_calls` ); - await timedQuery( - sql, - stats, - steps, - "get-tool-calls-by-message-id", - `SELECT * FROM tool_calls WHERE message_id = ?`, - unresolvedMessageId + const [threadEvents] = await database.execute( + `SELECT COUNT(*) AS count FROM thread_events` ); + return { + seeded: false, + messages: Number(existing[0]?.count ?? 0), + toolCalls: Number(toolCalls?.count ?? 0), + threadEvents: Number(threadEvents?.count ?? 0) + }; } - await timedQuery( - sql, - stats, - steps, - "is-last-message-cancelled-assistant", - `SELECT role, cancelled FROM messages - WHERE parent_tool_use_id IS NULL - ORDER BY created_at DESC - LIMIT 1` + const vary = (base, min = 1) => Math.max(min, Math.round(base * (0.25 + Math.random() * 3.75))); + const messageCount = vary(MESSAGE_COUNT2, 2); + const messageToolRefCount = vary(MESSAGE_TOOL_REF_COUNT2, 2); + const toolCallCount = vary(TOOL_CALL_COUNT2); + const executorToolCount = vary(EXECUTOR_TOOL_COUNT2); + const threadEventCount = vary(THREAD_EVENT_COUNT2); + const assistantSpan = Math.max(1, Math.floor(messageCount / 2)); + const now = (/* @__PURE__ */ new Date("2026-05-16T03:58:18.661Z")).getTime(); + const text2 = (size) => "x".repeat(size); + const isoAt = (index) => new Date(now + index * 1e3).toISOString(); + await batchInsert2( + database, + `INSERT INTO thread_meta_kv (key, value, updated_at)`, + [ + ["executor_type", "local-client", isoAt(0)], + [ + "sandbox_intent", + JSON.stringify({ spec: null, pendingLaunch: null }), + isoAt(0) + ], + [ + "executor_status", + JSON.stringify({ available: true, message: "ready" }), + isoAt(0) + ] + ] ); - await timedQuery( - sql, - stats, - steps, - "get-last-uncancelled", - `SELECT m.* FROM messages m - WHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL - ORDER BY m.created_at DESC - LIMIT 1` + const messageRows = []; + for (let index = 1; index <= messageCount; index++) { + const role = index % 2 === 0 ? "assistant" : "user"; + messageRows.push([ + messageId2(index), + role, + text2(MESSAGE_CONTENT_BYTES2), + null, + null, + isoAt(index), + 0, + null, + null, + null + ]); + } + await batchInsert2( + database, + `INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`, + messageRows, + 20 + ); + const messageToolRefRows = []; + for (let index = 0; index < messageToolRefCount / 2; index++) { + const assistantIndex = 2 + index % assistantSpan * 2; + const sourceIndex = Math.max(1, assistantIndex - 1); + const resultIndex = Math.min(messageCount, assistantIndex + 1); + const toolUseId = toolUseID2(index + 1); + messageToolRefRows.push([ + messageId2(sourceIndex), + messageId2(assistantIndex), + toolUseId, + "tool_use", + 0 + ]); + messageToolRefRows.push([ + messageId2(resultIndex), + messageId2(assistantIndex), + toolUseId, + "tool_result", + 0 + ]); + } + await batchInsert2( + database, + `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`, + messageToolRefRows, + 50 + ); + const toolCallRows = []; + for (let index = 1; index <= toolCallCount; index++) { + const assistantIndex = 2 + (index - 1) % assistantSpan * 2; + toolCallRows.push([ + toolUseID2(index), + `provider-${index}`, + `tool_${index % 21}`, + JSON.stringify({ path: `/tmp/file-${index}` }), + "seed-executor", + messageId2(assistantIndex), + isoAt(index), + null, + "completed", + JSON.stringify({ + ok: true, + run: { status: "done", result: text2(TOOL_CALL_RESULT_BYTES2) } + }), + null, + isoAt(index + 100) + ]); + } + await batchInsert2( + database, + `INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`, + toolCallRows, + 20 + ); + const executorToolRows = []; + for (let index = 1; index <= executorToolCount; index++) { + const schema = JSON.stringify({ + name: `tool_${index}`, + description: text2(EXECUTOR_TOOL_SCHEMA_BYTES2), + input_schema: { type: "object", properties: {} } + }); + executorToolRows.push([ + "seed-executor", + `tool_${index}`, + schema, + isoAt(index) + ]); + } + await batchInsert2( + database, + `INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`, + executorToolRows, + 42 + ); + const threadEventRows = []; + for (let index = 1; index <= threadEventCount; index++) { + threadEventRows.push([ + index, + index % 3 === 0 ? "message_added" : "agent_state_changed", + JSON.stringify({ + type: "seed_event", + body: text2(THREAD_EVENT_PAYLOAD_BYTES2) + }), + isoAt(index) + ]); + } + await batchInsert2( + database, + `INSERT INTO thread_events (seq, event_type, payload, created_at)`, + threadEventRows, + 25 + ); + const messageAddedRows = []; + for (let index = 1; index <= messageCount; index++) { + messageAddedRows.push([messageId2(index), index]); + } + await batchInsert2( + database, + `INSERT INTO message_added_events (message_id, seq)`, + messageAddedRows, + 50 + ); + await database.execute( + `INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`, + JSON.stringify({ cwd: "/workspace", body: text2(3620) }), + isoAt(0) + ); + await database.execute( + `INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`, + JSON.stringify({ maxTokens: 2e4, body: text2(55) }), + isoAt(0) ); return { - name: "build-tool-plan-context", - totalMs: Math.round(performance.now() - startedAt), - steps - }; -} -async function runCatchupSnapshot(sql, version, stats) { - const startedAt = performance.now(); - const steps = []; - await Promise.all([ - timedQuery( - sql, - stats, - steps, - "thread-events-list-since-version", - `SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`, - version - ), - timedQuery( - sql, - stats, - steps, - "environment-snapshot", - `SELECT snapshot FROM environment_snapshot WHERE id = 1` - ), - timedQuery( - sql, - stats, - steps, - "thread-settings-snapshot", - `SELECT settings FROM thread_settings_snapshot WHERE id = 1` - ), - timedQuery( - sql, - stats, - steps, - "retry-state", - `SELECT * FROM retry_state WHERE id = 1` - ), - timedQuery( - sql, - stats, - steps, - "queued-messages", - `SELECT * FROM queued_messages ORDER BY created_at ASC` - ), - timedQuery( - sql, - stats, - steps, - "executor-artifacts", - `SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC` - ), - timedQuery( - sql, - stats, - steps, - "tool-approvals", - `SELECT * FROM tool_approvals ORDER BY timestamp ASC` - ), - timedQuery( - sql, - stats, - steps, - "compaction-summaries", - `SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC` - ), - timedQuery( - sql, - stats, - steps, - "executor-status", - `SELECT value FROM thread_meta_kv WHERE key = 'executor_status'` - ) - ]); - steps.sort((a, b) => b.durationMs - a.durationMs); - return { - name: "catchup-snapshot", - totalMs: Math.round(performance.now() - startedAt), - steps + seeded: true, + messages: messageCount, + toolCalls: toolCallCount, + threadEvents: threadEventCount }; } -async function runRecoverToolCalls(sql, stats) { - const startedAt = performance.now(); - const steps = []; - await timedQuery( - sql, - stats, - steps, - "hydrate-tool-progress", - `SELECT id, progress - FROM tool_calls - WHERE progress IS NOT NULL - AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` - ); - await timedQuery( - sql, - stats, - steps, - "get-pending-tool-calls", - `SELECT * FROM tool_calls - WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running') - ORDER BY issued_at ASC` +var MAX_BATCH_BIND_BYTES = 120 * 1024; +function bindParamBytes(value) { + if (value === null || value === void 0) { + return 0; + } + if (typeof value === "string") { + return Buffer.byteLength(value, "utf8"); + } + if (typeof value === "number") { + return 8; + } + if (value instanceof Uint8Array) { + return value.byteLength; + } + return Buffer.byteLength(String(value), "utf8"); +} +async function batchInsert2(database, insertPrefix, rows, batchSize = 100) { + if (rows.length === 0) { + return; + } + const columnCount = rows[0]?.length ?? 0; + if (columnCount === 0) { + return; + } + const rowPlaceholder = `(${"?,".repeat(columnCount).slice(0, -1)})`; + const rowBytes = rows.map( + (row) => row.reduce((sum, value) => sum + bindParamBytes(value), 0) ); - await timedQuery( - sql, - stats, - steps, - "get-next-tool-expiry", - `SELECT MIN(expires_at) AS expires_at - FROM tool_calls - WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` + let index = 0; + while (index < rows.length) { + const chunk = []; + let chunkBytes = 0; + while (index < rows.length && chunk.length < batchSize && // Always take at least one row so an oversized single row still runs + // (and surfaces the engine error) rather than looping forever. + (chunk.length === 0 || chunkBytes + rowBytes[index] <= MAX_BATCH_BIND_BYTES)) { + chunk.push(rows[index]); + chunkBytes += rowBytes[index]; + index++; + } + const values = chunk.map(() => rowPlaceholder).join(","); + const bindings = chunk.flat(); + await database.execute(`${insertPrefix} VALUES ${values}`, ...bindings); + } +} +function messageId2(index) { + return `M-${String(index).padStart(22, "0")}`; +} +function toolUseID2(index) { + return `toolu_${String(index).padStart(22, "0")}`; +} +var registry = setup({ + use: { slowReconnectActor }, + maxIncomingMessageSize: 5 * 1024 * 1024, + maxOutgoingMessageSize: 5 * 1024 * 1024 +}); + +// src/actors/testing/sqlite-cold-start-bench.ts +import { randomBytes } from "crypto"; +import { actor as actor46 } from "rivetkit"; +import { db as db11 } from "rivetkit/db"; +var DEFAULT_TARGET_BYTES = 50 * 1024 * 1024; +var DEFAULT_ROW_BYTES2 = 16 * 1024; +var DEFAULT_BATCH_ROWS2 = 8; +var DEFAULT_TRANSACTION_BYTES = 64 * 1024; +var READ_BATCH_ROWS = 64; +var REVERSE_PROBE_ROWS = 32 * 1024; +var PAYLOAD_TABLE = "cold_start_payload"; +var REVERSE_PROBE_TABLE = "cold_start_reverse_probe"; +function positiveInteger2(value, fallback, name) { + const resolved = value ?? fallback; + if (!Number.isInteger(resolved) || resolved < 1) { + throw new Error(`${name} must be a positive integer`); + } + return resolved; +} +function randomAsciiString(bytes) { + return randomBytes(Math.ceil(bytes / 2)).toString("hex").slice(0, bytes); +} +async function readPayloads(database, direction = "forward") { + const t0 = performance.now(); + const [bounds] = await database.execute( + ` + SELECT + MIN(id) AS min_id, + MAX(id) AS max_id, + COUNT(*) AS rows, + 0 AS bytes, + 0 AS expected_bytes + FROM ${PAYLOAD_TABLE} + ` ); + if (!bounds) throw new Error("read query returned no rows"); + let rows = 0; + let bytes = 0; + let expectedBytes = 0; + let chunks = 0; + const minId = bounds.min_id ?? 0; + const maxId = bounds.max_id ?? 0; + if (direction === "backward") { + const [probeBounds] = await database.execute( + ` + SELECT + MIN(id) AS min_id, + MAX(id) AS max_id, + COUNT(*) AS rows, + 0 AS bytes, + 0 AS expected_bytes + FROM ${REVERSE_PROBE_TABLE} + ` + ); + if (!probeBounds) + throw new Error("reverse probe query returned no rows"); + const probeMinId = probeBounds.min_id ?? 0; + const probeMaxId = probeBounds.max_id ?? 0; + for (let upperId = probeMaxId; upperId >= probeMinId && upperId > 0; upperId -= READ_BATCH_ROWS) { + const lowerId = Math.max(probeMinId, upperId - READ_BATCH_ROWS + 1); + const chunkRows = await database.execute( + ` + SELECT + marker AS bytes, + marker AS expected_bytes + FROM ${REVERSE_PROBE_TABLE} + WHERE id BETWEEN ? AND ? + ORDER BY id DESC + `, + lowerId, + upperId + ); + for (const row of chunkRows) { + rows += 1; + bytes += row.bytes; + expectedBytes += row.expected_bytes; + } + chunks += 1; + } + return { + ms: performance.now() - t0, + ops: rows, + rows, + bytes, + expectedBytes, + chunks, + readBatchRows: READ_BATCH_ROWS, + direction + }; + } + for (let lowerId = minId; lowerId <= maxId; lowerId += READ_BATCH_ROWS) { + const upperId = lowerId + READ_BATCH_ROWS - 1; + const [chunk] = await database.execute( + ` + SELECT + COUNT(*) AS rows, + COALESCE(SUM(length(payload)), 0) AS bytes, + COALESCE(SUM(payload_bytes), 0) AS expected_bytes + FROM ${PAYLOAD_TABLE} + WHERE id BETWEEN ? AND ? + `, + lowerId, + upperId + ); + if (!chunk) throw new Error("chunked read query returned no rows"); + rows += chunk.rows; + bytes += chunk.bytes; + expectedBytes += chunk.expected_bytes; + chunks += 1; + } return { - name: "recover-tool-calls", - totalMs: Math.round(performance.now() - startedAt), - steps + ms: performance.now() - t0, + ops: rows, + rows, + bytes, + expectedBytes, + chunks, + readBatchRows: READ_BATCH_ROWS }; } -async function runMutationMix(sql, clientId, stats) { - const startedAt = performance.now(); - const steps = []; - const writeCount = await sql.withTransaction(stats, async (tx) => { - const now = (/* @__PURE__ */ new Date()).toISOString(); - const suffix = safeId(clientId); - const seqRows = await timedQuery( - tx, - stats, - steps, - "select-max-thread-event-seq", - `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` - ); - const seq = Number(seqRows[0]?.seq ?? 1); - const lastMessageRows = await timedQuery( - tx, - stats, - steps, - "select-last-message-created-at", - `SELECT MAX(created_at) AS created_at FROM messages` - ); - const latestToolRows = await timedQuery( - tx, - stats, - steps, - "select-existing-tool-call", - `SELECT id FROM tool_calls ORDER BY issued_at DESC LIMIT 1` - ); - await timedQuery( - tx, - stats, - steps, - "select-sandbox-row", - `SELECT sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup - FROM e2b_sandbox - WHERE id = 1` - ); - const messageIdValue = `agent2-message-${suffix}-${seq}`; - const toolUseIdValue = `agent2-tool-${suffix}-${seq}`; - const toolCallIdValue = `agent2-call-${suffix}-${seq}`; - const latestToolCallId = String(latestToolRows[0]?.id ?? toolUseID(1)); - const lastCreatedAt = String(lastMessageRows[0]?.created_at ?? now); - await timedQuery( - tx, - stats, - steps, - "upsert-agent-state", - `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES (?, ?, ?)`, - "last_agent_state", - JSON.stringify({ status: "working", clientId, lastCreatedAt }), - now - ); - await timedQuery( - tx, - stats, - steps, - "insert-work-event", - `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, - seq, - "message_added", - JSON.stringify({ type: "message_added", messageId: messageIdValue }), - now - ); - await timedQuery( - tx, - stats, - steps, - "insert-message", - `INSERT INTO messages (role, content, meta, user_state, message_id, created_at, cancelled, parent_tool_use_id, tool_result_for_message_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - "assistant", - "agent concurrent 2 mutation payload", - JSON.stringify({ clientId, seq }), - null, - messageIdValue, - now, - 0, - null, - null - ); - await timedQuery( - tx, - stats, - steps, - "delete-message-tool-refs", - `DELETE FROM message_tool_refs WHERE source_message_id = ?`, - messageIdValue - ); - await timedQuery( - tx, - stats, - steps, - "insert-message-added-event", - `INSERT OR IGNORE INTO message_added_events (message_id, seq) VALUES (?, ?)`, - messageIdValue, - seq - ); - await timedQuery( - tx, - stats, - steps, - "insert-message-tool-ref", - `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled) - VALUES (?, ?, ?, ?, ?)`, - messageIdValue, - messageIdValue, - toolUseIdValue, - "tool_use", - 0 - ); - await timedQuery( - tx, - stats, - steps, - "insert-tool-call", - `INSERT OR IGNORE INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - toolCallIdValue, - `provider-${toolCallIdValue}`, - "tool_1", - JSON.stringify({ path: `/tmp/${toolCallIdValue}` }), - "seed-executor", - messageIdValue, - now, - null, - "running", - null, - JSON.stringify({ pct: 0.5, clientId }), - null - ); - await timedQuery( - tx, - stats, - steps, - "update-tool-call-progress", - `UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`, - JSON.stringify({ pct: 0.75, clientId, updatedAt: now }), - toolCallIdValue - ); - await timedQuery( - tx, - stats, - steps, - "update-existing-tool-call-progress", - `UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`, - JSON.stringify({ pct: 0.25, clientId, updatedAt: now }), - latestToolCallId - ); - return seq; - }); - steps.push({ - name: "transaction-total", - durationMs: Math.round(performance.now() - startedAt), - rowCount: writeCount - }); +var sqliteColdStartBench = actor46({ + options: { + actionTimeout: 6e5 + }, + db: db11({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS cold_start_payload ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload TEXT NOT NULL, + payload_bytes INTEGER NOT NULL, + created_at INTEGER NOT NULL + ) + `); + await database.execute(` + CREATE TABLE IF NOT EXISTS cold_start_reverse_probe ( + id INTEGER PRIMARY KEY, + marker INTEGER NOT NULL + ) + `); + } + }), + actions: { + reset: async (c) => { + await c.db.execute(`DELETE FROM ${PAYLOAD_TABLE}`); + await c.db.execute(`DELETE FROM ${REVERSE_PROBE_TABLE}`); + return { ok: true }; + }, + writeRandomStrings: async (c, input = {}) => { + const targetBytes = positiveInteger2( + input.targetBytes, + DEFAULT_TARGET_BYTES, + "targetBytes" + ); + const rowBytes = positiveInteger2( + input.rowBytes, + DEFAULT_ROW_BYTES2, + "rowBytes" + ); + const batchRows = positiveInteger2( + input.batchRows, + DEFAULT_BATCH_ROWS2, + "batchRows" + ); + const transactionBytes = positiveInteger2( + input.transactionBytes, + DEFAULT_TRANSACTION_BYTES, + "transactionBytes" + ); + const createdAt = Date.now(); + let remainingBytes = targetBytes; + let rows = 0; + let transactions = 0; + let randomStringMs = 0; + let sqliteInsertMs = 0; + let commitMs = 0; + let inTransaction = false; + const wallT0 = performance.now(); + try { + while (remainingBytes > 0) { + let transactionRemainingBytes = Math.min( + transactionBytes, + remainingBytes + ); + await c.db.execute("BEGIN"); + inTransaction = true; + transactions += 1; + while (transactionRemainingBytes > 0) { + const placeholders = []; + const args = []; + const generateT0 = performance.now(); + for (let batchIndex = 0; batchIndex < batchRows && transactionRemainingBytes > 0 && remainingBytes > 0; batchIndex += 1) { + const payloadBytes = Math.min( + rowBytes, + transactionRemainingBytes, + remainingBytes + ); + placeholders.push("(?, ?, ?)"); + args.push( + randomAsciiString(payloadBytes), + payloadBytes, + createdAt + rows + ); + transactionRemainingBytes -= payloadBytes; + remainingBytes -= payloadBytes; + rows += 1; + } + randomStringMs += performance.now() - generateT0; + const insertT0 = performance.now(); + await c.db.execute( + `INSERT INTO ${PAYLOAD_TABLE} (payload, payload_bytes, created_at) VALUES ${placeholders.join(", ")}`, + ...args + ); + sqliteInsertMs += performance.now() - insertT0; + } + const commitT0 = performance.now(); + await c.db.execute("COMMIT"); + commitMs += performance.now() - commitT0; + inTransaction = false; + } + await c.db.execute("BEGIN"); + inTransaction = true; + for (let lowerId = 1; lowerId <= REVERSE_PROBE_ROWS; lowerId += 256) { + const upperId = Math.min(REVERSE_PROBE_ROWS, lowerId + 255); + const placeholders = []; + const args = []; + for (let id = lowerId; id <= upperId; id += 1) { + placeholders.push("(?, ?)"); + args.push(id, 1); + } + const insertT0 = performance.now(); + await c.db.execute( + `INSERT INTO ${REVERSE_PROBE_TABLE} (id, marker) VALUES ${placeholders.join(", ")}`, + ...args + ); + sqliteInsertMs += performance.now() - insertT0; + } + const reverseCommitT0 = performance.now(); + await c.db.execute("COMMIT"); + commitMs += performance.now() - reverseCommitT0; + inTransaction = false; + return { + ms: sqliteInsertMs + commitMs, + writeWallMs: performance.now() - wallT0, + randomStringMs, + sqliteInsertMs, + commitMs, + ops: rows, + rows, + transactions, + bytes: targetBytes, + rowBytes, + batchRows, + transactionBytes, + reverseProbeRows: REVERSE_PROBE_ROWS + }; + } catch (err) { + if (inTransaction) { + await c.db.execute("ROLLBACK"); + } + throw err; + } + }, + readAll: async (c) => { + return readPayloads(c.db); + }, + readAllReverse: async (c) => { + return readPayloads(c.db, "backward"); + }, + wakeSqlite: async (c) => { + const t0 = performance.now(); + const [row] = await c.db.execute( + `SELECT COUNT(*) AS rows FROM ${PAYLOAD_TABLE} WHERE id = -1` + ); + return { + ms: performance.now() - t0, + rows: row?.rows ?? 0 + }; + }, + goToSleep: (c) => { + c.sleep(); + return { ok: true }; + } + } +}); + +// src/actors/testing/sqlite-memory-pressure.ts +import { actor as actor47 } from "rivetkit"; +import { db as db12 } from "rivetkit/db"; +var DEFAULT_INSERT_ROWS = 128; +var DEFAULT_ROW_BYTES3 = 16 * 1024; +var DEFAULT_SCAN_ROWS = 512; +var INSERT_BATCH_ROWS = 32; +function finiteInt2(value, fallback) { + if (value === void 0) return fallback; + if (!Number.isFinite(value) || value < 0) { + throw new Error(`expected a non-negative finite number, got ${value}`); + } + return Math.floor(value); +} +function copyNativeMetrics(metrics) { + if (!metrics) return null; + const raw = metrics; + const numberField3 = (camel, snake) => Number(raw[camel] ?? raw[snake] ?? 0); return { - name: "mutation-mix", - totalMs: Math.round(performance.now() - startedAt), - steps + requestBuildNs: numberField3("requestBuildNs", "request_build_ns"), + serializeNs: numberField3("serializeNs", "serialize_ns"), + transportNs: numberField3("transportNs", "transport_ns"), + stateUpdateNs: numberField3("stateUpdateNs", "state_update_ns"), + totalNs: numberField3("totalNs", "total_ns"), + commitCount: numberField3("commitCount", "commit_count"), + pageCacheEntries: numberField3("pageCacheEntries", "page_cache_entries"), + pageCacheWeightedSize: numberField3( + "pageCacheWeightedSize", + "page_cache_weighted_size" + ), + pageCacheCapacityPages: numberField3( + "pageCacheCapacityPages", + "page_cache_capacity_pages" + ), + writeBufferDirtyPages: numberField3( + "writeBufferDirtyPages", + "write_buffer_dirty_pages" + ), + dbSizePages: numberField3("dbSizePages", "db_size_pages") }; } -async function timedQuery(sql, stats, steps, name, query, ...values) { - const startedAt = performance.now(); - try { - const rows = await sql(query, ...values); - const durationMs = Math.round(performance.now() - startedAt); - recordAgentConcurrent2Query(stats, name, query, durationMs, rows.length, false); - steps.push({ - name, - durationMs, - rowCount: rows.length - }); - return rows; - } catch (error) { - const durationMs = Math.round(performance.now() - startedAt); - recordAgentConcurrent2Query(stats, name, query, durationMs, 0, true); - throw error; - } +async function queryOne3(database, sql, ...args) { + const rows = await database.execute(sql, ...args); + if (!rows[0]) throw new Error(`query returned no rows: ${sql}`); + return rows[0]; } -async function executeTrackedQuery(execute, stats, name, query, ...values) { - const startedAt = performance.now(); - try { - const rows = await execute(query, ...values); - recordAgentConcurrent2Query( - stats, - name, - query, - Math.round(performance.now() - startedAt), - rows.length, - false - ); - return rows; - } catch (error) { - recordAgentConcurrent2Query( - stats, - name, - query, - Math.round(performance.now() - startedAt), - 0, - true - ); - throw error; - } +async function storageStats2(database) { + const [pageCount, freelistCount, pageSize] = await Promise.all([ + queryOne3(database, "PRAGMA page_count"), + queryOne3(database, "PRAGMA freelist_count"), + queryOne3(database, "PRAGMA page_size") + ]); + const nativeMetrics = await database.nativeMetrics?.(); + const copiedMetrics = copyNativeMetrics(nativeMetrics); + return { + page_count: pageCount.page_count, + freelist_count: freelistCount.freelist_count, + page_size: pageSize.page_size, + vfs: copiedMetrics + }; } -function recordAgentConcurrent2Query(stats, name, query, durationMs, rowCount, failed) { - const classification = classifyAgentConcurrent2Query(query); - for (const target of [stats.cycle, stats.wake, stats.actor]) { - target.total++; - target.rows += rowCount; - if (failed) target.errors++; - if (durationMs >= SLOW_QUERY_MS) target.slow++; - if (durationMs > target.maxMs) { - target.maxMs = durationMs; - target.maxStep = `${name}:${classification.table}`; +var sqliteMemoryPressure = actor47({ + options: { + actionTimeout: 3e5 + }, + state: { + sleepCount: 0 + }, + db: db12({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS pressure_rows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + seed TEXT NOT NULL, + cycle INTEGER NOT NULL, + bucket INTEGER NOT NULL, + payload BLOB NOT NULL, + touched_count INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ) + `); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_pressure_rows_seed_cycle ON pressure_rows(seed, cycle)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_pressure_rows_bucket ON pressure_rows(bucket)" + ); + await database.execute(` + CREATE TABLE IF NOT EXISTS pressure_cycles ( + cycle INTEGER PRIMARY KEY, + seed TEXT NOT NULL, + inserted_rows INTEGER NOT NULL, + deleted_rows INTEGER NOT NULL, + active_rows INTEGER NOT NULL, + active_bytes INTEGER NOT NULL, + duration_ms REAL NOT NULL, + created_at INTEGER NOT NULL + ) + `); } - target.byOperation[classification.operation] = (target.byOperation[classification.operation] ?? 0) + 1; - target.byTable[classification.table] = (target.byTable[classification.table] ?? 0) + 1; - if (classification.kind === "read") { - target.reads++; - } else if (classification.kind === "mutation") { - target.mutations++; - } else if (classification.kind === "tx") { - target.tx++; - } else { - target.other++; + }), + onSleep: (c) => { + c.state.sleepCount += 1; + console.log( + JSON.stringify({ + kind: "sqlite_memory_pressure_on_sleep", + actorId: c.actorId, + sleepCount: c.state.sleepCount, + timestamp: (/* @__PURE__ */ new Date()).toISOString() + }) + ); + }, + actions: { + reset: async (c) => { + await c.db.execute("DELETE FROM pressure_cycles"); + await c.db.execute("DELETE FROM pressure_rows"); + await c.db.execute("VACUUM"); + return { + ok: true, + storage: await storageStats2(c.db) + }; + }, + goToSleep: (c) => { + c.sleep(); + return { ok: true }; + }, + releaseStorage: async (c) => { + const before = await storageStats2(c.db); + return { + ok: true, + before, + after: await storageStats2(c.db) + }; + }, + stats: async (c) => { + const rowStats = await queryOne3( + c.db, + "SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes, COALESCE(SUM(touched_count), 0) AS touched_sum FROM pressure_rows" + ); + const cycles = await queryOne3( + c.db, + "SELECT COUNT(*) AS count FROM pressure_cycles" + ); + const integrity = await queryOne3( + c.db, + "PRAGMA integrity_check" + ); + return { + activeRows: rowStats.active_rows, + activeBytes: rowStats.active_bytes ?? 0, + touchedCount: rowStats.touched_sum ?? 0, + cycles: cycles.count, + integrityCheck: integrity.integrity_check, + storage: await storageStats2(c.db) + }; + }, + runCycle: async (c, input) => { + const startedAt = performance.now(); + const insertRows = finiteInt2(input.insertRows, DEFAULT_INSERT_ROWS); + const rowBytes = finiteInt2(input.rowBytes, DEFAULT_ROW_BYTES3); + const scanRows = Math.max( + 1, + finiteInt2(input.scanRows, DEFAULT_SCAN_ROWS) + ); + const now = Date.now(); + let insertedRows = 0; + const logStage = (stage, phase, fields = {}) => { + console.log( + JSON.stringify({ + kind: "sqlite_memory_pressure_run_cycle_stage", + actorId: c.actorId, + seed: input.seed, + cycle: input.cycle, + stage, + phase, + elapsedMs: performance.now() - startedAt, + timestamp: (/* @__PURE__ */ new Date()).toISOString(), + ...fields + }) + ); + }; + const executeTimed = async (stage, sql, ...args) => { + const stageStartedAt = performance.now(); + logStage(stage, "start", { argCount: args.length }); + try { + const rows = await c.db.execute(sql, ...args); + logStage(stage, "end", { + durationMs: performance.now() - stageStartedAt, + rowCount: rows.length + }); + return rows; + } catch (err) { + logStage(stage, "error", { + durationMs: performance.now() - stageStartedAt, + error: err instanceof Error ? err.message : String(err) + }); + throw err; + } + }; + logStage("run_cycle", "start", { + insertRows, + rowBytes, + scanRows + }); + await executeTimed("begin", "BEGIN"); + try { + while (insertedRows < insertRows) { + const batchRows = Math.min( + INSERT_BATCH_ROWS, + insertRows - insertedRows + ); + const placeholders = []; + const args = []; + for (let i = 0; i < batchRows; i += 1) { + const rowIndex = insertedRows + i; + placeholders.push("(?, ?, ?, randomblob(?), 0, ?)"); + args.push( + input.seed, + input.cycle, + (input.cycle + rowIndex) % 32, + rowBytes, + now + rowIndex + ); + } + await executeTimed( + "insert_batch", + `INSERT INTO pressure_rows (seed, cycle, bucket, payload, touched_count, created_at) VALUES ${placeholders.join(", ")}`, + ...args + ); + insertedRows += batchRows; + logStage("insert_batch_progress", "end", { + insertedRows, + batchRows + }); + } + await executeTimed("commit", "COMMIT"); + } catch (err) { + await executeTimed("rollback", "ROLLBACK").catch( + () => void 0 + ); + throw err; + } + const scan = await executeTimed( + "scan_recent", + "SELECT id, length(payload) AS payload_bytes FROM pressure_rows ORDER BY id DESC LIMIT ?", + scanRows + ); + const bucketAgg = await executeTimed( + "bucket_agg", + "SELECT bucket, COUNT(*) AS rows, SUM(length(payload)) AS bytes FROM pressure_rows WHERE bucket BETWEEN ? AND ? GROUP BY bucket ORDER BY bucket", + input.cycle % 16, + input.cycle % 16 + 15 + ); + await executeTimed( + "touch_recent", + "UPDATE pressure_rows SET touched_count = touched_count + 1 WHERE id IN (SELECT id FROM pressure_rows ORDER BY id DESC LIMIT ?)", + Math.min(scanRows, insertRows) + ); + const deletedRows = 0; + const rowStatsRows = await executeTimed( + "row_stats", + "SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes FROM pressure_rows" + ); + const rowStats = rowStatsRows[0]; + if (!rowStats) throw new Error("query returned no rows: row_stats"); + const integrityRows = await executeTimed( + "integrity_check", + "PRAGMA integrity_check" + ); + const integrity = integrityRows[0]; + if (!integrity) { + throw new Error("query returned no rows: integrity_check"); + } + const durationMs = performance.now() - startedAt; + await executeTimed( + "record_cycle", + "INSERT OR REPLACE INTO pressure_cycles (cycle, seed, inserted_rows, deleted_rows, active_rows, active_bytes, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + input.cycle, + input.seed, + insertedRows, + deletedRows, + rowStats.active_rows, + rowStats.active_bytes ?? 0, + durationMs, + now + ); + const storageStartedAt = performance.now(); + logStage("storage_stats", "start"); + const storage = await storageStats2(c.db); + logStage("storage_stats", "end", { + durationMs: performance.now() - storageStartedAt, + pageCount: storage.page_count, + dbSizePages: storage.vfs?.dbSizePages ?? null, + pageCacheEntries: storage.vfs?.pageCacheEntries ?? null + }); + logStage("run_cycle", "end", { + durationMs, + activeRows: rowStats.active_rows, + activeBytes: rowStats.active_bytes ?? 0, + pageCount: storage.page_count + }); + return { + seed: input.seed, + cycle: input.cycle, + insertedRows, + deletedRows, + activeRows: rowStats.active_rows, + activeBytes: rowStats.active_bytes ?? 0, + scannedRows: scan.length, + bucketsRead: bucketAgg.length, + integrityCheck: integrity.integrity_check, + storage, + durationMs + }; } } -} -function classifyAgentConcurrent2Query(query) { - const normalized = query.trim().replace(/\s+/g, " "); - const operation = normalized.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? "other"; - const table = extractAgentConcurrent2Table(normalized, operation); - if (operation === "select") { - return { operation, kind: "read", table }; - } - if (operation === "insert" || operation === "update" || operation === "delete" || operation === "replace") { - return { operation, kind: "mutation", table }; - } - if (operation === "begin" || operation === "commit" || operation === "rollback") { - return { operation, kind: "tx", table }; - } - return { operation, kind: "other", table }; -} -function extractAgentConcurrent2Table(query, operation) { - const lower = query.toLowerCase(); - if (operation === "select") { - return firstMatch(lower, /\bfrom\s+([a-z0-9_]+)/) ?? "unknown"; - } - if (operation === "insert" || operation === "replace") { - return firstMatch(lower, /\binto\s+([a-z0-9_]+)/) ?? "unknown"; - } - if (operation === "update") { - return firstMatch(lower, /\bupdate\s+([a-z0-9_]+)/) ?? "unknown"; - } - if (operation === "delete") { - return firstMatch(lower, /\bfrom\s+([a-z0-9_]+)/) ?? "unknown"; - } - if (operation === "begin" || operation === "commit" || operation === "rollback") { - return "transaction"; +}); + +// src/actors/testing/sqlite-realworld-bench.ts +import { actor as actor48 } from "rivetkit"; +import { db as db13 } from "rivetkit/db"; +var DEFAULT_ROW_BYTES4 = 2 * 1024; +var ORDER_BATCH_ROWS = 50; +var DOC_BATCH_ROWS = 75; +var LEDGER_BATCH_ROWS = 100; +var POINT_LOOKUP_OPS = 1e3; +var RANGE_CHUNK_ROWS = 512; +var SETUP_TRANSACTION_ROWS = 128; +var FEED_PAGE_ROWS = 100; +var CHAT_LOG_CHUNK_BYTES = 4 * 1024; +var CHAT_LOG_INSERT_BATCH_SIZE = 50; +var CHAT_THREAD_ID = "rw-chat-main"; +var SQL_RUSH_MSGS_COUNT = 2500; +var SQL_RUSH_TOOL_REFS_COUNT = 240; +var SQL_RUSH_EVENTS_COUNT = 700; +var SQL_RUSH_KV_COUNT = 40; +var SQL_RUSH_TOOLS_COUNT = 41; +var SQL_RUSH_META_COUNT = 12; +var WORKLOADS = [ + "small-rowid-point", + "small-schema-read", + "small-range-scan", + "rowid-range-forward", + "rowid-range-backward", + "secondary-index-covering-range", + "secondary-index-scattered-table", + "aggregate-status", + "aggregate-time-bucket", + "aggregate-tenant-time-range", + "parallel-read-aggregates", + "parallel-read-write-transition", + "feed-order-by-limit", + "feed-pagination-adjacent", + "join-order-items", + "random-point-lookups", + "hot-index-cold-table", + "ledger-without-rowid-range", + "chat-log-select-limit", + "chat-log-select-indexed", + "chat-log-count", + "chat-log-sum", + "chat-tool-read-fanout", + "chat-tool-script", + "write-batch-after-wake", + "update-hot-partition", + "delete-churn-range-read", + "migration-create-indexes-large", + "migration-create-indexes-skewed-large", + "migration-table-rebuild-large", + "migration-add-column-large", + "migration-ddl-small" +]; +function positiveInteger3(value, fallback, name) { + const resolved = value ?? fallback; + if (!Number.isInteger(resolved) || resolved < 1) { + throw new Error(`${name} must be a positive integer`); } - return "unknown"; -} -function firstMatch(value, pattern) { - return pattern.exec(value)?.[1] ?? null; + return resolved; } -function hasPendingLaunch(value) { - if (typeof value !== "string" || value.length === 0) { - return false; - } - try { - const parsed = JSON.parse(value); - return parsed.pendingLaunch !== null && parsed.pendingLaunch !== void 0; - } catch { - return false; +function assertWorkload(workload) { + if (!WORKLOADS.includes(workload)) { + throw new Error(`unknown SQLite benchmark workload: ${workload}`); } } -function delay2(ms) { - return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms))); +function pseudoRandom(value) { + return Math.imul(value ^ 2654435769, 2246822507) >>> 0; } -async function createAgentConcurrent2Schema(database) { - await database.execute(`CREATE TABLE IF NOT EXISTS executor_tools ( - executor_id TEXT NOT NULL, - tool_name TEXT NOT NULL, - schema TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (executor_id, tool_name) - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv ( - key TEXT PRIMARY KEY, - value TEXT, - updated_at TEXT NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS thread_events ( - seq INTEGER PRIMARY KEY, - event_type TEXT NOT NULL, - payload TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (datetime('now')) - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS message_added_events ( - message_id TEXT PRIMARY KEY, - seq INTEGER NOT NULL UNIQUE - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS messages ( - message_id TEXT PRIMARY KEY, - role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')), - content TEXT NOT NULL, - meta TEXT, - user_state TEXT, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - cancelled INTEGER NOT NULL DEFAULT 0, - read_at TEXT, - parent_tool_use_id TEXT, - tool_result_for_message_id TEXT - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs ( - source_message_id TEXT NOT NULL, - assistant_message_id TEXT NOT NULL, - tool_use_id TEXT NOT NULL, - block_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')), - cancelled INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (source_message_id, block_type, tool_use_id) - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)` - ); - await database.execute( - `CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS tool_calls ( - id TEXT PRIMARY KEY, - provider_tool_use_id TEXT NOT NULL, - tool_name TEXT NOT NULL, - args TEXT NOT NULL, - executor_id TEXT, - message_id TEXT NOT NULL, - issued_at TEXT NOT NULL, - expires_at TEXT, - state TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')), - result TEXT, - progress TEXT, - completed_at TEXT - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS e2b_sandbox ( - id INTEGER PRIMARY KEY CHECK (id = 1), - sandbox_id TEXT, - restart_attempts INTEGER NOT NULL DEFAULT 0, - traffic_access_token TEXT, - project_id TEXT, - repository_url TEXT, - additional_repositories TEXT, - setup TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - )` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)` - ); +function paddedHex(value) { + return pseudoRandom(value).toString(16).padStart(8, "0"); } -async function seedAgentConcurrent2Data(database) { - const existing = await database.execute(`SELECT COUNT(*) AS count FROM messages`); - if (Number(existing[0]?.count ?? 0) > 0) { - return; - } - const now = (/* @__PURE__ */ new Date("2026-05-16T03:58:18.661Z")).getTime(); - const text2 = (size) => "x".repeat(size); - const isoAt = (index) => new Date(now + index * 1e3).toISOString(); - await batchInsert2(database, `INSERT INTO thread_meta_kv (key, value, updated_at)`, [ - ["executor_type", "local-client", isoAt(0)], - ["workspace_intent", JSON.stringify({ spec: null, pendingLaunch: null }), isoAt(0)], - ["executor_status", JSON.stringify({ available: true, message: "ready" }), isoAt(0)] - ]); - const messageRows = []; - for (let index = 1; index <= MESSAGE_COUNT; index++) { - const role = index % 2 === 0 ? "assistant" : "user"; - messageRows.push([ - messageId(index), - role, - text2(MESSAGE_CONTENT_BYTES), - null, - null, - isoAt(index), - 0, - null, - null, - null - ]); - } - await batchInsert2( - database, - `INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`, - messageRows, - 20 - ); - const messageToolRefRows = []; - for (let index = 0; index < MESSAGE_TOOL_REF_COUNT / 2; index++) { - const assistantIndex = 2 + index % 42 * 2; - const sourceIndex = Math.max(1, assistantIndex - 1); - const resultIndex = Math.min(MESSAGE_COUNT, assistantIndex + 1); - const toolUseId = toolUseID(index + 1); - messageToolRefRows.push([ - messageId(sourceIndex), - messageId(assistantIndex), - toolUseId, - "tool_use", - 0 - ]); - messageToolRefRows.push([ - messageId(resultIndex), - messageId(assistantIndex), - toolUseId, - "tool_result", - 0 - ]); - } - await batchInsert2( - database, - `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`, - messageToolRefRows, - 50 - ); - const toolCallRows = []; - for (let index = 1; index <= TOOL_CALL_COUNT; index++) { - const assistantIndex = 2 + (index - 1) % 42 * 2; - toolCallRows.push([ - toolUseID(index), - `provider-${index}`, - `tool_${index % 21}`, - JSON.stringify({ path: `/tmp/file-${index}` }), - "seed-executor", - messageId(assistantIndex), - isoAt(index), - null, - "completed", - JSON.stringify({ - ok: true, - run: { status: "done", result: text2(TOOL_CALL_RESULT_BYTES) } - }), - null, - isoAt(index + 100) - ]); - } - await batchInsert2( - database, - `INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`, - toolCallRows, - 20 - ); - const executorToolRows = []; - for (let index = 1; index <= EXECUTOR_TOOL_COUNT; index++) { - const schema = JSON.stringify({ - name: `tool_${index}`, - description: text2(EXECUTOR_TOOL_SCHEMA_BYTES), - input_schema: { type: "object", properties: {} } - }); - executorToolRows.push(["seed-executor", `tool_${index}`, schema, isoAt(index)]); - } - await batchInsert2( - database, - `INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`, - executorToolRows, - 42 +function payload(prefix, bytes) { + return prefix + "x".repeat(Math.max(0, bytes - prefix.length)); +} +function typedRows2(rows) { + return rows; +} +async function queryPageCount(database) { + const [row] = typedRows2( + await database.execute("PRAGMA page_count") ); - const threadEventRows = []; - for (let index = 1; index <= THREAD_EVENT_COUNT; index++) { - threadEventRows.push([ - index, - index % 3 === 0 ? "message_added" : "agent_state_changed", - JSON.stringify({ type: "seed_event", body: text2(THREAD_EVENT_PAYLOAD_BYTES) }), - isoAt(index) - ]); - } - await batchInsert2( - database, - `INSERT INTO thread_events (seq, event_type, payload, created_at)`, - threadEventRows, - 25 + return row?.page_count ?? 0; +} +async function resetCommerce(database) { + await database.execute("DELETE FROM rw_order_items"); + await database.execute("DELETE FROM rw_orders"); + await database.execute("DELETE FROM rw_customers"); + await database.execute("DELETE FROM rw_events"); +} +async function resetDocs(database) { + await database.execute("DELETE FROM rw_docs"); +} +async function resetLedger(database) { + await database.execute("DELETE FROM rw_ledger"); +} +async function resetChatLog(database) { + await database.execute("DELETE FROM rw_chat_log"); +} +async function resetSqlRush(database) { + await database.execute("DELETE FROM tool_refs"); + await database.execute("DELETE FROM msgs"); + await database.execute("DELETE FROM events"); + await database.execute("DELETE FROM kv"); + await database.execute("DELETE FROM tools"); + await database.execute("DELETE FROM meta"); +} +async function resetMigration(database) { + await database.execute( + "DROP INDEX IF EXISTS idx_rw_migration_source_account" ); - const messageAddedRows = []; - for (let index = 1; index <= MESSAGE_COUNT; index++) { - messageAddedRows.push([messageId(index), index]); - } - await batchInsert2( - database, - `INSERT INTO message_added_events (message_id, seq)`, - messageAddedRows, - 50 + await database.execute( + "DROP INDEX IF EXISTS idx_rw_migration_source_created" ); await database.execute( - `INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`, - JSON.stringify({ cwd: "/workspace", body: text2(3620) }), - isoAt(0) + "DROP INDEX IF EXISTS idx_rw_migration_source_status_total" ); await database.execute( - `INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`, - JSON.stringify({ maxTokens: 2e4, body: text2(55) }), - isoAt(0) + "DROP INDEX IF EXISTS idx_rw_migration_source_skew_account" ); await database.execute( - `INSERT INTO e2b_sandbox (id, sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup, created_at, updated_at) - VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - "sandbox-seed", - 0, - "token-seed", - "project-seed", - "https://example.invalid/repo.git", - JSON.stringify([]), - JSON.stringify({ commands: [] }), - isoAt(0), - isoAt(0) + "DROP INDEX IF EXISTS idx_rw_migration_source_skew_status" ); + await database.execute("DROP TABLE IF EXISTS rw_migration_source_rebuilt"); + await database.execute("DROP TABLE IF EXISTS rw_migration_source"); + await database.execute("DROP TABLE IF EXISTS rw_migration_audit"); + await database.execute("DROP TABLE IF EXISTS rw_migration_empty"); } -async function batchInsert2(database, insertPrefix, rows, batchSize = 100) { - if (rows.length === 0) { - return; +async function withTransaction(database, fn) { + let inTransaction = false; + await database.execute("BEGIN"); + inTransaction = true; + try { + await fn(); + await database.execute("COMMIT"); + inTransaction = false; + } catch (err) { + if (inTransaction) { + await database.execute("ROLLBACK").catch(() => void 0); + } + throw err; } - const columnCount = rows[0]?.length ?? 0; - if (columnCount === 0) { - return; +} +async function seedCommerce(database, targetBytes, rowBytes) { + await resetCommerce(database); + const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); + const customerCount = Math.max(32, Math.ceil(rows / 16)); + const startedAt = performance.now(); + await withTransaction(database, async () => { + for (let offset = 0; offset < customerCount; offset += ORDER_BATCH_ROWS) { + const placeholders = []; + const args = []; + const batchEnd = Math.min(customerCount, offset + ORDER_BATCH_ROWS); + for (let i = offset; i < batchEnd; i += 1) { + placeholders.push("(?, ?, ?, ?, ?)"); + args.push( + i + 1, + `acct-${i % 64}`, + `user-${paddedHex(i)}@example.test`, + ["free", "pro", "team", "enterprise"][i % 4], + ["iad", "sfo", "fra", "sin"][i % 4] + ); + } + await database.execute( + `INSERT INTO rw_customers (id, account_id, email, plan, region) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + }); + for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { + const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); + await withTransaction(database, async () => { + for (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) { + const orderPlaceholders = []; + const orderArgs = []; + const itemPlaceholders = []; + const itemArgs = []; + const eventPlaceholders = []; + const eventArgs = []; + const batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS); + for (let i = offset; i < batchEnd; i += 1) { + const id = i + 1; + const customerId = pseudoRandom(i) % customerCount + 1; + const createdAt = 17e11 + i * 1e3; + const status = ["pending", "paid", "shipped", "refunded"][i % 4]; + const totalCents = 500 + pseudoRandom(i + 17) % 25e3; + const note = payload(`order-${id}-${status}:`, rowBytes); + orderPlaceholders.push("(?, ?, ?, ?, ?, ?, ?)"); + orderArgs.push( + id, + customerId, + createdAt, + status, + totalCents, + i % 128, + note + ); + for (let item = 0; item < 2; item += 1) { + itemPlaceholders.push("(?, ?, ?, ?, ?)"); + itemArgs.push( + id, + `sku-${paddedHex(i + item).slice(0, 6)}`, + 1 + (i + item) % 5, + 100 + pseudoRandom(i + item + 31) % 5e3, + item + ); + } + eventPlaceholders.push("(?, ?, ?, ?, ?)"); + eventArgs.push( + `acct-${customerId % 64}`, + ["click", "purchase", "refund", "shipment"][i % 4], + createdAt, + `order:${id}`, + payload(`event-${id}:`, Math.min(rowBytes, 512)) + ); + } + await database.execute( + `INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${orderPlaceholders.join(", ")}`, + ...orderArgs + ); + await database.execute( + `INSERT INTO rw_order_items (order_id, sku, quantity, price_cents, line_no) VALUES ${itemPlaceholders.join(", ")}`, + ...itemArgs + ); + await database.execute( + `INSERT INTO rw_events (account_id, event_type, created_at, entity_key, properties) VALUES ${eventPlaceholders.join(", ")}`, + ...eventArgs + ); + } + }); } - const rowPlaceholder = `(${"?,".repeat(columnCount).slice(0, -1)})`; - for (let index = 0; index < rows.length; index += batchSize) { - const chunk = rows.slice(index, index + batchSize); - const values = chunk.map(() => rowPlaceholder).join(","); - const bindings = chunk.flat(); - await database.execute(`${insertPrefix} VALUES ${values}`, ...bindings); + return { + rows, + targetBytes, + rowBytes, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; +} +async function seedDocs(database, targetBytes, rowBytes) { + await resetDocs(database); + const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); + const startedAt = performance.now(); + for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { + const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); + await withTransaction(database, async () => { + for (let offset = txStart; offset < txEnd; offset += DOC_BATCH_ROWS) { + const placeholders = []; + const args = []; + const batchEnd = Math.min(txEnd, offset + DOC_BATCH_ROWS); + for (let i = offset; i < batchEnd; i += 1) { + const rank = pseudoRandom(i); + const body = payload(`doc-${i}-${rank}:`, rowBytes); + placeholders.push("(?, ?, ?, ?, ?)"); + args.push( + `doc-${paddedHex(i)}`, + rank, + `tenant-${rank % 128}`, + body, + rowBytes + ); + } + await database.execute( + `INSERT INTO rw_docs (external_key, row_rank, tenant_id, body, body_bytes) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + }); + } + return { + rows, + targetBytes, + rowBytes, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; +} +async function seedLedger(database, targetBytes, rowBytes) { + await resetLedger(database); + const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); + const startedAt = performance.now(); + for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { + const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); + await withTransaction(database, async () => { + for (let offset = txStart; offset < txEnd; offset += LEDGER_BATCH_ROWS) { + const placeholders = []; + const args = []; + const batchEnd = Math.min(txEnd, offset + LEDGER_BATCH_ROWS); + for (let i = offset; i < batchEnd; i += 1) { + const accountId = `acct-${String(i % 256).padStart(4, "0")}`; + const entryId = Math.floor(i / 256) + 1; + placeholders.push("(?, ?, ?, ?, ?)"); + args.push( + accountId, + entryId, + (i % 2 === 0 ? 1 : -1) * (100 + i % 1e4), + 17e11 + i * 1e3, + payload( + `ledger-${accountId}-${entryId}:`, + Math.min(rowBytes, 512) + ) + ); + } + await database.execute( + `INSERT INTO rw_ledger (account_id, entry_id, amount_cents, created_at, memo) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + }); } + return { + rows, + targetBytes, + rowBytes, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; } -function messageId(index) { - return `M-${String(index).padStart(22, "0")}`; +function buildChatLogMessage(seq, targetBytes) { + const prefix = `message-${seq}: `; + return prefix + "x".repeat(Math.max(0, targetBytes - prefix.length)); } -function toolUseID(index) { - return `toolu_${String(index).padStart(22, "0")}`; +async function seedChatLog(database, targetBytes) { + await resetChatLog(database); + const createdAtBase = 17e11; + let remainingBytes = targetBytes; + let rows = 0; + const startedAt = performance.now(); + await withTransaction(database, async () => { + while (remainingBytes > 0) { + const placeholders = []; + const args = []; + for (let batchIndex = 0; batchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0; batchIndex += 1) { + const contentBytes = Math.min( + CHAT_LOG_CHUNK_BYTES, + remainingBytes + ); + const seq = rows; + const role = seq % 2 === 0 ? "user" : "assistant"; + placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); + args.push( + CHAT_THREAD_ID, + seq, + role, + buildChatLogMessage(seq, contentBytes), + contentBytes, + Math.ceil(contentBytes / 4), + createdAtBase + seq + ); + remainingBytes -= contentBytes; + rows += 1; + } + await database.execute( + `INSERT INTO rw_chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + }); + return { + rows, + targetBytes, + rowBytes: CHAT_LOG_CHUNK_BYTES, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; } -function safeId(value) { - return value.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80); +async function batchInsert3(database, sql, rows, batchSize) { + if (rows.length === 0) return; + const colsPerRow = rows[0]?.length ?? 0; + if (colsPerRow === 0) return; + const placeholder = `(${"?,".repeat(colsPerRow).slice(0, -1)})`; + for (let i = 0; i < rows.length; i += batchSize) { + const chunk = rows.slice(i, i + batchSize); + const values = new Array(chunk.length).fill(placeholder).join(","); + const args = []; + for (const row of chunk) args.push(...row); + await database.execute(`${sql} VALUES ${values}`, ...args); + } } - -// src/actors/testing/sigterm-sleep-probe.ts -import { actor as actor58 } from "rivetkit"; -import { db as db14 } from "rivetkit/db"; -var DEFAULT_ON_SLEEP_DURATION_MS = 5e3; -var DEFAULT_ON_SLEEP_TICK_MS = 1e3; -var SLEEP_TIMEOUT_MS = 10 * 60 * 1e3; -var SLEEP_GRACE_PERIOD_MS = 30 * 60 * 1e3; -var ACTOR_STOPPED_CLOSE_CODE = 1e3; -var ACTOR_STOPPED_CLOSE_REASON = "actor stopped"; -function sleep5(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); +async function seedSqlRush(database, targetBytes) { + await resetSqlRush(database); + const now = 17e11; + const startedAt = performance.now(); + await withTransaction(database, async () => { + const msgsRows = []; + for (let i = 0; i < SQL_RUSH_MSGS_COUNT; i += 1) { + msgsRows.push([ + i === 0 ? null : i, + i % 3 === 0 ? "user" : "assistant", + payload("msg:", 512), + 0, + now - (SQL_RUSH_MSGS_COUNT - i) * 1e3 + ]); + } + await batchInsert3( + database, + "INSERT INTO msgs (parent, role, content, cancelled, created_at)", + msgsRows, + 50 + ); + const toolRefsRows = []; + for (let i = 0; i < SQL_RUSH_TOOL_REFS_COUNT; i += 1) { + toolRefsRows.push([ + i + 1, + `tool_${i % 20}`, + `call_${i}`, + i % 5 === 0 ? "pending" : "done" + ]); + } + await batchInsert3( + database, + "INSERT INTO tool_refs (msg_id, tool_name, tool_call_id, status)", + toolRefsRows, + 100 + ); + const eventsRows = []; + for (let i = 0; i < SQL_RUSH_EVENTS_COUNT; i += 1) { + eventsRows.push([ + i + 1, + `event_${i % 8}`, + payload("event:", 256), + now - (SQL_RUSH_EVENTS_COUNT - i) * 100 + ]); + } + await batchInsert3( + database, + "INSERT INTO events (seq, event_type, payload, created_at)", + eventsRows, + 100 + ); + const kvRows = []; + for (let i = 0; i < SQL_RUSH_KV_COUNT; i += 1) { + kvRows.push([`kv_${i}`, payload("kv:", 128), now]); + } + await batchInsert3( + database, + "INSERT INTO kv (key, value, updated_at)", + kvRows, + 40 + ); + const toolsRows = []; + for (let i = 0; i < SQL_RUSH_TOOLS_COUNT; i += 1) { + toolsRows.push([ + "exec-1", + `tool_${i}`, + payload("tool:", 1024), + now + ]); + } + await batchInsert3( + database, + "INSERT INTO tools (executor_id, name, spec, updated_at)", + toolsRows, + 41 + ); + const metaRows = []; + for (let i = 0; i < SQL_RUSH_META_COUNT; i += 1) { + metaRows.push([`key_${i}`, payload("meta:", 64)]); + } + await batchInsert3( + database, + "INSERT INTO meta (key, value)", + metaRows, + 12 + ); + }); + return { + rows: SQL_RUSH_MSGS_COUNT + SQL_RUSH_TOOL_REFS_COUNT + SQL_RUSH_EVENTS_COUNT + SQL_RUSH_KV_COUNT + SQL_RUSH_TOOLS_COUNT + SQL_RUSH_META_COUNT, + targetBytes, + rowBytes: 0, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; } -function formatError(error) { - if (error instanceof Error) return error.stack ?? error.message; - return String(error); +async function seedMigrationSource(database, targetBytes, rowBytes, skewed = false) { + await resetMigration(database); + await database.execute(`CREATE TABLE rw_migration_source ( + id INTEGER PRIMARY KEY, + account_id TEXT NOT NULL, + status TEXT NOT NULL, + created_at INTEGER NOT NULL, + total_cents INTEGER NOT NULL, + body TEXT NOT NULL + )`); + const rows = Math.max(1, Math.ceil(targetBytes / rowBytes)); + const startedAt = performance.now(); + for (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) { + const txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS); + await withTransaction(database, async () => { + for (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) { + const placeholders = []; + const args = []; + const batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS); + for (let i = offset; i < batchEnd; i += 1) { + const accountId = skewed ? `acct-${i % 10 === 0 ? i % 512 : i % 8}` : `acct-${pseudoRandom(i) % 512}`; + const status = skewed ? i % 20 === 0 ? "failed" : "open" : ["open", "closed", "failed", "pending"][i % 4]; + placeholders.push("(?, ?, ?, ?, ?, ?)"); + args.push( + i + 1, + accountId, + status, + 17e11 + i * 1e3, + 100 + pseudoRandom(i + 41) % 5e4, + payload(`migration-${i}:`, rowBytes) + ); + } + await database.execute( + `INSERT INTO rw_migration_source (id, account_id, status, created_at, total_cents, body) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + }); + } + return { + rows, + targetBytes, + rowBytes, + setupMs: performance.now() - startedAt, + pageCount: await queryPageCount(database) + }; } -var sigtermSleepProbe = actor58({ - state: { - label: "unprepared", - wakeCount: 0, - sleepCount: 0, - onSleepDurationMs: DEFAULT_ON_SLEEP_DURATION_MS, - onSleepTickMs: DEFAULT_ON_SLEEP_TICK_MS, - connectionCount: 0, - messageCount: 0, - onSleepStartedAt: null, - onSleepAsyncFinishedAt: null, - onSleepFinishedAt: null, - onSleepLastError: null +async function readRowidRange(database, direction) { + const [count] = typedRows2( + await database.execute("SELECT COUNT(*) AS rows FROM rw_orders") + ); + const rows = count?.rows ?? 0; + let bytes = 0; + let scannedRows = 0; + if (direction === "backward") { + for (let upper = rows; upper > 0; upper -= RANGE_CHUNK_ROWS) { + const lower = Math.max(1, upper - RANGE_CHUNK_ROWS + 1); + const chunk = typedRows2( + await database.execute( + `SELECT length(note) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ? ORDER BY id DESC`, + lower, + upper + ) + ); + for (const row of chunk) { + bytes += row.bytes; + scannedRows += 1; + } + } + return { rows: scannedRows, bytes }; + } + for (let lower = 1; lower <= rows; lower += RANGE_CHUNK_ROWS) { + const upper = lower + RANGE_CHUNK_ROWS - 1; + const [chunk] = typedRows2( + await database.execute( + `SELECT COUNT(*) AS rows, COALESCE(SUM(length(note)), 0) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ?`, + lower, + upper + ) + ); + bytes += chunk?.bytes ?? 0; + scannedRows += chunk?.rows ?? 0; + } + return { rows: scannedRows, bytes }; +} +var sqliteRealworldBench = actor48({ + options: { + actionTimeout: 12e5, + sleepGracePeriod: 3e4 }, - createVars: () => ({ - websockets: /* @__PURE__ */ new Set() - }), - db: db14({ + db: db13({ onMigrate: async (database) => { - await database.execute(` - CREATE TABLE IF NOT EXISTS sigterm_sleep_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - event TEXT NOT NULL, - sleep_count INTEGER NOT NULL, - detail TEXT, - created_at INTEGER NOT NULL - ) - `); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_customers ( + id INTEGER PRIMARY KEY, + account_id TEXT NOT NULL, + email TEXT NOT NULL, + plan TEXT NOT NULL, + region TEXT NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_orders ( + id INTEGER PRIMARY KEY, + customer_id INTEGER NOT NULL, + created_at INTEGER NOT NULL, + status TEXT NOT NULL, + total_cents INTEGER NOT NULL, + shard INTEGER NOT NULL, + note TEXT NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_order_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + sku TEXT NOT NULL, + quantity INTEGER NOT NULL, + price_cents INTEGER NOT NULL, + line_no INTEGER NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account_id TEXT NOT NULL, + event_type TEXT NOT NULL, + created_at INTEGER NOT NULL, + entity_key TEXT NOT NULL, + properties TEXT NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_docs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + external_key TEXT NOT NULL UNIQUE, + row_rank INTEGER NOT NULL, + tenant_id TEXT NOT NULL, + body TEXT NOT NULL, + body_bytes INTEGER NOT NULL + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_ledger ( + account_id TEXT NOT NULL, + entry_id INTEGER NOT NULL, + amount_cents INTEGER NOT NULL, + created_at INTEGER NOT NULL, + memo TEXT NOT NULL, + PRIMARY KEY (account_id, entry_id) + ) WITHOUT ROWID`); + await database.execute(`CREATE TABLE IF NOT EXISTS rw_chat_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + thread_id TEXT NOT NULL, + seq INTEGER NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL, + content_bytes INTEGER NOT NULL, + token_estimate INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT 0 + )`); + await database.execute( + "CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY AUTOINCREMENT, parent INTEGER, role TEXT NOT NULL, content TEXT NOT NULL, cancelled INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL)" + ); + await database.execute( + "CREATE TABLE IF NOT EXISTS tool_refs (id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id INTEGER NOT NULL, tool_name TEXT NOT NULL, tool_call_id TEXT NOT NULL, status TEXT NOT NULL)" + ); + await database.execute( + "CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY, event_type TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL)" + ); + await database.execute( + "CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)" + ); + await database.execute( + "CREATE TABLE IF NOT EXISTS tools (id INTEGER PRIMARY KEY AUTOINCREMENT, executor_id TEXT NOT NULL, name TEXT NOT NULL, spec TEXT NOT NULL, updated_at INTEGER NOT NULL)" + ); + await database.execute( + "CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_orders_customer_created ON rw_orders(customer_id, created_at DESC)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_orders_status_created ON rw_orders(status, created_at)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_orders_created ON rw_orders(created_at DESC)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_order_items_order ON rw_order_items(order_id)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_events_account_created ON rw_events(account_id, created_at)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_docs_external_rank ON rw_docs(external_key, row_rank)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_docs_tenant_rank ON rw_docs(tenant_id, row_rank)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_rw_chat_log_thread_seq ON rw_chat_log(thread_id, seq DESC)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_msgs_parent_role_cancelled_created_at ON msgs (parent, role, cancelled, created_at)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_tool_refs_msg_id ON tool_refs (msg_id)" + ); } }), - onWebSocket: (c, websocket) => { - c.vars.websockets.add(websocket); - c.state.connectionCount += 1; - const connectionId = crypto.randomUUID(); - c.log.info({ - msg: "sigterm sleep probe websocket connected", - label: c.state.label, - connectionId, - connectionCount: c.state.connectionCount - }); - websocket.send( - JSON.stringify({ - type: "welcome", - connectionId, - label: c.state.label, - connectionCount: c.state.connectionCount - }) - ); - websocket.addEventListener("message", (event21) => { - c.state.messageCount += 1; - const data = event21.data; - if (typeof data !== "string") return; - try { - const parsed = JSON.parse(data); - if (parsed.type === "ping") { - websocket.send( - JSON.stringify({ - type: "pong", - connectionId, - messageCount: c.state.messageCount, - timestamp: Date.now() - }) + actions: { + inspectCacheConfig: async (c) => { + const [cacheSize] = typedRows2( + await c.db.execute("PRAGMA cache_size") + ); + const [pageSize] = typedRows2( + await c.db.execute("PRAGMA page_size") + ); + return { + sqliteCacheSizePragma: cacheSize?.cache_size ?? null, + sqlitePageSize: pageSize?.page_size ?? null, + pageCount: await queryPageCount(c.db) + }; + }, + setupWorkload: async (c, input) => { + assertWorkload(input.workload); + const rowBytes = positiveInteger3( + input.rowBytes, + DEFAULT_ROW_BYTES4, + "rowBytes" + ); + if (input.workload === "migration-ddl-small") { + await resetMigration(c.db); + return { + rows: 0, + targetBytes: 0, + rowBytes, + setupMs: 0, + pageCount: await queryPageCount(c.db) + }; + } + const targetBytes = positiveInteger3( + input.targetBytes, + 8 * 1024 * 1024, + "targetBytes" + ); + switch (input.workload) { + case "small-rowid-point": + case "small-schema-read": + case "small-range-scan": + case "rowid-range-forward": + case "rowid-range-backward": + case "aggregate-status": + case "aggregate-time-bucket": + case "aggregate-tenant-time-range": + case "parallel-read-aggregates": + case "parallel-read-write-transition": + case "feed-order-by-limit": + case "feed-pagination-adjacent": + case "join-order-items": + case "random-point-lookups": + case "write-batch-after-wake": + case "update-hot-partition": + case "delete-churn-range-read": + return seedCommerce(c.db, targetBytes, rowBytes); + case "secondary-index-covering-range": + case "secondary-index-scattered-table": + case "hot-index-cold-table": + return seedDocs(c.db, targetBytes, rowBytes); + case "ledger-without-rowid-range": + return seedLedger(c.db, targetBytes, rowBytes); + case "chat-log-select-limit": + case "chat-log-select-indexed": + case "chat-log-count": + case "chat-log-sum": + case "chat-tool-read-fanout": + return seedChatLog(c.db, targetBytes); + case "chat-tool-script": + return seedSqlRush(c.db, targetBytes); + case "migration-create-indexes-large": + return seedMigrationSource(c.db, targetBytes, rowBytes); + case "migration-create-indexes-skewed-large": + return seedMigrationSource( + c.db, + targetBytes, + rowBytes, + true + ); + case "migration-table-rebuild-large": + case "migration-add-column-large": + return seedMigrationSource(c.db, targetBytes, rowBytes); + } + }, + runWorkload: async (c, input) => { + assertWorkload(input.workload); + const t0 = performance.now(); + let details; + switch (input.workload) { + case "small-rowid-point": { + let bytes = 0; + for (let i = 0; i < 50; i += 1) { + const id = i % 16 + 1; + const [row] = typedRows2( + await c.db.execute( + "SELECT length(note) AS bytes FROM rw_orders WHERE id = ?", + id + ) + ); + bytes += row?.bytes ?? 0; + } + details = { ops: 50, bytes }; + break; + } + case "small-schema-read": { + const tables = await c.db.execute( + "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index') ORDER BY name" + ); + const columns = await c.db.execute( + "PRAGMA table_info(rw_orders)" + ); + const [count] = typedRows2( + await c.db.execute( + "SELECT COUNT(*) AS rows FROM rw_orders" + ) + ); + details = { + objects: tables.length, + columns: columns.length, + rows: count?.rows ?? 0 + }; + break; + } + case "small-range-scan": + case "rowid-range-forward": { + details = await readRowidRange(c.db, "forward"); + break; + } + case "rowid-range-backward": { + details = await readRowidRange(c.db, "backward"); + break; + } + case "secondary-index-covering-range": { + const rows = typedRows2( + await c.db.execute( + `SELECT external_key, row_rank FROM rw_docs + WHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff' + ORDER BY external_key` + ) + ); + let checksum2 = 0; + for (const row of rows) + checksum2 = checksum2 + row.row_rank >>> 0; + details = { rows: rows.length, checksum: checksum2 }; + break; + } + case "secondary-index-scattered-table": { + const rows = typedRows2( + await c.db.execute( + `SELECT body_bytes AS bytes FROM rw_docs + WHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff' + ORDER BY external_key` + ) + ); + let bytes = 0; + for (const row of rows) bytes += row.bytes; + details = { rows: rows.length, bytes }; + break; + } + case "aggregate-status": { + const rows = typedRows2( + await c.db.execute( + `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total + FROM rw_orders + GROUP BY status + ORDER BY status` + ) + ); + details = { + groups: rows.length, + rows: rows.reduce((sum, row) => sum + row.rows, 0), + total: rows.reduce((sum, row) => sum + row.total, 0) + }; + break; + } + case "aggregate-time-bucket": { + const rows = typedRows2( + await c.db.execute( + `SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total + FROM rw_orders + GROUP BY bucket + ORDER BY bucket` + ) + ); + details = { + buckets: rows.length, + rows: rows.reduce((sum, row) => sum + row.rows, 0), + total: rows.reduce((sum, row) => sum + row.total, 0) + }; + break; + } + case "aggregate-tenant-time-range": { + const rows = typedRows2( + await c.db.execute( + `SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total + FROM rw_events e + JOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER) + WHERE e.account_id = ? AND e.created_at BETWEEN ? AND ? + GROUP BY e.event_type + ORDER BY e.event_type`, + "acct-7", + 17e11, + 17e11 + 864e5 + ) + ); + details = { + groups: rows.length, + rows: rows.reduce((sum, row) => sum + row.rows, 0), + total: rows.reduce((sum, row) => sum + row.total, 0) + }; + break; + } + case "parallel-read-aggregates": { + const [statusRows, bucketRows, tenantRows, joinRows] = await Promise.all([ + c.db.execute( + `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total + FROM rw_orders + GROUP BY status + ORDER BY status` + ), + c.db.execute( + `SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total + FROM rw_orders + GROUP BY bucket + ORDER BY bucket` + ), + c.db.execute( + `SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total + FROM rw_events e + JOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER) + WHERE e.account_id = ? AND e.created_at BETWEEN ? AND ? + GROUP BY e.event_type + ORDER BY e.event_type`, + "acct-7", + 17e11, + 17e11 + 864e5 + ), + c.db.execute( + `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total + FROM rw_orders o + JOIN rw_order_items oi ON oi.order_id = o.id + GROUP BY o.status + ORDER BY o.status` + ) + ]); + const aggregates = [ + ...typedRows2(statusRows), + ...typedRows2(bucketRows), + ...typedRows2(tenantRows), + ...typedRows2(joinRows) + ]; + details = { + ops: 4, + groups: aggregates.length, + rows: aggregates.reduce( + (sum, row) => sum + row.rows, + 0 + ), + total: aggregates.reduce( + (sum, row) => sum + row.total, + 0 + ) + }; + break; + } + case "parallel-read-write-transition": { + const readStatus = c.db.execute( + `SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total + FROM rw_orders + GROUP BY status + ORDER BY status` + ); + const readJoin = c.db.execute( + `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total + FROM rw_orders o + JOIN rw_order_items oi ON oi.order_id = o.id + GROUP BY o.status + ORDER BY o.status` + ); + const writeHotShard = c.db.execute( + "UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 7" + ); + const readAfterWrite = c.db.execute( + "SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 7" + ); + const [statusRows, joinRows, , shardRows] = await Promise.all([ + readStatus, + readJoin, + writeHotShard, + readAfterWrite + ]); + const aggregates = [ + ...typedRows2(statusRows), + ...typedRows2(joinRows) + ]; + const [shardCount] = typedRows2(shardRows); + details = { + ops: 4, + readOps: 3, + writeOps: 1, + groups: aggregates.length, + rows: aggregates.reduce((sum, row) => sum + row.rows, 0) + (shardCount?.rows ?? 0), + total: aggregates.reduce( + (sum, row) => sum + row.total, + 0 + ) + }; + break; + } + case "feed-order-by-limit": { + const rows = await c.db.execute( + `SELECT id, customer_id, created_at, status, total_cents + FROM rw_orders + WHERE created_at >= ? + ORDER BY created_at DESC + LIMIT 1000`, + 17e11 + ); + details = { rows: rows.length }; + break; + } + case "feed-pagination-adjacent": { + const firstPage = typedRows2( + await c.db.execute( + `SELECT created_at + FROM rw_orders + WHERE created_at >= ? + ORDER BY created_at DESC + LIMIT ?`, + 17e11, + FEED_PAGE_ROWS + ) + ); + const cursor = firstPage.at(-1)?.created_at ?? 17e11; + const secondPage = await c.db.execute( + `SELECT id, customer_id, created_at, status, total_cents + FROM rw_orders + WHERE created_at < ? + ORDER BY created_at DESC + LIMIT ?`, + cursor, + FEED_PAGE_ROWS + ); + details = { + firstPageRows: firstPage.length, + rows: secondPage.length + }; + break; + } + case "join-order-items": { + const rows = typedRows2( + await c.db.execute( + `SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total + FROM rw_orders o + JOIN rw_order_items oi ON oi.order_id = o.id + GROUP BY o.status + ORDER BY o.status` + ) + ); + details = { + groups: rows.length, + rows: rows.reduce((sum, row) => sum + row.rows, 0), + total: rows.reduce((sum, row) => sum + row.total, 0) + }; + break; + } + case "random-point-lookups": { + const [count] = typedRows2( + await c.db.execute( + "SELECT COUNT(*) AS rows FROM rw_orders" + ) + ); + const rows = Math.max(1, count?.rows ?? 1); + let bytes = 0; + for (let i = 0; i < POINT_LOOKUP_OPS; i += 1) { + const id = pseudoRandom(i) % rows + 1; + const [row] = typedRows2( + await c.db.execute( + "SELECT length(note) AS bytes FROM rw_orders WHERE id = ?", + id + ) + ); + bytes += row?.bytes ?? 0; + } + details = { ops: POINT_LOOKUP_OPS, bytes }; + break; + } + case "hot-index-cold-table": { + const indexRows = typedRows2( + await c.db.execute( + `SELECT id + FROM rw_docs + WHERE tenant_id = ? + ORDER BY row_rank + LIMIT 1000`, + "tenant-7" + ) + ); + let bytes = 0; + for (const row of indexRows) { + const [doc] = typedRows2( + await c.db.execute( + "SELECT body_bytes AS bytes FROM rw_docs WHERE id = ?", + row.id + ) + ); + bytes += doc?.bytes ?? 0; + } + details = { rows: indexRows.length, bytes }; + break; + } + case "ledger-without-rowid-range": { + const rows = typedRows2( + await c.db.execute( + `SELECT account_id, entry_id, amount_cents, length(memo) AS bytes + FROM rw_ledger + WHERE account_id BETWEEN 'acct-0040' AND 'acct-0180' + ORDER BY account_id, entry_id` + ) ); - return; + let bytes = 0; + for (const row of rows) bytes += row.bytes; + details = { rows: rows.length, bytes }; + break; } - } catch { - } - websocket.send( - JSON.stringify({ - type: "echo", - connectionId, - received: data, - messageCount: c.state.messageCount, - timestamp: Date.now() - }) - ); - }); - websocket.addEventListener("close", (event21) => { - c.vars.websockets.delete(websocket); - c.state.connectionCount -= 1; - c.log.info({ - msg: "sigterm sleep probe websocket closed", - label: c.state.label, - connectionId, - connectionCount: c.state.connectionCount, - code: event21.code, - reason: event21.reason - }); - }); - }, - onWake: async (c) => { - c.state.wakeCount += 1; - c.log.info({ - msg: "sigterm sleep probe onWake", - label: c.state.label, - wakeCount: c.state.wakeCount, - sleepCount: c.state.sleepCount - }); - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "wake", - c.state.sleepCount, - `wake-${c.state.wakeCount}`, - Date.now() - ); - }, - onSleep: async (c) => { - const sleepCount = c.state.sleepCount + 1; - const startedAt = Date.now(); - c.state.sleepCount = sleepCount; - c.state.onSleepStartedAt = startedAt; - c.state.onSleepAsyncFinishedAt = null; - c.state.onSleepFinishedAt = null; - c.state.onSleepLastError = null; - c.log.info({ - msg: "sigterm sleep probe onSleep start", - label: c.state.label, - sleepCount, - onSleepDurationMs: c.state.onSleepDurationMs, - onSleepTickMs: c.state.onSleepTickMs - }); - try { - for (const websocket of c.vars.websockets) { - if (websocket.readyState !== 1) continue; - websocket.send( - JSON.stringify({ - type: "onSleepStarted", - sleepCount, - onSleepDurationMs: c.state.onSleepDurationMs, - onSleepTickMs: c.state.onSleepTickMs, - timestamp: startedAt - }) - ); - } - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "on-sleep-start", - sleepCount, - c.state.label, - startedAt - ); - const deadline = startedAt + c.state.onSleepDurationMs; - let tickIndex = 0; - while (Date.now() < deadline) { - const waitMs = Math.min( - c.state.onSleepTickMs, - Math.max(0, deadline - Date.now()) - ); - if (waitMs > 0) await sleep5(waitMs); - tickIndex += 1; - const tickAt = Date.now(); - const detail = `tick=${tickIndex} elapsed-ms=${tickAt - startedAt}`; - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "on-sleep-tick", - sleepCount, - detail, - tickAt - ); - c.log.info({ - msg: "sigterm sleep probe onSleep tick", - label: c.state.label, - sleepCount, - tickIndex, - elapsedMs: tickAt - startedAt - }); - for (const websocket of c.vars.websockets) { - if (websocket.readyState !== 1) continue; - websocket.send( - JSON.stringify({ - type: "onSleepTick", - sleepCount, - tickIndex, - elapsedMs: tickAt - startedAt, - timestamp: tickAt - }) + case "chat-log-select-limit": { + const rows = await c.db.execute( + "SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100" + ); + details = { rows: rows.length }; + break; + } + case "chat-log-select-indexed": { + const expectedRows = Math.max( + 1, + Math.ceil( + positiveInteger3( + input.targetBytes, + CHAT_LOG_CHUNK_BYTES, + "targetBytes" + ) / CHAT_LOG_CHUNK_BYTES + ) + ); + const lowerBound = Math.max(0, expectedRows - 100); + const rows = await c.db.execute( + "SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", + CHAT_THREAD_ID, + lowerBound + ); + details = { rows: rows.length }; + break; + } + case "chat-log-count": { + const [row] = typedRows2( + await c.db.execute( + "SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?", + CHAT_THREAD_ID + ) + ); + details = { ops: 1, rows: row?.count ?? 0 }; + break; + } + case "chat-log-sum": { + const [row] = typedRows2( + await c.db.execute( + "SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?", + CHAT_THREAD_ID + ) + ); + details = { ops: 1, bytes: row?.total_bytes ?? 0 }; + break; + } + case "chat-tool-read-fanout": { + const expectedRows = Math.max( + 1, + Math.ceil( + positiveInteger3( + input.targetBytes, + CHAT_LOG_CHUNK_BYTES, + "targetBytes" + ) / CHAT_LOG_CHUNK_BYTES + ) + ); + const lowerBound = Math.max(0, expectedRows - 100); + const [limitRows, indexedRows, countRows, sumRows] = await Promise.all([ + c.db.execute( + "SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100" + ), + c.db.execute( + "SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", + CHAT_THREAD_ID, + lowerBound + ), + c.db.execute( + "SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?", + CHAT_THREAD_ID + ), + c.db.execute( + "SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?", + CHAT_THREAD_ID + ) + ]); + const [countRow] = typedRows2(countRows); + const [sumRow] = typedRows2( + sumRows + ); + details = { + ops: 4, + limitRows: limitRows.length, + indexedRows: indexedRows.length, + rows: countRow?.count ?? 0, + bytes: sumRow?.total_bytes ?? 0 + }; + break; + } + case "chat-tool-script": { + const [ + msgsRows, + toolRefsRows, + eventsRows, + kvRows, + toolsRows, + metaRows, + unresolvedRows + ] = await Promise.all([ + c.db.execute( + "SELECT id, role, length(content) AS bytes FROM msgs WHERE parent IS NOT NULL AND role = ? AND cancelled = 0 ORDER BY created_at DESC LIMIT 50", + "assistant" + ), + c.db.execute( + "SELECT id, tool_name, status FROM tool_refs WHERE status = ? ORDER BY id DESC LIMIT 50", + "pending" + ), + c.db.execute( + "SELECT seq, event_type, length(payload) AS bytes FROM events WHERE seq > ? ORDER BY seq ASC LIMIT 100", + 600 + ), + c.db.execute( + "SELECT key, length(value) AS bytes FROM kv ORDER BY updated_at DESC LIMIT 20" + ), + c.db.execute( + "SELECT id, name, length(spec) AS bytes FROM tools WHERE executor_id = ? ORDER BY updated_at DESC", + "exec-1" + ), + c.db.execute( + "SELECT key, length(value) AS bytes FROM meta" + ), + c.db.execute(`SELECT m.id, m.role, count(tr.id) AS pending_refs + FROM msgs m + LEFT JOIN tool_refs tr ON tr.msg_id = m.id AND tr.status = 'pending' + WHERE m.role = 'assistant' AND m.cancelled = 0 + GROUP BY m.id + ORDER BY m.created_at DESC + LIMIT 100`) + ]); + details = { + ops: 7, + msgsRows: msgsRows.length, + toolRefsRows: toolRefsRows.length, + eventsRows: eventsRows.length, + kvRows: kvRows.length, + toolsRows: toolsRows.length, + metaRows: metaRows.length, + unresolvedRows: unresolvedRows.length + }; + break; + } + case "write-batch-after-wake": { + const [count] = typedRows2( + await c.db.execute( + "SELECT COUNT(*) AS rows FROM rw_orders" + ) ); + const startId = (count?.rows ?? 0) + 1; + await c.db.execute("BEGIN"); + for (let offset = 0; offset < 1e3; offset += ORDER_BATCH_ROWS) { + const placeholders = []; + const args = []; + for (let i = offset; i < offset + ORDER_BATCH_ROWS; i += 1) { + const id = startId + i; + placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); + args.push( + id, + i % 128 + 1, + 18e11 + i, + "pending", + 1e3 + i, + i % 128, + payload( + `wake-insert-${id}:`, + DEFAULT_ROW_BYTES4 + ) + ); + } + await c.db.execute( + `INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + await c.db.execute("COMMIT"); + details = { rows: 1e3 }; + break; } - } - const asyncFinishedAt = Date.now(); - c.state.onSleepAsyncFinishedAt = asyncFinishedAt; - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "on-sleep-after-await", - sleepCount, - `delay-ms=${asyncFinishedAt - startedAt}`, - asyncFinishedAt - ); - const finishedAt = Date.now(); - c.state.onSleepFinishedAt = finishedAt; - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "on-sleep-finish", - sleepCount, - c.state.label, - finishedAt - ); - for (const websocket of c.vars.websockets) { - if (websocket.readyState !== 1) continue; - websocket.send( - JSON.stringify({ - type: "onSleepFinished", - sleepCount, - elapsedMs: finishedAt - startedAt, - timestamp: finishedAt - }) - ); - websocket.close( - ACTOR_STOPPED_CLOSE_CODE, - ACTOR_STOPPED_CLOSE_REASON - ); - } - c.log.info({ - msg: "sigterm sleep probe onSleep finish", - label: c.state.label, - sleepCount, - elapsedMs: finishedAt - startedAt - }); - } catch (error) { - const message = formatError(error); - c.state.onSleepLastError = message; - c.log.error({ - msg: "sigterm sleep probe onSleep error", - label: c.state.label, - sleepCount, - error: message - }); - throw error; - } - }, - actions: { - prepare: async (c, label = `sigterm-sleep-probe-${Date.now()}`, onSleepDurationMs = DEFAULT_ON_SLEEP_DURATION_MS, onSleepTickMs = DEFAULT_ON_SLEEP_TICK_MS) => { - if (!Number.isFinite(onSleepDurationMs) || onSleepDurationMs < 0) { - throw new Error("onSleepDurationMs must be a finite non-negative number"); - } - if (!Number.isFinite(onSleepTickMs) || onSleepTickMs <= 0) { - throw new Error("onSleepTickMs must be a finite positive number"); - } - c.state.label = label; - c.state.onSleepDurationMs = onSleepDurationMs; - c.state.onSleepTickMs = onSleepTickMs; - await c.db.execute( - "INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)", - "prepared", - c.state.sleepCount, - label, - Date.now() - ); - return { - label: c.state.label, - onSleepDurationMs: c.state.onSleepDurationMs, - onSleepTickMs: c.state.onSleepTickMs, - wakeCount: c.state.wakeCount, - sleepCount: c.state.sleepCount, - connectionCount: c.state.connectionCount, - messageCount: c.state.messageCount - }; - }, - getProof: async (c) => { - const rows = await c.db.execute("SELECT * FROM sigterm_sleep_log ORDER BY id"); - return { - state: { - label: c.state.label, - wakeCount: c.state.wakeCount, - sleepCount: c.state.sleepCount, - onSleepDurationMs: c.state.onSleepDurationMs, - onSleepTickMs: c.state.onSleepTickMs, - connectionCount: c.state.connectionCount, - messageCount: c.state.messageCount, - onSleepStartedAt: c.state.onSleepStartedAt, - onSleepAsyncFinishedAt: c.state.onSleepAsyncFinishedAt, - onSleepFinishedAt: c.state.onSleepFinishedAt, - onSleepLastError: c.state.onSleepLastError - }, - rows - }; - } - }, - options: { - canHibernateWebSocket: false, - sleepTimeout: SLEEP_TIMEOUT_MS, - sleepGracePeriod: SLEEP_GRACE_PERIOD_MS - } -}); - -// src/actors/testing/slow-reconnect-actor.ts -import { actor as actor59, setup } from "rivetkit"; -import { db as db15 } from "rivetkit/db"; -var AsyncMutex2 = class { - locked = false; - waiters = []; - async acquire() { - if (!this.locked) { - this.locked = true; - return; - } - await new Promise((resolve) => this.waiters.push(resolve)); - this.locked = true; - } - release() { - const next = this.waiters.shift(); - if (next) { - next(); - return; - } - this.locked = false; - } -}; -function createDb(execute) { - const mutex = new AsyncMutex2(); - let activeTransaction = null; - const createTransactionDb = () => { - const tx = Object.assign( - (query, ...values) => execute(query, ...values), - { - withTransaction: async (fn) => fn(tx) - } - ); - return tx; - }; - const queryWithMutex = async (query, ...values) => { - if (activeTransaction) { - return activeTransaction(query, ...values); - } - await mutex.acquire(); - try { - return await execute(query, ...values); - } finally { - mutex.release(); - } - }; - const sql = Object.assign(queryWithMutex, { - withTransaction: async (fn) => { - if (activeTransaction) { - return fn(activeTransaction); - } - await mutex.acquire(); - const tx = createTransactionDb(); - try { - await execute("BEGIN"); - activeTransaction = tx; - try { - const result = await fn(tx); - activeTransaction = null; - await execute("COMMIT"); - return result; - } catch (error) { - activeTransaction = null; - await execute("ROLLBACK"); - throw error; + case "update-hot-partition": { + await c.db.execute( + "UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 15" + ); + const [count] = typedRows2( + await c.db.execute( + "SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 15" + ) + ); + details = { rows: count?.rows ?? 0 }; + break; + } + case "delete-churn-range-read": { + await c.db.execute( + "DELETE FROM rw_orders WHERE shard BETWEEN 0 AND 15" + ); + const result = await readRowidRange(c.db, "forward"); + details = { + ...result, + deletedShardCount: 16 + }; + break; + } + case "migration-create-indexes-large": { + await c.db.execute( + "CREATE INDEX idx_rw_migration_source_account ON rw_migration_source(account_id)" + ); + await c.db.execute( + "CREATE INDEX idx_rw_migration_source_created ON rw_migration_source(created_at)" + ); + await c.db.execute( + "CREATE INDEX idx_rw_migration_source_status_total ON rw_migration_source(status, total_cents)" + ); + details = { indexes: 3 }; + break; + } + case "migration-create-indexes-skewed-large": { + await c.db.execute( + "CREATE INDEX idx_rw_migration_source_skew_account ON rw_migration_source(account_id, created_at)" + ); + await c.db.execute( + "CREATE INDEX idx_rw_migration_source_skew_status ON rw_migration_source(status, total_cents)" + ); + details = { indexes: 2, skewed: true }; + break; + } + case "migration-table-rebuild-large": { + await c.db.execute(`CREATE TABLE rw_migration_source_rebuilt ( + id INTEGER PRIMARY KEY, + account_id TEXT NOT NULL, + status TEXT NOT NULL, + created_at INTEGER NOT NULL, + total_cents INTEGER NOT NULL, + body TEXT NOT NULL, + archived_at INTEGER + )`); + await c.db.execute(`INSERT INTO rw_migration_source_rebuilt ( + id, account_id, status, created_at, total_cents, body, archived_at + ) + SELECT id, account_id, status, created_at, total_cents, body, NULL + FROM rw_migration_source`); + await c.db.execute("DROP TABLE rw_migration_source"); + await c.db.execute( + "ALTER TABLE rw_migration_source_rebuilt RENAME TO rw_migration_source" + ); + details = { rebuilt: true }; + break; + } + case "migration-add-column-large": { + await c.db.execute( + "ALTER TABLE rw_migration_source ADD COLUMN archived_at INTEGER" + ); + details = { alters: 1, rewritesRows: false }; + break; + } + case "migration-ddl-small": { + await c.db.execute(`CREATE TABLE rw_migration_empty ( + id INTEGER PRIMARY KEY, + tenant_id TEXT NOT NULL, + created_at INTEGER NOT NULL + )`); + await c.db.execute( + "ALTER TABLE rw_migration_empty ADD COLUMN status TEXT" + ); + await c.db.execute( + "CREATE INDEX idx_rw_migration_empty_tenant_created ON rw_migration_empty(tenant_id, created_at)" + ); + await c.db.execute(`CREATE TABLE rw_migration_audit ( + id INTEGER PRIMARY KEY, + migration_name TEXT NOT NULL, + applied_at INTEGER NOT NULL + )`); + details = { tables: 2, indexes: 1, alters: 1 }; + break; } - } finally { - activeTransaction = null; - mutex.release(); } - } - }); - return sql; -} -var MESSAGE_COUNT2 = 84; -var MESSAGE_TOOL_REF_COUNT2 = 122; -var TOOL_CALL_COUNT2 = 61; -var EXECUTOR_TOOL_COUNT2 = 42; -var THREAD_EVENT_COUNT2 = 233; -var MESSAGE_CONTENT_BYTES2 = 10620; -var THREAD_EVENT_PAYLOAD_BYTES2 = 4036; -var TOOL_CALL_RESULT_BYTES2 = 10975; -var EXECUTOR_TOOL_SCHEMA_BYTES2 = 2235; -var slowReconnectActor = actor59({ - state: { runCount: 0 }, - db: db15({ - onMigrate: async (database) => { - await createSlowReconnectSchema(database); - } - }), - vars: { sql: null }, - onWebSocket: (c, ws) => { - const sock = ws; - if (sock.readyState === WebSocket.OPEN) { - sock.send("pong"); - } - ws.addEventListener("message", (event21) => { - const promise = handleSlowReconnectWebSocketMessage(c, sock, event21.data); - void c.keepAwake(promise); - }); - }, - actions: { - prepare: async (c) => { - await createSlowReconnectSchema(c.db); - return await seedSlowReconnectData(c.db); - }, - reproReconnect: async (c, clientId) => { - c.vars.sql ??= createSlowReconnectDb(c.db); - c.state.runCount++; - return await runReconnectRepro(c.vars.sql, clientId ?? `action-${c.state.runCount}`, 0); + const ms = performance.now() - t0; + return { + ms, + workload: input.workload, + ...details, + pageCount: await queryPageCount(c.db) + }; }, - getRunCount: (c) => c.state.runCount, - sleep: (c) => { + goToSleep: (c) => { c.sleep(); - return true; + return { ok: true }; } } }); -async function handleSlowReconnectWebSocketMessage(c, sock, data) { - if (data === "ping") { - if (sock.readyState === WebSocket.OPEN) { - sock.send("pong"); + +// src/actors/testing/test-counter.ts +import { actor as actor49 } from "rivetkit"; +var testCounter = actor49({ + state: { count: 0 }, + actions: { + increment: (c, amount = 1) => { + c.state.count += amount; + return c.state.count; + }, + getCount: (c) => { + return c.state.count; + }, + reset: (c) => { + c.state.count = 0; + return c.state.count; } - return; } - let trigger = "unknown"; - try { - const request = parseSlowReconnectRequest(data); - trigger = request.type; - c.vars.sql ??= createSlowReconnectDb(c.db); - c.state.runCount++; - if (request.type === "client_resume") { - const startedAt = performance.now(); - const result2 = await runCatchupSnapshot2(c.vars.sql, request.version); - sendJSON(sock, { - type: "slow_reconnect_result", - trigger: request.type, - totalMs: Math.round(performance.now() - startedAt), - results: [result2] - }); - return; - } - const clientId = request.type === "executor_connect" ? request.clientId : request.clientId ?? `slow-reconnect-${c.state.runCount}`; - const staggerHandleMs = request.type === "repro_reconnect" ? request.staggerHandleMs ?? 0 : 0; - const result = await runReconnectRepro(c.vars.sql, clientId, staggerHandleMs); - if (request.type === "executor_connect") { - sendJSON(sock, { - type: "executor_connected", - executorId: clientId, - registeredToolCount: EXECUTOR_TOOL_COUNT2, - guidanceInventory: [], - resumeBootstrap: true - }); +}); + +// src/actors/testing/test-counter-sqlite.ts +import { actor as actor50 } from "rivetkit"; +import { db as db14 } from "rivetkit/db"; +var testCounterSqlite = actor50({ + db: db14({ + onMigrate: async (db17) => { + await db17.execute(` + CREATE TABLE IF NOT EXISTS counter ( + id INTEGER PRIMARY KEY CHECK (id = 1), + value INTEGER NOT NULL DEFAULT 0 + ) + `); + await db17.execute( + "INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)" + ); } - sendJSON(sock, { - type: "slow_reconnect_result", - trigger: request.type, - ...result - }); - } catch (error) { - sendJSON(sock, { - type: "slow_reconnect_error", - trigger, - error: error instanceof Error ? error.message : String(error) - }); - } -} -function parseSlowReconnectRequest(data) { - if (typeof data !== "string") { - throw new Error("slowReconnectActor request must be a string"); - } - const parsed = JSON.parse(data); - if (!parsed || typeof parsed !== "object") { - throw new Error("slowReconnectActor request must be an object"); - } - const request = parsed; - if (request.type === "client_resume") { - return { type: "client_resume", version: numberField2(request, "version") }; - } - if (request.type === "executor_connect") { - const executorType = request.executorType; - return { - type: "executor_connect", - clientId: stringField2(request, "clientId"), - ...executorType === "local-client" || executorType === "sandbox" || executorType === "virtual" ? { executorType } : {} - }; - } - if (request.type === "repro_reconnect") { - return { - type: "repro_reconnect", - ...typeof request.clientId === "string" ? { clientId: request.clientId } : {}, - ...typeof request.staggerHandleMs === "number" ? { staggerHandleMs: request.staggerHandleMs } : {} - }; - } - throw new Error(`Unknown slowReconnectActor request type: ${String(request.type)}`); -} -function stringField2(record, field) { - const value = record[field]; - if (typeof value !== "string" || value.length === 0) { - throw new Error(`slowReconnectActor request ${field} must be a non-empty string`); - } - return value; -} -function numberField2(record, field) { - const value = record[field]; - if (typeof value !== "number" || !Number.isFinite(value)) { - throw new Error(`slowReconnectActor request ${field} must be a finite number`); - } - return value; -} -function sendJSON(sock, message) { - if (sock.readyState === WebSocket.OPEN) { - sock.send(JSON.stringify(message)); - } -} -function createSlowReconnectDb(db16) { - return createDb(async (query, ...values) => { - const converted = values.map( - (value) => typeof value === "boolean" ? value ? 1 : 0 : value - ); - return await db16.execute(query, ...converted); - }); -} -async function runReconnectRepro(sql, clientId, staggerHandleMs) { - const startedAt = performance.now(); - const buildToolPlanContext = await runBuildToolPlanContext2(sql); - const catchupSnapshot = await runCatchupSnapshot2(sql, 0); - const recoverToolCalls = await runRecoverToolCalls2(sql); - const handleExecutorConnect = await delay3(staggerHandleMs).then( - () => runHandleExecutorConnect(sql, clientId) - ); - const results = [ - handleExecutorConnect, - buildToolPlanContext, - catchupSnapshot, - recoverToolCalls - ]; - return { - totalMs: Math.round(performance.now() - startedAt), - results - }; -} -async function runHandleExecutorConnect(sql, clientId) { - const startedAt = performance.now(); - const steps = []; - const nextSeq = await sql.withTransaction(async (tx) => { - const latestExecutor = await timedQuery2( - tx, - steps, - "load-latest-executor-id", - `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` - ); - const latestExecutorId = String(latestExecutor[0]?.executor_id ?? "seed-executor"); - await timedQuery2( - tx, - steps, - "select-cached-executor-tools", - `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, - latestExecutorId - ); - const executorType = await timedQuery2( - tx, - steps, - "select-executor-type", - `SELECT value FROM thread_meta_kv WHERE key = 'executor_type'` - ); - if (!executorType[0]?.value) { - await timedQuery2( - tx, - steps, - "set-executor-type", - `INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`, - "local-client", - (/* @__PURE__ */ new Date()).toISOString() + }), + actions: { + increment: async (c, amount = 1) => { + await c.db.execute( + "UPDATE counter SET value = value + ? WHERE id = 1", + amount ); - } - const sandboxIntent = await timedQuery2( - tx, - steps, - "select-sandbox-intent", - `SELECT value FROM thread_meta_kv WHERE key = 'sandbox_intent'` - ); - if (hasPendingLaunch2(sandboxIntent[0]?.value)) { - await timedQuery2( - tx, - steps, - "clear-pending-launch", - `UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'sandbox_intent'`, - JSON.stringify({ spec: null, pendingLaunch: null }), - (/* @__PURE__ */ new Date()).toISOString() + const rows = await c.db.execute( + "SELECT value FROM counter WHERE id = 1" + ); + return rows[0].value; + }, + getCount: async (c) => { + const rows = await c.db.execute( + "SELECT value FROM counter WHERE id = 1" ); + return rows[0].value; + }, + reset: async (c) => { + await c.db.execute("UPDATE counter SET value = 0 WHERE id = 1"); + return 0; } - const seqRows = await timedQuery2( - tx, - steps, - "select-next-thread-event-seq", - `SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events` - ); - const seq = Number(seqRows[0]?.seq ?? 1); - await timedQuery2( - tx, - steps, - "insert-executor-connected-event", - `INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`, - seq, - "executor_connected", - JSON.stringify({ type: "executor_connected", executorId: clientId }), - (/* @__PURE__ */ new Date()).toISOString() - ); - return seq; - }); - steps.push({ - name: "transaction-total", - durationMs: Math.round(performance.now() - startedAt), - rowCount: nextSeq - }); - return { - name: "handle-executor-connect", - totalMs: Math.round(performance.now() - startedAt), - steps - }; -} -async function runBuildToolPlanContext2(sql) { - const startedAt = performance.now(); - const steps = []; - const latestExecutor = await timedQuery2( - sql, - steps, - "load-latest-executor-id", - `SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1` - ); - const latestExecutorId = String(latestExecutor[0]?.executor_id ?? "seed-executor"); - await timedQuery2( - sql, - steps, - "select-executor-tools", - `SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`, - latestExecutorId - ); - await timedQuery2( - sql, - steps, - "count-uncancelled-top-level", - `SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL` - ); - const unresolvedRows = await timedQuery2( - sql, - steps, - "find-unresolved-assistant-message", - `SELECT m.* - FROM message_tool_refs AS tool_use - JOIN messages AS m - ON m.message_id = tool_use.assistant_message_id - WHERE tool_use.block_type = 'tool_use' - AND tool_use.cancelled = 0 - AND m.cancelled = 0 - AND m.role = 'assistant' - AND m.parent_tool_use_id IS NULL - AND NOT EXISTS ( - SELECT 1 - FROM message_tool_refs AS tool_result - JOIN messages AS tool_result_message - ON tool_result_message.message_id = tool_result.source_message_id - WHERE tool_result.assistant_message_id = tool_use.assistant_message_id - AND tool_result.block_type = 'tool_result' - AND tool_result.cancelled = 0 - AND tool_result.tool_use_id = tool_use.tool_use_id - AND tool_result_message.parent_tool_use_id IS NULL - ) - GROUP BY m.message_id - ORDER BY m.created_at DESC - LIMIT 1` - ); - const unresolvedMessageId = unresolvedRows[0]?.message_id; - if (typeof unresolvedMessageId === "string") { - await timedQuery2( - sql, - steps, - "get-persisted-tool-result-ids", - `SELECT tool_result.tool_use_id - FROM message_tool_refs AS tool_result - JOIN messages AS tool_result_message - ON tool_result_message.message_id = tool_result.source_message_id - WHERE tool_result.assistant_message_id = ? - AND tool_result.block_type = 'tool_result' - AND tool_result.cancelled = 0 - AND tool_result_message.parent_tool_use_id IS NULL`, - unresolvedMessageId - ); - await timedQuery2( - sql, - steps, - "get-tool-calls-by-message-id", - `SELECT * FROM tool_calls WHERE message_id = ?`, - unresolvedMessageId - ); - } - await timedQuery2( - sql, - steps, - "is-last-message-cancelled-assistant", - `SELECT role, cancelled FROM messages - WHERE parent_tool_use_id IS NULL - ORDER BY created_at DESC - LIMIT 1` - ); - await timedQuery2( - sql, - steps, - "get-last-uncancelled", - `SELECT m.* FROM messages m - WHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL - ORDER BY m.created_at DESC - LIMIT 1` - ); - return { - name: "build-tool-plan-context", - totalMs: Math.round(performance.now() - startedAt), - steps - }; -} -async function runCatchupSnapshot2(sql, version) { - const startedAt = performance.now(); - const steps = []; - await timedQuery2( - sql, - steps, - "thread-events-list-since-version", - `SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`, - version - ); - await timedQuery2( - sql, - steps, - "environment-snapshot", - `SELECT snapshot FROM environment_snapshot WHERE id = 1` - ); - await timedQuery2( - sql, - steps, - "thread-settings-snapshot", - `SELECT settings FROM thread_settings_snapshot WHERE id = 1` - ); - await timedQuery2(sql, steps, "retry-state", `SELECT * FROM retry_state WHERE id = 1`); - await timedQuery2( - sql, - steps, - "queued-messages", - `SELECT * FROM queued_messages ORDER BY created_at ASC` - ); - await timedQuery2( - sql, - steps, - "executor-artifacts", - `SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC` - ); - await timedQuery2(sql, steps, "tool-approvals", `SELECT * FROM tool_approvals ORDER BY timestamp ASC`); - await timedQuery2( - sql, - steps, - "compaction-summaries", - `SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC` - ); - await timedQuery2( - sql, - steps, - "executor-status", - `SELECT value FROM thread_meta_kv WHERE key = 'executor_status'` - ); - steps.sort((a, b) => b.durationMs - a.durationMs); - return { name: "catchup-snapshot", totalMs: Math.round(performance.now() - startedAt), steps }; -} -async function runRecoverToolCalls2(sql) { - const startedAt = performance.now(); - const steps = []; - await timedQuery2( - sql, - steps, - "hydrate-tool-progress", - `SELECT id, progress - FROM tool_calls - WHERE progress IS NOT NULL - AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` - ); - await timedQuery2( - sql, - steps, - "get-pending-tool-calls", - `SELECT * FROM tool_calls - WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running') - ORDER BY issued_at ASC` - ); - await timedQuery2( - sql, - steps, - "get-next-tool-expiry", - `SELECT MIN(expires_at) AS expires_at - FROM tool_calls - WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` - ); - return { name: "recover-tool-calls", totalMs: Math.round(performance.now() - startedAt), steps }; -} -async function timedQuery2(sql, steps, name, query, ...values) { - const startedAt = performance.now(); - const rows = await sql(query, ...values); - steps.push({ - name, - durationMs: Math.round(performance.now() - startedAt), - rowCount: rows.length - }); - return rows; -} -function hasPendingLaunch2(value) { - if (typeof value !== "string" || value.length === 0) { - return false; } +}); + +// src/actors/testing/test-sqlite-bench.ts +import { actor as actor51 } from "rivetkit"; +import { db as db15 } from "rivetkit/db"; +var CHAT_LOG_CHUNK_BYTES2 = 4 * 1024; +var CHAT_LOG_INSERT_BATCH_SIZE2 = 50; +function buildChatLogMessage2(seq, targetBytes) { + const prefix = `message-${seq}: `; + return prefix + "x".repeat(Math.max(0, targetBytes - prefix.length)); +} +async function seedChatLog2(database, targetBytes) { + const threadId = `chat-${crypto.randomUUID()}`; + const createdAtBase = Date.now(); + let remainingBytes = targetBytes; + let rows = 0; + await database.execute("BEGIN"); try { - const parsed = JSON.parse(value); - return parsed.pendingLaunch !== null && parsed.pendingLaunch !== void 0; - } catch { - return false; + while (remainingBytes > 0) { + const placeholders = []; + const args = []; + for (let batchIndex = 0; batchIndex < CHAT_LOG_INSERT_BATCH_SIZE2 && remainingBytes > 0; batchIndex++) { + const contentBytes = Math.min( + CHAT_LOG_CHUNK_BYTES2, + remainingBytes + ); + const seq = rows; + const role = seq % 2 === 0 ? "user" : "assistant"; + placeholders.push("(?, ?, ?, ?, ?, ?, ?)"); + args.push( + threadId, + seq, + role, + buildChatLogMessage2(seq, contentBytes), + contentBytes, + Math.ceil(contentBytes / 4), + createdAtBase + seq + ); + remainingBytes -= contentBytes; + rows++; + } + await database.execute( + `INSERT INTO chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(", ")}`, + ...args + ); + } + await database.execute("COMMIT"); + } catch (err) { + await database.execute("ROLLBACK"); + throw err; } + return { threadId, rows, totalBytes: targetBytes }; } -function delay3(ms) { - return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms))); -} -async function createSlowReconnectSchema(database) { - await database.execute(`CREATE TABLE IF NOT EXISTS executor_tools ( - executor_id TEXT NOT NULL, - tool_name TEXT NOT NULL, - schema TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (executor_id, tool_name) - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv ( - key TEXT PRIMARY KEY, - value TEXT, - updated_at TEXT NOT NULL - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS thread_events ( - seq INTEGER PRIMARY KEY, - event_type TEXT NOT NULL, - payload TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (datetime('now')) - )`); - await database.execute(`CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)`); - await database.execute(`CREATE TABLE IF NOT EXISTS message_added_events ( - message_id TEXT PRIMARY KEY, - seq INTEGER NOT NULL UNIQUE - )`); - await database.execute(`CREATE TABLE IF NOT EXISTS messages ( - message_id TEXT PRIMARY KEY, - role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')), - content TEXT NOT NULL, - meta TEXT, - user_state TEXT, - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), - cancelled INTEGER NOT NULL DEFAULT 0, - read_at TEXT, - parent_tool_use_id TEXT, - tool_result_for_message_id TEXT - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs ( - source_message_id TEXT NOT NULL, - assistant_message_id TEXT NOT NULL, - tool_use_id TEXT NOT NULL, - block_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')), - cancelled INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (source_message_id, block_type, tool_use_id) - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)` - ); - await database.execute( - `CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0` - ); - await database.execute(`CREATE TABLE IF NOT EXISTS tool_calls ( - id TEXT PRIMARY KEY, - provider_tool_use_id TEXT NOT NULL, - tool_name TEXT NOT NULL, - args TEXT NOT NULL, - executor_id TEXT, - message_id TEXT NOT NULL, - issued_at TEXT NOT NULL, - expires_at TEXT, - state TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')), - result TEXT, - progress TEXT, - completed_at TEXT - )`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)` - ); - await database.execute(`CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)`); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))` - ); - await database.execute( - `CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)` - ); - await database.execute( - `CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)` - ); -} -async function seedSlowReconnectData(database) { - const existing = await database.execute(`SELECT COUNT(*) AS count FROM messages`); - if (Number(existing[0]?.count ?? 0) > 0) { - const [toolCalls] = await database.execute(`SELECT COUNT(*) AS count FROM tool_calls`); - const [threadEvents] = await database.execute(`SELECT COUNT(*) AS count FROM thread_events`); - return { - seeded: false, - messages: Number(existing[0]?.count ?? 0), - toolCalls: Number(toolCalls?.count ?? 0), - threadEvents: Number(threadEvents?.count ?? 0) - }; +var testSqliteBench = actor51({ + options: { + actionTimeout: 3e5 + }, + db: db15({ + onMigrate: async (database) => { + await database.execute(`CREATE TABLE IF NOT EXISTS bench ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL, + value TEXT NOT NULL, + num INTEGER NOT NULL DEFAULT 0, + payload BLOB, + created_at INTEGER NOT NULL DEFAULT 0 + )`); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_bench_key ON bench(key)" + ); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_bench_num ON bench(num)" + ); + await database.execute(`CREATE TABLE IF NOT EXISTS bench_json ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + data TEXT NOT NULL DEFAULT '{}' + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS bench_secondary ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bench_id INTEGER NOT NULL, + label TEXT NOT NULL, + score REAL NOT NULL DEFAULT 0, + FOREIGN KEY (bench_id) REFERENCES bench(id) + )`); + await database.execute(`CREATE TABLE IF NOT EXISTS chat_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + thread_id TEXT NOT NULL, + seq INTEGER NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL, + content_bytes INTEGER NOT NULL, + token_estimate INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT 0 + )`); + await database.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_log_thread_seq ON chat_log(thread_id, seq DESC)" + ); + } + }), + actions: { + noop: (_c) => ({ ok: true }), + goToSleep: (c) => { + c.sleep(); + return { ok: true }; + }, + insertSingle: async (c, n) => { + const t0 = performance.now(); + for (let i = 0; i < n; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `k-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + return { ms: performance.now() - t0, ops: n }; + }, + insertTx: async (c, n) => { + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < n; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `k-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: n }; + }, + insertBatch: async (c, n) => { + const t0 = performance.now(); + const placeholders = Array.from( + { length: n }, + () => "(?, ?, ?, ?)" + ).join(", "); + const args = []; + for (let i = 0; i < n; i++) { + args.push(`k-${i}`, `v-${i}`, i, Date.now()); + } + await c.db.execute( + `INSERT INTO bench (key, value, num, created_at) VALUES ${placeholders}`, + ...args + ); + return { ms: performance.now() - t0, ops: n }; + }, + pointRead: async (c, n) => { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES ('pr', 'pr', 0, 0)" + ); + const rows = await c.db.execute( + "SELECT id FROM bench WHERE key = 'pr' LIMIT 1" + ); + const id = rows[0].id; + const t0 = performance.now(); + for (let i = 0; i < n; i++) { + await c.db.execute("SELECT * FROM bench WHERE id = ?", id); + } + return { ms: performance.now() - t0, ops: n }; + }, + fullScan: async (c, seedRows) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < seedRows; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `scan-${i}`, + `val-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute("SELECT * FROM bench"); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + rangeScanIndexed: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `rs-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT * FROM bench WHERE num BETWEEN 100 AND 300" + ); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + rangeScanUnindexed: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `ru-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT * FROM bench WHERE value BETWEEN 'v-100' AND 'v-300'" + ); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + bulkUpdate: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `bu-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + await c.db.execute( + "UPDATE bench SET value = 'updated', num = num + 1000 WHERE key LIKE 'bu-%'" + ); + return { ms: performance.now() - t0, seedMs, ops: 200 }; + }, + bulkDelete: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `bd-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + await c.db.execute("DELETE FROM bench WHERE key LIKE 'bd-%'"); + return { ms: performance.now() - t0, seedMs, ops: 200 }; + }, + hotRowUpdates: async (c, n) => { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES ('hot', 'v', 0, 0)" + ); + const rows = await c.db.execute( + "SELECT id FROM bench WHERE key = 'hot' LIMIT 1" + ); + const id = rows[0].id; + const t0 = performance.now(); + for (let i = 0; i < n; i++) { + await c.db.execute( + "UPDATE bench SET num = ? WHERE id = ?", + i, + id + ); + } + return { ms: performance.now() - t0, ops: n }; + }, + vacuumAfterDelete: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `vac-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + await c.db.execute("DELETE FROM bench WHERE key LIKE 'vac-%'"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + await c.db.execute("VACUUM"); + return { ms: performance.now() - t0, seedMs }; + }, + largePayloadInsert: async (c, n) => { + const blob = "x".repeat(32 * 1024); + const t0 = performance.now(); + for (let i = 0; i < n; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, payload, created_at) VALUES (?, ?, ?, ?, ?)", + `lp-${i}`, + `v-${i}`, + i, + blob, + Date.now() + ); + } + return { ms: performance.now() - t0, ops: n }; + }, + mixedOltp: async (c) => { + const t0 = performance.now(); + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + "oltp", + "initial", + 0, + Date.now() + ); + const rows = await c.db.execute( + "SELECT * FROM bench WHERE key = 'oltp' LIMIT 1" + ); + const id = rows[0].id; + await c.db.execute( + "UPDATE bench SET value = 'updated', num = 1 WHERE id = ?", + id + ); + await c.db.execute("SELECT * FROM bench WHERE id = ?", id); + return { ms: performance.now() - t0, ops: 4 }; + }, + jsonInsertAndQuery: async (c) => { + const t0 = performance.now(); + for (let i = 0; i < 50; i++) { + await c.db.execute( + "INSERT INTO bench_json (data) VALUES (?)", + JSON.stringify({ + name: `item-${i}`, + tags: ["a", "b"], + score: Math.random() * 100 + }) + ); + } + const rows = await c.db.execute( + "SELECT id, json_extract(data, '$.name') as name, json_extract(data, '$.score') as score FROM bench_json ORDER BY json_extract(data, '$.score') DESC LIMIT 10" + ); + return { + ms: performance.now() - t0, + ops: 51, + rows: rows.length + }; + }, + jsonEachAgg: async (c) => { + await c.db.execute( + "INSERT INTO bench_json (data) VALUES (?)", + JSON.stringify({ + items: Array.from({ length: 100 }, (_, i) => ({ + id: i, + val: i * 10 + })) + }) + ); + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT SUM(json_extract(value, '$.val')) as total FROM bench_json, json_each(json_extract(data, '$.items')) LIMIT 1" + ); + return { + ms: performance.now() - t0, + total: rows[0].total + }; + }, + complexAggregation: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `grp-${i % 10}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT key, COUNT(*) as cnt, AVG(num) as avg_num, MIN(num) as min_num, MAX(num) as max_num FROM bench WHERE key LIKE 'grp-%' GROUP BY key ORDER BY cnt DESC" + ); + return { + ms: performance.now() - t0, + seedMs, + groups: rows.length + }; + }, + complexSubquery: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `sq-${i}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT * FROM bench WHERE num > (SELECT AVG(num) FROM bench) ORDER BY num DESC LIMIT 50" + ); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + complexJoin: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `j-${i}`, + `v-${i}`, + i, + Date.now() + ); + await c.db.execute( + "INSERT INTO bench_secondary (bench_id, label, score) VALUES (?, ?, ?)", + i + 1, + `label-${i}`, + Math.random() * 100 + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT b.key, b.num, s.label, s.score FROM bench b INNER JOIN bench_secondary s ON s.bench_id = b.id WHERE b.key LIKE 'j-%' ORDER BY s.score DESC LIMIT 200" + ); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + complexCteWindow: async (c) => { + const t0Seed = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 200; i++) { + await c.db.execute( + "INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)", + `cte-${i % 10}`, + `v-${i}`, + i, + Date.now() + ); + } + await c.db.execute("COMMIT"); + const seedMs = performance.now() - t0Seed; + const t0 = performance.now(); + const rows = await c.db.execute(` + WITH ranked AS ( + SELECT key, num, ROW_NUMBER() OVER (PARTITION BY key ORDER BY num DESC) as rn, + AVG(num) OVER (PARTITION BY key) as avg_num + FROM bench + WHERE key LIKE 'cte-%' + ) + SELECT * FROM ranked WHERE rn <= 3 ORDER BY key, rn + `); + return { + ms: performance.now() - t0, + seedMs, + rows: rows.length + }; + }, + migrationTables: async (c, n) => { + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < n; i++) { + await c.db.execute(`CREATE TABLE IF NOT EXISTS mig_${i} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + data TEXT NOT NULL DEFAULT '' + )`); + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: n }; + }, + chatLogInsert: async (c, totalBytes) => { + const t0 = performance.now(); + const seeded = await seedChatLog2(c.db, totalBytes); + return { + ms: performance.now() - t0, + ops: seeded.rows, + bytes: seeded.totalBytes + }; + }, + chatLogSelectLimit: async (c, totalBytes) => { + const seeded = await seedChatLog2(c.db, totalBytes); + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT seq, role, substr(content, 1, 128) AS preview FROM chat_log ORDER BY created_at DESC LIMIT 100" + ); + return { + ms: performance.now() - t0, + ops: rows.length, + rows: rows.length, + bytes: seeded.totalBytes + }; + }, + chatLogSelectIndexed: async (c, totalBytes) => { + const seeded = await seedChatLog2(c.db, totalBytes); + const lowerBound = Math.max(0, seeded.rows - 100); + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT seq, role, content_bytes FROM chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100", + seeded.threadId, + lowerBound + ); + return { + ms: performance.now() - t0, + ops: rows.length, + rows: rows.length, + bytes: seeded.totalBytes + }; + }, + chatLogCount: async (c, totalBytes) => { + const seeded = await seedChatLog2(c.db, totalBytes); + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT COUNT(*) AS count FROM chat_log WHERE thread_id = ?", + seeded.threadId + ); + return { + ms: performance.now() - t0, + ops: 1, + count: rows[0].count, + bytes: seeded.totalBytes + }; + }, + chatLogSum: async (c, totalBytes) => { + const seeded = await seedChatLog2(c.db, totalBytes); + const t0 = performance.now(); + const rows = await c.db.execute( + "SELECT SUM(content_bytes) AS total_bytes FROM chat_log WHERE thread_id = ?", + seeded.threadId + ); + return { + ms: performance.now() - t0, + ops: 1, + totalBytes: rows[0].total_bytes ?? 0, + bytes: seeded.totalBytes + }; + }, + largeTxInsert500KB: async (c) => { + const targetBytes = 500 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + largeTxInsert1MB: async (c) => { + const targetBytes = 1024 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + // 1 MiB total, 4096 × 256 B rows. Max NAPI crossings. + largeTxInsert1MBTinyRows: async (c) => { + const targetBytes = 1024 * 1024; + const rowSize = 256; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + // 1 MiB total, 256 × 4 KiB rows. Same shape as largeTxInsert1MB; kept as a sanity duplicate. + largeTxInsert1MBMediumRows: async (c) => { + const targetBytes = 1024 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + // 1 MiB total, 1 × 1 MiB row. One NAPI crossing, exercises SQLite overflow-page chain. + largeTxInsert1MBOneRow: async (c) => { + const rowSize = 1024 * 1024; + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: 1, bytes: rowSize }; + }, + largeTxInsert5MB: async (c) => { + const targetBytes = 5 * 1024 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + largeTxInsert10MB: async (c) => { + const targetBytes = 10 * 1024 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + largeTxInsert50MB: async (c) => { + const targetBytes = 50 * 1024 * 1024; + const rowSize = 4 * 1024; + const rowCount = Math.ceil(targetBytes / rowSize); + await c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < rowCount; i++) { + await c.db.execute( + "INSERT INTO large_tx (payload) VALUES (randomblob(?))", + rowSize + ); + } + await c.db.execute("COMMIT"); + return { + ms: performance.now() - t0, + ops: rowCount, + bytes: rowCount * rowSize + }; + }, + // Stress test: insert 1000 rows, delete them all, repeat 10 times. + // Tests freelist reuse and space reclamation patterns. + churnInsertDelete: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS churn ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + const t0 = performance.now(); + const cycles = 10; + const perCycle = 1e3; + for (let cycle = 0; cycle < cycles; cycle++) { + await c.db.execute("BEGIN"); + for (let i = 0; i < perCycle; i++) { + await c.db.execute( + "INSERT INTO churn (payload) VALUES (randomblob(1024))" + ); + } + await c.db.execute("DELETE FROM churn"); + await c.db.execute("COMMIT"); + } + return { + ms: performance.now() - t0, + ops: cycles * perCycle, + cycles + }; + }, + // Interleave inserts, updates, deletes in same transaction. Tests how + // the VFS handles mixed page dirtying patterns. + mixedOltpLarge: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS mixed_oltp ( + id INTEGER PRIMARY KEY, + value INTEGER NOT NULL, + data BLOB NOT NULL + )`); + await c.db.execute("DELETE FROM mixed_oltp"); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))", + i, + i * 2 + ); + } + await c.db.execute("COMMIT"); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))", + 500 + i, + i * 3 + ); + await c.db.execute( + "UPDATE mixed_oltp SET value = value + 1 WHERE id = ?", + i + ); + if (i % 5 === 0) { + await c.db.execute( + "DELETE FROM mixed_oltp WHERE id = ?", + i - 50 >= 0 ? i - 50 : i + ); + } + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: 500 * 2 + 100 }; + }, + // Growing aggregation: insert then SELECT SUM after each batch. + // Tests cache invalidation and read-after-write patterns. + growingAggregation: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS agg_test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + value INTEGER NOT NULL + )`); + await c.db.execute("DELETE FROM agg_test"); + const t0 = performance.now(); + const batches = 20; + const perBatch = 100; + let lastSum = 0; + for (let batch2 = 0; batch2 < batches; batch2++) { + await c.db.execute("BEGIN"); + for (let i = 0; i < perBatch; i++) { + await c.db.execute( + "INSERT INTO agg_test (value) VALUES (?)", + batch2 * perBatch + i + ); + } + await c.db.execute("COMMIT"); + const rows = await c.db.execute( + "SELECT SUM(value) AS s FROM agg_test" + ); + lastSum = rows[0]?.s ?? 0; + } + return { + ms: performance.now() - t0, + ops: batches * perBatch, + batches, + lastSum + }; + }, + // Create index on already-populated table. Tests large rewrite patterns. + indexCreationOnLargeTable: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS idx_test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL, + value INTEGER NOT NULL + )`); + await c.db.execute("DROP INDEX IF EXISTS idx_test_key"); + await c.db.execute("DELETE FROM idx_test"); + await c.db.execute("BEGIN"); + for (let i = 0; i < 1e4; i++) { + await c.db.execute( + "INSERT INTO idx_test (key, value) VALUES (?, ?)", + `key-${i % 1e3}-${i}`, + i + ); + } + await c.db.execute("COMMIT"); + const t0 = performance.now(); + await c.db.execute("CREATE INDEX idx_test_key ON idx_test(key)"); + return { ms: performance.now() - t0, ops: 1e4 }; + }, + // Update 1000 different rows in separate UPDATEs in one transaction. + // Stresses B-tree navigation and page dirtying. + bulkUpdate1000Rows: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS bulk_update ( + id INTEGER PRIMARY KEY, + value INTEGER NOT NULL + )`); + await c.db.execute("DELETE FROM bulk_update"); + await c.db.execute("BEGIN"); + for (let i = 0; i < 1e3; i++) { + await c.db.execute( + "INSERT INTO bulk_update (id, value) VALUES (?, ?)", + i, + i + ); + } + await c.db.execute("COMMIT"); + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 1e3; i++) { + await c.db.execute( + "UPDATE bulk_update SET value = value + 1 WHERE id = ?", + i + ); + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: 1e3 }; + }, + // Delete everything then re-insert. Tests truncate+regrow cycle. + truncateAndRegrow: async (c) => { + await c.db.execute(`CREATE TABLE IF NOT EXISTS regrow ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + payload BLOB NOT NULL + )`); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO regrow (payload) VALUES (randomblob(1024))" + ); + } + await c.db.execute("COMMIT"); + const t0 = performance.now(); + await c.db.execute("DELETE FROM regrow"); + await c.db.execute("BEGIN"); + for (let i = 0; i < 500; i++) { + await c.db.execute( + "INSERT INTO regrow (payload) VALUES (randomblob(1024))" + ); + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: 500 }; + }, + // Many small tables vs one large. Tests schema page growth. + manySmallTables: async (c) => { + const t0 = performance.now(); + await c.db.execute("BEGIN"); + for (let i = 0; i < 50; i++) { + await c.db.execute( + `CREATE TABLE IF NOT EXISTS small_t_${i} (id INTEGER PRIMARY KEY, value INTEGER)` + ); + for (let j = 0; j < 10; j++) { + await c.db.execute( + `INSERT INTO small_t_${i} (id, value) VALUES (?, ?)`, + j, + i * j + ); + } + } + await c.db.execute("COMMIT"); + return { ms: performance.now() - t0, ops: 50 * 10, tables: 50 }; + } } - const vary = (base, min = 1) => Math.max(min, Math.round(base * (0.25 + Math.random() * 3.75))); - const messageCount = vary(MESSAGE_COUNT2, 2); - const messageToolRefCount = vary(MESSAGE_TOOL_REF_COUNT2, 2); - const toolCallCount = vary(TOOL_CALL_COUNT2); - const executorToolCount = vary(EXECUTOR_TOOL_COUNT2); - const threadEventCount = vary(THREAD_EVENT_COUNT2); - const assistantSpan = Math.max(1, Math.floor(messageCount / 2)); - const now = (/* @__PURE__ */ new Date("2026-05-16T03:58:18.661Z")).getTime(); - const text2 = (size) => "x".repeat(size); - const isoAt = (index) => new Date(now + index * 1e3).toISOString(); - await batchInsert3(database, `INSERT INTO thread_meta_kv (key, value, updated_at)`, [ - ["executor_type", "local-client", isoAt(0)], - ["sandbox_intent", JSON.stringify({ spec: null, pendingLaunch: null }), isoAt(0)], - ["executor_status", JSON.stringify({ available: true, message: "ready" }), isoAt(0)] - ]); - const messageRows = []; - for (let index = 1; index <= messageCount; index++) { - const role = index % 2 === 0 ? "assistant" : "user"; - messageRows.push([ - messageId2(index), - role, - text2(MESSAGE_CONTENT_BYTES2), - null, - null, - isoAt(index), - 0, - null, - null, - null - ]); +}); + +// src/actors/testing/test-sqlite-load.ts +import { actor as actor52 } from "rivetkit"; +import { db as db16 } from "rivetkit/db"; +var testSqliteLoad = actor52({ + db: db16({ + onMigrate: async (db17) => { + await db17.execute(` + CREATE TABLE IF NOT EXISTS schema_version ( + id INTEGER PRIMARY KEY CHECK (id = 1), + version INTEGER NOT NULL DEFAULT 50 + ) + `); + await db17.execute( + "INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, 50)" + ); + await db17.execute(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT, + created_at INTEGER NOT NULL + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + price REAL NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + total REAL NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'pending', + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS order_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + price REAL NOT NULL, + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + description TEXT + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS product_categories ( + product_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + PRIMARY KEY (product_id, category_id), + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (category_id) REFERENCES categories(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS reviews ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5), + comment TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS addresses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + street TEXT NOT NULL, + city TEXT NOT NULL, + state TEXT, + zip TEXT, + country TEXT NOT NULL DEFAULT 'US', + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS payments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + amount REAL NOT NULL, + method TEXT NOT NULL DEFAULT 'card', + status TEXT NOT NULL DEFAULT 'pending', + processed_at INTEGER, + FOREIGN KEY (order_id) REFERENCES orders(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS inventory ( + product_id INTEGER PRIMARY KEY, + quantity INTEGER NOT NULL DEFAULT 0, + reserved INTEGER NOT NULL DEFAULT 0, + last_restocked_at INTEGER, + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS coupons ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL UNIQUE, + discount_percent REAL NOT NULL, + max_uses INTEGER, + used_count INTEGER NOT NULL DEFAULT 0, + expires_at INTEGER + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS shipping ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + address_id INTEGER NOT NULL, + carrier TEXT, + tracking_number TEXT, + shipped_at INTEGER, + delivered_at INTEGER, + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (address_id) REFERENCES addresses(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS product_tags ( + product_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + PRIMARY KEY (product_id, tag_id), + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (tag_id) REFERENCES tags(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS wishlists ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL DEFAULT 'Default', + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS wishlist_items ( + wishlist_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + added_at INTEGER NOT NULL, + PRIMARY KEY (wishlist_id, product_id), + FOREIGN KEY (wishlist_id) REFERENCES wishlists(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS notifications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + type TEXT NOT NULL, + message TEXT NOT NULL, + read INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS audit_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + entity_type TEXT NOT NULL, + entity_id INTEGER NOT NULL, + action TEXT NOT NULL, + details TEXT, + performed_at INTEGER NOT NULL + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at INTEGER NOT NULL, + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_reviews_product ON reviews(product_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_reviews_user ON reviews(user_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_inventory_quantity ON inventory(quantity)" + ); + await db17.execute(` + CREATE TABLE IF NOT EXISTS returns ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + reason TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'requested', + requested_at INTEGER NOT NULL, + processed_at INTEGER, + FOREIGN KEY (order_id) REFERENCES orders(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS return_items ( + return_id INTEGER NOT NULL, + order_item_id INTEGER NOT NULL, + quantity INTEGER NOT NULL, + PRIMARY KEY (return_id, order_item_id), + FOREIGN KEY (return_id) REFERENCES returns(id), + FOREIGN KEY (order_item_id) REFERENCES order_items(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS suppliers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + contact_email TEXT, + country TEXT + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS product_suppliers ( + product_id INTEGER NOT NULL, + supplier_id INTEGER NOT NULL, + cost REAL NOT NULL, + lead_time_days INTEGER, + PRIMARY KEY (product_id, supplier_id), + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (supplier_id) REFERENCES suppliers(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS price_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_id INTEGER NOT NULL, + old_price REAL NOT NULL, + new_price REAL NOT NULL, + changed_at INTEGER NOT NULL, + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS user_preferences ( + user_id INTEGER PRIMARY KEY, + theme TEXT NOT NULL DEFAULT 'dark', + language TEXT NOT NULL DEFAULT 'en', + notifications_enabled INTEGER NOT NULL DEFAULT 1, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS cart ( + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + added_at INTEGER NOT NULL, + PRIMARY KEY (user_id, product_id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS saved_searches ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + query TEXT NOT NULL, + filters TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS product_images ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_id INTEGER NOT NULL, + url TEXT NOT NULL, + alt_text TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute(` + CREATE TABLE IF NOT EXISTS discounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + product_id INTEGER NOT NULL, + discount_percent REAL NOT NULL, + starts_at INTEGER NOT NULL, + ends_at INTEGER, + FOREIGN KEY (product_id) REFERENCES products(id) + ) + `); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_order_items_product ON order_items(product_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_shipping_order ON shipping(order_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_returns_order ON returns(order_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_price_history_product ON price_history(product_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_product_images_product ON product_images(product_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_discounts_product ON discounts(product_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id)" + ); + await db17.execute( + "CREATE INDEX IF NOT EXISTS idx_saved_searches_user ON saved_searches(user_id)" + ); + await db17.execute(` + CREATE TABLE IF NOT EXISTS counter ( + id INTEGER PRIMARY KEY CHECK (id = 1), + value INTEGER NOT NULL DEFAULT 0 + ) + `); + await db17.execute( + "INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)" + ); + } + }), + actions: { + increment: async (c, amount = 1) => { + await c.db.execute( + "UPDATE counter SET value = value + ? WHERE id = 1", + amount + ); + const rows = await c.db.execute( + "SELECT value FROM counter WHERE id = 1" + ); + return rows[0].value; + }, + getCount: async (c) => { + const rows = await c.db.execute( + "SELECT value FROM counter WHERE id = 1" + ); + return rows[0].value; + }, + reset: async (c) => { + await c.db.execute("UPDATE counter SET value = 0 WHERE id = 1"); + return 0; + }, + runLoadTest: async (c) => { + const now = Date.now(); + const results = []; + await c.db.execute( + "INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)", + "Load Test User", + `load-${now}@test.com`, + now + ); + results.push("inserted user"); + const users = await c.db.execute( + "SELECT * FROM users WHERE email = ?", + `load-${now}@test.com` + ); + results.push( + `fetched user: ${users[0].name}` + ); + const userId = users[0].id; + await c.db.execute( + "INSERT INTO products (name, price, created_at) VALUES (?, ?, ?)", + "Test Widget", + 29.99, + now + ); + results.push("inserted product"); + const products = await c.db.execute( + "SELECT * FROM products LIMIT 10" + ); + results.push(`fetched ${products.length} products`); + const productId = products[0].id; + await c.db.execute( + "INSERT OR IGNORE INTO categories (name, description) VALUES (?, ?)", + `test-cat-${now}`, + "A test category" + ); + results.push("inserted category"); + const categories = await c.db.execute("SELECT * FROM categories"); + results.push( + `fetched ${categories.length} categories` + ); + await c.db.execute( + "INSERT INTO orders (user_id, total, status, created_at) VALUES (?, ?, ?, ?)", + userId, + 29.99, + "pending", + now + ); + results.push("inserted order"); + const orders = await c.db.execute( + "SELECT * FROM orders WHERE user_id = ?", + userId + ); + results.push( + `fetched ${orders.length} orders for user` + ); + const orderId = orders[0].id; + await c.db.execute( + "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)", + orderId, + productId, + 2, + 29.99 + ); + results.push("inserted order item"); + await c.db.execute( + "INSERT OR REPLACE INTO inventory (product_id, quantity, reserved, last_restocked_at) VALUES (?, ?, ?, ?)", + productId, + 100, + 2, + now + ); + results.push("inserted inventory"); + await c.db.execute( + "INSERT INTO reviews (user_id, product_id, rating, comment, created_at) VALUES (?, ?, ?, ?, ?)", + userId, + productId, + 5, + "Great product!", + now + ); + results.push("inserted review"); + const reviews = await c.db.execute( + "SELECT r.*, u.name as reviewer FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.product_id = ?", + productId + ); + results.push(`fetched ${reviews.length} reviews`); + await c.db.execute( + "INSERT INTO notifications (user_id, type, message, created_at) VALUES (?, ?, ?, ?)", + userId, + "order", + "Your order has been placed", + now + ); + results.push("inserted notification"); + await c.db.execute( + "INSERT INTO audit_log (entity_type, entity_id, action, details, performed_at) VALUES (?, ?, ?, ?, ?)", + "order", + orderId, + "created", + `Order created by user ${userId}`, + now + ); + results.push("inserted audit log"); + const orderStats = await c.db.execute( + "SELECT status, COUNT(*) as count, SUM(total) as total_value FROM orders GROUP BY status" + ); + results.push( + `order stats: ${orderStats.length} statuses` + ); + await c.db.execute( + "INSERT INTO addresses (user_id, street, city, state, zip, country) VALUES (?, ?, ?, ?, ?, ?)", + userId, + "123 Test St", + "Testville", + "CA", + "90210", + "US" + ); + results.push("inserted address"); + const orderDetails = await c.db.execute(` + SELECT o.id, o.status, o.total, u.name as customer, COUNT(oi.id) as item_count + FROM orders o + JOIN users u ON o.user_id = u.id + LEFT JOIN order_items oi ON oi.order_id = o.id + GROUP BY o.id + LIMIT 10 + `); + results.push( + `fetched ${orderDetails.length} order details` + ); + await c.db.execute( + "UPDATE orders SET status = ? WHERE id = ?", + "completed", + orderId + ); + results.push("updated order status"); + const version = await c.db.execute( + "SELECT version FROM schema_version WHERE id = 1" + ); + results.push( + `schema version: ${version[0].version}` + ); + const tableCounts = await c.db.execute(` + SELECT 'users' as tbl, COUNT(*) as cnt FROM users + UNION ALL SELECT 'products', COUNT(*) FROM products + UNION ALL SELECT 'orders', COUNT(*) FROM orders + UNION ALL SELECT 'reviews', COUNT(*) FROM reviews + UNION ALL SELECT 'categories', COUNT(*) FROM categories + `); + results.push( + `table counts: ${tableCounts.length} tables checked` + ); + return { + queriesRun: 20, + results + }; + } } - await batchInsert3( - database, - `INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`, - messageRows, - 20 - ); - const messageToolRefRows = []; - for (let index = 0; index < messageToolRefCount / 2; index++) { - const assistantIndex = 2 + index % assistantSpan * 2; - const sourceIndex = Math.max(1, assistantIndex - 1); - const resultIndex = Math.min(messageCount, assistantIndex + 1); - const toolUseId = toolUseID2(index + 1); - messageToolRefRows.push([ - messageId2(sourceIndex), - messageId2(assistantIndex), - toolUseId, - "tool_use", - 0 - ]); - messageToolRefRows.push([ - messageId2(resultIndex), - messageId2(assistantIndex), - toolUseId, - "tool_result", - 0 - ]); +}); + +// src/actors/workflow/approval.ts +import { actor as actor53, event as event11, queue as queue4 } from "rivetkit"; +import { Loop, workflow } from "rivetkit/workflow"; +var QUEUE_DECISION = "decision"; +var APPROVAL_TIMEOUT_MS = 3e4; +var approval = actor53({ + createState: (c, input) => ({ + id: c.key[0], + title: input?.title ?? "Untitled Request", + description: input?.description ?? "", + status: "pending", + createdAt: Date.now() + }), + queues: { + decision: queue4() + }, + events: { + requestUpdated: event11(), + requestCreated: event11() + }, + actions: { + getRequest: (c) => c.state, + approve: async (c, approver) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: true, approver }); + }, + reject: async (c, approver) => { + if (c.state.status !== "pending") return; + c.state.deciding = true; + c.broadcast("requestUpdated", c.state); + await c.queue.send(QUEUE_DECISION, { approved: false, approver }); + } + }, + run: workflow(async (ctx) => { + await ctx.loop("approval-loop", async (loopCtx) => { + await loopCtx.step("init-request", async (c) => { + c.log.info({ + msg: "waiting for approval decision", + requestId: c.state.id, + title: c.state.title + }); + c.broadcast("requestCreated", c.state); + }); + const [decisionMessage] = await loopCtx.queue.nextBatch( + "wait-decision", + { + names: [QUEUE_DECISION], + timeout: APPROVAL_TIMEOUT_MS + } + ); + const decision = decisionMessage?.body ?? null; + await loopCtx.step("update-status", async (c) => { + c.state.deciding = false; + if (decision === null) { + c.state.status = "timeout"; + c.log.info({ + msg: "request timed out", + requestId: c.state.id + }); + } else if (decision.approved) { + c.state.status = "approved"; + c.state.decidedBy = decision.approver; + c.log.info({ + msg: "request approved", + requestId: c.state.id, + approver: decision.approver + }); + } else { + c.state.status = "rejected"; + c.state.decidedBy = decision.approver; + c.log.info({ + msg: "request rejected", + requestId: c.state.id, + approver: decision.approver + }); + } + c.state.decidedAt = Date.now(); + c.broadcast("requestUpdated", c.state); + }); + return Loop.break(void 0); + }); + }) +}); + +// src/actors/workflow/batch.ts +import { actor as actor54, event as event12 } from "rivetkit"; +import { Loop as Loop2, workflow as workflow2 } from "rivetkit/workflow"; +function fetchBatch(cursor, batchSize, totalItems) { + const start = cursor * batchSize; + const end = Math.min(start + batchSize, totalItems); + const items = []; + for (let i = start; i < end; i++) { + items.push(i); } - await batchInsert3( - database, - `INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`, - messageToolRefRows, - 50 - ); - const toolCallRows = []; - for (let index = 1; index <= toolCallCount; index++) { - const assistantIndex = 2 + (index - 1) % assistantSpan * 2; - toolCallRows.push([ - toolUseID2(index), - `provider-${index}`, - `tool_${index % 21}`, - JSON.stringify({ path: `/tmp/file-${index}` }), - "seed-executor", - messageId2(assistantIndex), - isoAt(index), - null, - "completed", - JSON.stringify({ - ok: true, - run: { status: "done", result: text2(TOOL_CALL_RESULT_BYTES2) } - }), - null, - isoAt(index + 100) + return { + items, + hasMore: end < totalItems + }; +} +var batch = actor54({ + createState: (c, input) => ({ + id: c.key[0], + totalItems: input?.totalItems ?? 50, + batchSize: input?.batchSize ?? 5, + status: "running", + processedTotal: 0, + currentBatch: 0, + batches: [], + startedAt: Date.now() + }), + events: { + batchProcessed: event12(), + stateChanged: event12(), + processingComplete: event12() + }, + actions: { + getJob: (c) => c.state + }, + run: workflow2(async (ctx) => { + await ctx.loop({ + name: "batch-loop", + state: { cursor: 0 }, + run: async (batchCtx, loopState) => { + const batch2 = await batchCtx.step("fetch-batch", async (c) => { + c.log.info({ + msg: "processing batch", + jobId: c.state.id, + cursor: loopState.cursor + }); + await new Promise( + (r) => setTimeout(r, 200 + Math.random() * 300) + ); + return fetchBatch( + loopState.cursor, + c.state.batchSize, + c.state.totalItems + ); + }); + await batchCtx.step("process-batch", async (c) => { + await new Promise( + (r) => setTimeout(r, 300 + Math.random() * 500) + ); + const batchInfo = { + id: loopState.cursor, + count: batch2.items.length, + processedAt: Date.now() + }; + c.state.currentBatch = loopState.cursor; + c.state.processedTotal += batch2.items.length; + c.state.batches.push(batchInfo); + c.broadcast("batchProcessed", batchInfo); + c.broadcast("stateChanged", c.state); + c.log.info({ + msg: "batch processed", + jobId: c.state.id, + cursor: loopState.cursor, + count: batch2.items.length + }); + }); + if (!batch2.hasMore) { + await batchCtx.step("mark-complete", async (c) => { + c.state.status = "completed"; + c.state.completedAt = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("processingComplete", { + totalBatches: loopState.cursor + 1, + totalItems: c.state.processedTotal + }); + }); + return Loop2.break(loopState.cursor + 1); + } + return Loop2.continue({ cursor: loopState.cursor + 1 }); + } + }); + }) +}); + +// src/actors/workflow/dashboard.ts +import { actor as actor55, event as event13, queue as queue5 } from "rivetkit"; +import { Loop as Loop3, workflow as workflow3 } from "rivetkit/workflow"; +var QUEUE_REFRESH = "refresh"; +async function fetchUserStats() { + await new Promise((r) => setTimeout(r, 800 + Math.random() * 1200)); + return { + count: Math.floor(1e3 + Math.random() * 500), + activeToday: Math.floor(100 + Math.random() * 200), + newThisWeek: Math.floor(20 + Math.random() * 80) + }; +} +async function fetchOrderStats() { + await new Promise((r) => setTimeout(r, 600 + Math.random() * 1e3)); + const count = Math.floor(50 + Math.random() * 150); + const revenue = Math.floor(5e3 + Math.random() * 15e3); + return { + count, + revenue, + avgOrderValue: Math.round(revenue / count) + }; +} +async function fetchMetricsStats() { + await new Promise((r) => setTimeout(r, 400 + Math.random() * 800)); + return { + pageViews: Math.floor(1e4 + Math.random() * 5e4), + sessions: Math.floor(2e3 + Math.random() * 8e3), + bounceRate: Math.round(30 + Math.random() * 40) + }; +} +var dashboard = actor55({ + state: { + data: null, + loading: false, + branches: { + users: "pending", + orders: "pending", + metrics: "pending" + }, + lastRefresh: null + }, + queues: { + [QUEUE_REFRESH]: queue5() + }, + events: { + stateChanged: event13(), + refreshComplete: event13() + }, + actions: { + refresh: async (c) => { + if (!c.state.loading) { + c.state.loading = true; + c.state.branches = { + users: "pending", + orders: "pending", + metrics: "pending" + }; + c.broadcast("stateChanged", c.state); + await c.queue.send(QUEUE_REFRESH, {}); + } + }, + getState: (c) => c.state + }, + run: workflow3(async (ctx) => { + await ctx.loop("refresh-loop", async (loopCtx) => { + await loopCtx.queue.next("wait-refresh", { + names: [QUEUE_REFRESH] + }); + ctx.log.info({ msg: "starting dashboard refresh" }); + const results = await loopCtx.join("fetch-all", { + users: { + run: async (branchCtx) => { + await branchCtx.step("mark-running", async (bc) => { + bc.state.branches.users = "running"; + bc.broadcast("stateChanged", bc.state); + }); + const data = await branchCtx.step( + "fetch-users", + async () => { + return await fetchUserStats(); + } + ); + await branchCtx.step("mark-complete", async (bc) => { + bc.state.branches.users = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + return data; + } + }, + orders: { + run: async (branchCtx) => { + await branchCtx.step("mark-running", async (bc) => { + bc.state.branches.orders = "running"; + bc.broadcast("stateChanged", bc.state); + }); + const data = await branchCtx.step( + "fetch-orders", + async () => { + return await fetchOrderStats(); + } + ); + await branchCtx.step("mark-complete", async (bc) => { + bc.state.branches.orders = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + return data; + } + }, + metrics: { + run: async (branchCtx) => { + await branchCtx.step("mark-running", async (bc) => { + bc.state.branches.metrics = "running"; + bc.broadcast("stateChanged", bc.state); + }); + const data = await branchCtx.step( + "fetch-metrics", + async () => { + return await fetchMetricsStats(); + } + ); + await branchCtx.step("mark-complete", async (bc) => { + bc.state.branches.metrics = "completed"; + bc.broadcast("stateChanged", bc.state); + }); + return data; + } + } + }); + await loopCtx.step("save-data", async (c) => { + c.state.data = { + users: results.users, + orders: results.orders, + metrics: results.metrics, + fetchedAt: Date.now() + }; + c.state.loading = false; + c.state.lastRefresh = Date.now(); + c.broadcast("stateChanged", c.state); + c.broadcast("refreshComplete", c.state.data); + }); + ctx.log.info({ msg: "dashboard refresh complete" }); + return Loop3.continue(void 0); + }); + }) +}); + +// src/actors/workflow/history-examples.ts +import { actor as actor56, queue as queue6 } from "rivetkit"; +import { Loop as Loop4, workflow as workflow4 } from "rivetkit/workflow"; +function delay3(ms) { + return new Promise((resolve2) => setTimeout(resolve2, ms)); +} +var workflowHistorySimple = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "pending" + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("start", async (c) => { + c.state.status = "running"; + c.state.lastStep = "start"; + c.state.startedAt = Date.now(); + return { initialized: true }; + }); + await delay3(700); + await ctx.step("process", async (c) => { + c.state.lastStep = "process"; + return { processed: true, items: 5 }; + }); + await delay3(2200); + await ctx.step("validate", async (c) => { + c.state.lastStep = "validate"; + return { valid: true }; + }); + await delay3(600); + await ctx.step("complete", async (c) => { + c.state.lastStep = "complete"; + c.state.status = "completed"; + c.state.completedAt = Date.now(); + c.state.output = { success: true, processedItems: 3 }; + return { success: true }; + }); + }) +}); +var LOOP_ITEMS = ["A", "B", "C"]; +var workflowHistoryLoop = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "running", + processed: 0, + batches: [] + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("init", async (c) => { + c.state.status = "running"; + return { batchSize: LOOP_ITEMS.length }; + }); + await ctx.loop({ + name: "batch-loop", + state: { index: 0 }, + commitInterval: 1, + historyEvery: 1, + historyKeep: LOOP_ITEMS.length, + run: async (loopCtx, loopState) => { + const item = LOOP_ITEMS[loopState.index]; + await loopCtx.step(`process-${loopState.index}`, async (c) => { + c.state.processed += 1; + c.state.batches.push({ index: loopState.index, item }); + return { item, status: "done" }; + }); + if (loopState.index >= LOOP_ITEMS.length - 1) { + return Loop4.break({ processed: LOOP_ITEMS.length }); + } + return Loop4.continue({ index: loopState.index + 1 }); + } + }); + await ctx.step("finalize", async (c) => { + c.state.status = "completed"; + c.state.completedAt = Date.now(); + return { allProcessed: true }; + }); + }) +}); +var workflowHistoryJoin = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "pending" + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("start", async (c) => { + c.state.status = "running"; + return { ready: true }; + }); + const results = await ctx.join("parallel-tasks", { + "fetch-api": { + run: async (branchCtx) => { + await branchCtx.step("task-a", async (branchCtx2) => { + await delay3(120); + return { fetched: true }; + }); + return { data: "api-response" }; + } + }, + "query-db": { + run: async (branchCtx) => { + await branchCtx.step("task-b", async (branchCtx2) => { + await delay3(200); + return { queried: true }; + }); + return { rows: 42 }; + } + }, + "check-cache": { + run: async (branchCtx) => { + await branchCtx.step("task-c", async (branchCtx2) => { + await delay3(60); + return { checked: true }; + }); + return { hit: true }; + } + } + }); + await ctx.step("merge-results", async (c) => { + c.state.status = "completed"; + c.state.result = { + api: results["fetch-api"].data, + rows: results["query-db"].rows, + cacheHit: results["check-cache"].hit + }; + return { merged: true }; + }); + }) +}); +var workflowHistoryRace = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "running" + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("begin", async (c) => { + c.state.status = "running"; + return { started: true }; + }); + const { winner, value } = await ctx.race("race-providers", [ + { + name: "provider-fast", + run: async (branchCtx) => { + await branchCtx.sleep("provider-fast-step", 50); + return { provider: "cdn-edge", latency: 12 }; + } + }, + { + name: "provider-slow", + run: async (branchCtx) => { + await branchCtx.sleep("provider-slow-step", 200); + return { provider: "origin", latency: 120 }; + } + } ]); - } - await batchInsert3( - database, - `INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`, - toolCallRows, - 20 - ); - const executorToolRows = []; - for (let index = 1; index <= executorToolCount; index++) { - const schema = JSON.stringify({ - name: `tool_${index}`, - description: text2(EXECUTOR_TOOL_SCHEMA_BYTES2), - input_schema: { type: "object", properties: {} } + await ctx.step("use-result", async (c) => { + c.state.status = "completed"; + c.state.winner = winner; + c.state.result = value.provider; + return { used: value.provider }; + }); + }) +}); +var QUEUE_ORDER_CREATED = "order:created"; +var QUEUE_ORDER_UPDATED = "order:updated"; +var QUEUE_ORDER_ITEM = "order:item"; +var QUEUE_ORDER_ARTIFACT = "order:artifact"; +var QUEUE_ORDER_READY = "order:ready"; +var QUEUE_ORDER_OPTIONAL = "order:optional"; +var FULL_WORKFLOW_MESSAGE_SEEDS = [ + { name: QUEUE_ORDER_CREATED, payload: { id: "order-1" } }, + { name: QUEUE_ORDER_UPDATED, payload: { id: "order-1", status: "paid" } }, + { name: QUEUE_ORDER_ITEM, payload: { sku: "sku-0", qty: 1 } }, + { name: QUEUE_ORDER_ITEM, payload: { sku: "sku-4", qty: 1 } }, + { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-0" } }, + { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-1" } }, + { name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: "artifact-2" } }, + { name: QUEUE_ORDER_READY, payload: { batch: 3 } }, + { name: QUEUE_ORDER_READY, payload: { batch: 0 } }, + { name: QUEUE_ORDER_READY, payload: { batch: 2 } } +]; +var FULL_WORKFLOW_ITEMS = [ + { id: "item-1", basePrice: 100, tax: 8 }, + { id: "item-2", basePrice: 115, tax: 9 }, + { id: "item-3", basePrice: 130, tax: 10 }, + { id: "item-4", basePrice: 145, tax: 12 } +]; +var workflowHistoryFull = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "pending", + seededMessages: false + }), + queues: { + [QUEUE_ORDER_CREATED]: queue6(), + [QUEUE_ORDER_UPDATED]: queue6(), + [QUEUE_ORDER_ITEM]: queue6(), + [QUEUE_ORDER_ARTIFACT]: queue6(), + [QUEUE_ORDER_READY]: queue6(), + [QUEUE_ORDER_OPTIONAL]: queue6() + }, + actions: { + getState: (c) => c.state, + seedMessages: async (c) => { + if (c.state.seededMessages) return; + for (const seed of FULL_WORKFLOW_MESSAGE_SEEDS) { + await c.queue.send(seed.name, seed.payload); + } + c.state.seededMessages = true; + } + }, + run: workflow4(async (ctx) => { + await ctx.step("bootstrap", async (c) => { + c.state.status = "running"; + c.state.lastStep = "bootstrap"; + c.state.startedAt = Date.now(); + return { + requestId: `req-${c.state.id}`, + startedAt: Date.now() + }; + }); + await ctx.step("validate-input", async (c) => { + c.state.lastStep = "validate-input"; + return true; + }); + await ctx.rollbackCheckpoint("checkpoint-after-validation"); + await ctx.step("load-user-profile", async (c) => { + c.state.lastStep = "load-user-profile"; + return { + id: "user-123", + tier: "standard", + flags: ["email-verified", "promo-eligible"] + }; + }); + await ctx.step("compute-discount", async (c) => { + c.state.lastStep = "compute-discount"; + return { percent: 5, reason: "tier-discount" }; + }); + await ctx.step("ephemeral-cache-check", async (c) => { + c.state.lastStep = "ephemeral-cache-check"; + return { cacheHit: false, tier: "standard" }; + }); + await ctx.rollbackCheckpoint("checkpoint-before-reserve"); + await ctx.loop({ + name: "process-items-loop", + state: { index: 0 }, + commitInterval: 1, + historyEvery: 1, + historyKeep: 2, + run: async (loopCtx, loopState) => { + const item = FULL_WORKFLOW_ITEMS[loopState.index]; + if (!item) { + return Loop4.break({ count: FULL_WORKFLOW_ITEMS.length }); + } + await loopCtx.step( + `fetch-item-${loopState.index}`, + async (loopCtx2) => { + return { itemId: item.id, basePrice: item.basePrice }; + } + ); + await loopCtx.step( + `compute-tax-${loopState.index}`, + async (loopCtx2) => { + return item.tax; + } + ); + await loopCtx.step( + `reserve-inventory-${loopState.index}`, + async (loopCtx2) => ({ + reservationId: `res-${loopState.index}`, + itemId: item.id + }) + ); + if (loopState.index >= FULL_WORKFLOW_ITEMS.length - 1) { + return Loop4.break({ + count: FULL_WORKFLOW_ITEMS.length, + total: 504 + }); + } + return Loop4.continue({ index: loopState.index + 1 }); + } }); - executorToolRows.push(["seed-executor", `tool_${index}`, schema, isoAt(index)]); - } - await batchInsert3( - database, - `INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`, - executorToolRows, - 42 - ); - const threadEventRows = []; - for (let index = 1; index <= threadEventCount; index++) { - threadEventRows.push([ - index, - index % 3 === 0 ? "message_added" : "agent_state_changed", - JSON.stringify({ type: "seed_event", body: text2(THREAD_EVENT_PAYLOAD_BYTES2) }), - isoAt(index) + await ctx.sleep("short-cooldown", 40); + await ctx.sleep("cooldown-sleep", 60); + await ctx.sleep("wait-until-deadline", 45); + await ctx.step("compute-deadlines", async (ctx2) => { + const readyBy = Date.now() + 800; + const readyBatchBy = Date.now() + 1100; + return { readyBy, readyBatchBy }; + }); + await ctx.queue.next("listen-order-created", { + names: [QUEUE_ORDER_CREATED] + }); + await ctx.queue.nextBatch("listen-order-updated-timeout", { + names: [QUEUE_ORDER_UPDATED], + timeout: 250 + }); + await ctx.queue.nextBatch("listen-batch-two", { + names: [QUEUE_ORDER_ITEM], + count: 2 + }); + await ctx.queue.nextBatch("listen-artifacts-timeout", { + names: [QUEUE_ORDER_ARTIFACT], + count: 3, + timeout: 300 + }); + await ctx.queue.nextBatch("listen-optional", { + names: [QUEUE_ORDER_OPTIONAL], + timeout: 200 + }); + await ctx.queue.nextBatch("listen-until", { + names: [QUEUE_ORDER_READY], + timeout: 300 + }); + await ctx.queue.nextBatch("listen-batch-until", { + names: [QUEUE_ORDER_READY], + count: 2, + timeout: 400 + }); + await ctx.join("join-dependencies", { + inventory: { + run: async (branchCtx) => { + const reserved = await branchCtx.step( + "inventory-audit", + async (branchCtx2) => 4 + ); + await branchCtx.sleep("join-inventory-sleep", 35); + return { + reserved, + checked: 4, + notes: ["inventory-ok", "items=4"] + }; + } + }, + pricing: { + run: async (branchCtx) => { + const method = await branchCtx.step( + "pricing-method", + async (branchCtx2) => "promo" + ); + return { + subtotal: 504, + discount: 25, + total: 479, + method + }; + } + }, + shipping: { + run: async (branchCtx) => { + const zone = await branchCtx.step( + "shipping-zone", + async (branchCtx2) => "us-east" + ); + await branchCtx.sleep("join-shipping-sleep", 35); + return { method: "ground", etaDays: 4, zone }; + } + } + }); + await ctx.race("race-fulfillment", [ + { + name: "race-fast", + run: async (branchCtx) => { + await branchCtx.sleep("race-fast-sleep", 70); + return { method: "express", cost: 18, etaDays: 1 }; + } + }, + { + name: "race-slow", + run: async (branchCtx) => { + await branchCtx.sleep("race-slow-sleep", 250); + return { method: "ground", cost: 8, etaDays: 4 }; + } + } ]); + await ctx.removed("legacy-step-placeholder", "step"); + await ctx.step("finalize", async (c) => { + c.state.lastStep = "finalize"; + c.state.status = "completed"; + c.state.completedAt = Date.now(); + return true; + }); + }) +}); +var workflowHistoryInProgress = actor56({ + createState: (c, input) => ({ + id: c.key[0], + status: "running", + processingDurationMs: input?.processingDurationMs ?? 3e4, + progress: 0 + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("init", async (c) => { + c.state.startedAt = Date.now(); + c.state.progress = 10; + return { initialized: true }; + }); + await ctx.step("fetch-data", async (c) => { + c.state.progress = 25; + return { fetched: true, records: 100 }; + }); + await ctx.step("process", async (c) => { + c.state.progress = 42; + await delay3(c.state.processingDurationMs); + c.state.status = "completed"; + c.state.completedAt = Date.now(); + return { processed: true }; + }); + }) +}); +var RETRY_MAX_RETRIES = 20; +var workflowHistoryRetrying = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "running", + attempts: 0, + succeedAfter: 999 + }), + actions: { + getState: (c) => c.state, + allowSuccess: (c, afterAttempt) => { + c.state.succeedAfter = afterAttempt ?? c.state.attempts + 1; + } + }, + run: workflow4(async (ctx) => { + await ctx.step("start", async (c) => { + c.state.status = "running"; + return { ready: true }; + }); + await ctx.step({ + name: "api-call", + maxRetries: RETRY_MAX_RETRIES, + retryBackoffBase: 250, + retryBackoffMax: 1500, + run: async (c) => { + c.state.attempts += 1; + if (c.state.attempts < c.state.succeedAfter) { + const error = new Error("Connection timeout after 5000ms"); + c.state.lastError = error.message; + throw error; + } + c.state.status = "completed"; + c.state.lastError = void 0; + return { success: true }; + } + }); + }) +}); +var FAILED_MAX_RETRIES = 3; +var workflowHistoryFailed = actor56({ + createState: (c) => ({ + id: c.key[0], + status: "running", + attempts: 0 + }), + actions: { + getState: (c) => c.state + }, + run: workflow4(async (ctx) => { + await ctx.step("init", async (c) => { + c.state.status = "running"; + return { initialized: true }; + }); + await ctx.step("validate", async (ctx2) => { + return { valid: true }; + }); + await ctx.step({ + name: "process", + maxRetries: FAILED_MAX_RETRIES, + retryBackoffBase: 200, + retryBackoffMax: 800, + run: async (c) => { + c.state.attempts += 1; + const error = new Error( + "Database connection failed: ECONNREFUSED" + ); + c.state.lastError = error.message; + throw error; + } + }); + }) +}); + +// src/actors/workflow/order.ts +import { actor as actor57, event as event14 } from "rivetkit"; +import { Loop as Loop5, workflow as workflow5 } from "rivetkit/workflow"; +async function simulateWork(name, failChance = 0.1) { + await new Promise( + (resolve2) => setTimeout(resolve2, 500 + Math.random() * 1e3) + ); + if (Math.random() < failChance) { + throw new Error(`${name} failed (simulated)`); } - await batchInsert3( - database, - `INSERT INTO thread_events (seq, event_type, payload, created_at)`, - threadEventRows, - 25 - ); - const messageAddedRows = []; - for (let index = 1; index <= messageCount; index++) { - messageAddedRows.push([messageId2(index), index]); - } - await batchInsert3( - database, - `INSERT INTO message_added_events (message_id, seq)`, - messageAddedRows, - 50 - ); - await database.execute( - `INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`, - JSON.stringify({ cwd: "/workspace", body: text2(3620) }), - isoAt(0) - ); - await database.execute( - `INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`, - JSON.stringify({ maxTokens: 2e4, body: text2(55) }), - isoAt(0) - ); - return { - seeded: true, - messages: messageCount, - toolCalls: toolCallCount, - threadEvents: threadEventCount - }; -} -var MAX_BATCH_BIND_BYTES = 120 * 1024; -function bindParamBytes(value) { - if (value === null || value === void 0) { - return 0; - } - if (typeof value === "string") { - return Buffer.byteLength(value, "utf8"); - } - if (typeof value === "number") { - return 8; - } - if (value instanceof Uint8Array) { - return value.byteLength; +} +var order = actor57({ + createState: (c) => ({ + id: c.key[0], + status: "pending", + step: 0, + createdAt: Date.now() + }), + events: { + orderUpdated: event14() + }, + actions: { + getOrder: (c) => c.state + }, + run: workflow5(async (ctx) => { + await ctx.loop("process-order", async (loopCtx) => { + await loopCtx.step("validate", async (c) => { + c.log.info({ + msg: "processing order", + orderId: c.state.id + }); + c.state.status = "validating"; + c.state.step = 1; + c.broadcast("orderUpdated", c.state); + await simulateWork("validation", 0.05); + }); + await loopCtx.step("charge", async (c) => { + c.state.status = "charging"; + c.state.step = 2; + c.broadcast("orderUpdated", c.state); + await simulateWork("payment", 0.1); + }); + await loopCtx.step("fulfill", async (c) => { + c.state.status = "fulfilling"; + c.state.step = 3; + c.broadcast("orderUpdated", c.state); + await simulateWork("fulfillment", 0.05); + }); + await loopCtx.step("complete", async (c) => { + c.state.status = "completed"; + c.state.step = 4; + c.state.completedAt = Date.now(); + c.broadcast("orderUpdated", c.state); + c.log.info({ + msg: "order completed", + orderId: c.state.id + }); + }); + return Loop5.break(void 0); + }); + }) +}); + +// src/actors/workflow/payment.ts +import { actor as actor58, event as event15 } from "rivetkit"; +import { Loop as Loop6, workflow as workflow6 } from "rivetkit/workflow"; +var payment = actor58({ + createState: (c, input) => ({ + id: c.key[0], + amount: input?.amount ?? 100, + shouldFail: input?.shouldFail ?? false, + status: "pending", + steps: [ + { name: "reserve-inventory", status: "pending" }, + { name: "charge-card", status: "pending" }, + { name: "complete-order", status: "pending" } + ], + startedAt: Date.now() + }), + events: { + transactionStarted: event15(), + transactionUpdated: event15(), + transactionCompleted: event15() + }, + actions: { + getTransaction: (c) => c.state + }, + run: workflow6(async (ctx) => { + await ctx.loop("payment-loop", async (loopCtx) => { + await loopCtx.step("init-payment", async (c) => { + c.log.info({ + msg: "starting payment processing", + txId: c.state.id, + amount: c.state.amount, + shouldFail: c.state.shouldFail + }); + c.broadcast("transactionStarted", c.state); + }); + await loopCtx.rollbackCheckpoint("payment-checkpoint"); + await loopCtx.step({ + name: "reserve-inventory", + run: async (c) => { + c.state.status = "reserving"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + await new Promise( + (r) => setTimeout(r, 500 + Math.random() * 500) + ); + c.log.info({ + msg: "inventory reserved", + txId: c.state.id + }); + return { reserved: true }; + }, + rollback: async (c) => { + c.state.status = "rolling_back"; + const step = c.state.steps.find( + (s) => s.name === "reserve-inventory" + ); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + c.log.info({ + msg: "inventory released", + txId: c.state.id + }); + c.broadcast("transactionUpdated", c.state); + await new Promise((r) => setTimeout(r, 400)); + } + }); + await loopCtx.step({ + name: "charge-card", + run: async (c) => { + c.state.status = "charging"; + const step = c.state.steps.find( + (s) => s.name === "charge-card" + ); + if (step) { + step.status = "completed"; + step.completedAt = Date.now(); + } + c.broadcast("transactionUpdated", c.state); + await new Promise( + (r) => setTimeout(r, 500 + Math.random() * 500) + ); + if (c.state.shouldFail) { + throw new Error("Payment declined (simulated)"); + } + c.log.info({ msg: "card charged", txId: c.state.id }); + return { chargeId: `ch_${c.state.id}` }; + }, + rollback: async (c) => { + c.state.status = "rolling_back"; + const step = c.state.steps.find( + (s) => s.name === "charge-card" + ); + if (step) { + step.status = "rolled_back"; + step.rolledBackAt = Date.now(); + } + c.log.info({ msg: "charge refunded", txId: c.state.id }); + c.broadcast("transactionUpdated", c.state); + await new Promise((r) => setTimeout(r, 400)); + } + }); + await loopCtx.step("complete-order", async (c) => { + c.state.status = "completing"; + const step = c.state.steps.find( + (s) => s.name === "complete-order" + ); + if (step) step.status = "completed"; + c.broadcast("transactionUpdated", c.state); + await new Promise( + (r) => setTimeout(r, 300 + Math.random() * 300) + ); + c.state.status = "completed"; + c.state.completedAt = Date.now(); + c.log.info({ msg: "order completed", txId: c.state.id }); + c.broadcast("transactionCompleted", c.state); + }); + return Loop6.break(void 0); + }); + }) +}); + +// src/actors/workflow/race.ts +import { actor as actor59, event as event16 } from "rivetkit"; +import { Loop as Loop7, workflow as workflow7 } from "rivetkit/workflow"; +var race = actor59({ + createState: (c, input) => ({ + id: c.key[0], + workDurationMs: input?.workDurationMs ?? 2e3, + timeoutMs: input?.timeoutMs ?? 3e3, + status: "running", + startedAt: Date.now() + }), + events: { + raceStarted: event16(), + raceCompleted: event16() + }, + actions: { + getTask: (c) => c.state + }, + run: workflow7(async (ctx) => { + await ctx.loop("race-loop", async (loopCtx) => { + const { workDurationMs, timeoutMs, taskId } = await loopCtx.step( + "start-race", + async (c) => { + c.log.info({ + msg: "starting race", + taskId: c.state.id, + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs + }); + c.broadcast("raceStarted", c.state); + return { + workDurationMs: c.state.workDurationMs, + timeoutMs: c.state.timeoutMs, + taskId: c.state.id + }; + } + ); + const { winner, value } = await loopCtx.race("work-vs-timeout", [ + { + name: "work", + run: async (branchCtx) => { + await branchCtx.sleep("simulate-work", workDurationMs); + return await branchCtx.step( + "complete-work", + async () => { + return `Result for task ${taskId}`; + } + ); + } + }, + { + name: "timeout", + run: async (branchCtx) => { + await branchCtx.sleep("timeout-wait", timeoutMs); + return null; + } + } + ]); + await loopCtx.step("save-result", async (c) => { + c.state.completedAt = Date.now(); + c.state.actualDurationMs = c.state.completedAt - c.state.startedAt; + if (winner === "work") { + c.state.status = "work_won"; + c.state.result = value; + c.log.info({ + msg: "work completed before timeout", + taskId: c.state.id, + durationMs: c.state.actualDurationMs + }); + } else { + c.state.status = "timeout_won"; + c.log.info({ + msg: "timeout won the race", + taskId: c.state.id, + durationMs: c.state.actualDurationMs + }); + } + c.broadcast("raceCompleted", c.state); + }); + return Loop7.break(void 0); + }); + }) +}); + +// src/actors/workflow/timer.ts +import { actor as actor60, event as event17 } from "rivetkit"; +import { Loop as Loop8, workflow as workflow8 } from "rivetkit/workflow"; +var timer = actor60({ + createState: (c, input) => ({ + id: c.key[0], + name: input?.name ?? "Timer", + durationMs: input?.durationMs ?? 1e4, + startedAt: Date.now() + }), + events: { + timerStarted: event17(), + timerCompleted: event17() + }, + actions: { + getTimer: (c) => c.state + }, + run: workflow8(async (ctx) => { + await ctx.loop("timer-loop", async (loopCtx) => { + const durationMs = await loopCtx.step("start-timer", async (c) => { + c.log.info({ + msg: "starting timer", + timerId: c.state.id, + durationMs: c.state.durationMs + }); + c.broadcast("timerStarted", c.state); + return c.state.durationMs; + }); + await loopCtx.sleep("countdown", durationMs); + await loopCtx.step("complete-timer", async (c) => { + c.state.completedAt = Date.now(); + c.broadcast("timerCompleted", c.state); + c.log.info({ + msg: "timer completed", + timerId: c.state.id + }); + }); + return Loop8.break(void 0); + }); + }), + options: { + sleepTimeout: 1e3 } - return Buffer.byteLength(String(value), "utf8"); -} -async function batchInsert3(database, insertPrefix, rows, batchSize = 100) { - if (rows.length === 0) { - return; +}); + +// src/actors/workflow/workflow-fixtures.ts +import { actor as actor61, event as event18, queue as queue7 } from "rivetkit"; +import { Loop as Loop9, workflow as workflow9 } from "rivetkit/workflow"; +var WORKFLOW_GUARD_KV_KEY = "__rivet_actor_workflow_guard_triggered"; +var WORKFLOW_QUEUE_NAME = "workflow-default"; +var WORKFLOW_TIMEOUT_QUEUE_NAME = "workflow-timeout"; +var workflowCounterActor = actor61({ + state: { + runCount: 0, + guardTriggered: false, + history: [] + }, + run: workflow9(async (ctx) => { + let leakedCtx; + await ctx.loop("counter", async (loopCtx) => { + await loopCtx.step("increment", async (c) => { + incrementWorkflowCounter(c); + leakedCtx = c; + }); + if (leakedCtx) { + try { + leakedCtx.state; + } catch { + } + } + await loopCtx.sleep("idle", 25); + return Loop9.continue(void 0); + }); + }), + actions: { + getState: async (c) => { + const guardFlag = await c.kv.get(WORKFLOW_GUARD_KV_KEY); + if (guardFlag === "true") { + c.state.guardTriggered = true; + } + return c.state; + } + }, + options: { + sleepTimeout: 50 } - const columnCount = rows[0]?.length ?? 0; - if (columnCount === 0) { - return; +}); +var workflowQueueActor = actor61({ + state: { + received: [] + }, + queues: { + [WORKFLOW_QUEUE_NAME]: queue7() + }, + run: workflow9(async (ctx) => { + await ctx.loop("queue", async (loopCtx) => { + const message = await loopCtx.queue.next("queue-wait", { + names: [WORKFLOW_QUEUE_NAME], + completable: true + }); + if (!message.complete) { + return Loop9.continue(void 0); + } + const complete = message.complete; + await loopCtx.step("store-message", async (c) => { + await storeWorkflowQueueMessage(c, message.body, complete); + }); + return Loop9.continue(void 0); + }); + }), + actions: { + getMessages: (c) => c.state.received } - const rowPlaceholder = `(${"?,".repeat(columnCount).slice(0, -1)})`; - const rowBytes = rows.map( - (row) => row.reduce((sum, value) => sum + bindParamBytes(value), 0) - ); - let index = 0; - while (index < rows.length) { - const chunk = []; - let chunkBytes = 0; - while (index < rows.length && chunk.length < batchSize && // Always take at least one row so an oversized single row still runs - // (and surfaces the engine error) rather than looping forever. - (chunk.length === 0 || chunkBytes + rowBytes[index] <= MAX_BATCH_BIND_BYTES)) { - chunk.push(rows[index]); - chunkBytes += rowBytes[index]; - index++; - } - const values = chunk.map(() => rowPlaceholder).join(","); - const bindings = chunk.flat(); - await database.execute(`${insertPrefix} VALUES ${values}`, ...bindings); +}); +var workflowSleepActor = actor61({ + state: { + ticks: 0 + }, + run: workflow9(async (ctx) => { + await ctx.loop("sleep", async (loopCtx) => { + await loopCtx.step("tick", async (c) => { + incrementWorkflowSleepTick(c); + }); + await loopCtx.sleep("delay", 40); + return Loop9.continue(void 0); + }); + }), + actions: { + getState: (c) => c.state + }, + options: { + sleepTimeout: 50 } -} -function messageId2(index) { - return `M-${String(index).padStart(22, "0")}`; -} -function toolUseID2(index) { - return `toolu_${String(index).padStart(22, "0")}`; -} -var registry = setup({ - use: { slowReconnectActor }, - maxIncomingMessageSize: 5 * 1024 * 1024, - maxOutgoingMessageSize: 5 * 1024 * 1024 }); -if (import.meta.main) { - registry.start(); -} - -// src/actors/ai/ai-agent.ts -import { openai } from "@ai-sdk/openai"; -import { generateText, tool } from "ai"; -import { actor as actor60, event as event20 } from "rivetkit"; -import { z } from "zod"; - -// src/actors/ai/my-tools.ts -async function getWeather(location) { - return { - location, - temperature: Math.floor(Math.random() * 30) + 10, - condition: ["sunny", "cloudy", "rainy", "snowy"][Math.floor(Math.random() * 4)], - humidity: Math.floor(Math.random() * 50) + 30 - }; -} - -// src/actors/ai/ai-agent.ts -var aiAgent = actor60({ - // Persistent state that survives restarts: https://rivet.dev/docs/actors/state +var workflowQueueTimeoutActor = actor61({ state: { - messages: [] + processed: 0, + ticks: 0, + lastTickAt: null, + lastJob: null, + timeoutMs: 2e3 }, events: { - messageReceived: event20() + tick: event18(), + jobProcessed: event18() }, - actions: { - // Callable functions from clients: https://rivet.dev/docs/actors/actions - getMessages: (c) => c.state.messages, - sendMessage: async (c, userMessage) => { - const userMsg = { - role: "user", - content: userMessage, - timestamp: Date.now() - }; - c.state.messages.push(userMsg); - const { text: text2 } = await generateText({ - model: openai("gpt-4o-mini"), - prompt: userMessage, - messages: c.state.messages, - tools: { - weather: tool({ - description: "Get the weather in a location", - parameters: z.object({ - location: z.string().describe( - "The location to get the weather for" - ) - }), - execute: async ({ location }) => { - return await getWeather(location); - } - }) + queues: { + [WORKFLOW_TIMEOUT_QUEUE_NAME]: queue7() + }, + run: workflow9(async (ctx) => { + await ctx.loop("queue-timeout-loop", async (loopCtx) => { + const timeoutMs = await loopCtx.step( + "read-timeout", + async (c) => readWorkflowTimeoutMs(c) + ); + const [message] = await loopCtx.queue.nextBatch( + "wait-job-or-timeout", + { + names: [WORKFLOW_TIMEOUT_QUEUE_NAME], + timeout: timeoutMs } + ); + if (!message) { + await loopCtx.step("tick", async (c) => { + recordWorkflowTimeoutTick(c); + }); + return Loop9.continue(void 0); + } + await loopCtx.step("process-job", async (c) => { + processWorkflowTimeoutJob(c, message.body); }); - const assistantMsg = { - role: "assistant", - content: text2, - timestamp: Date.now() - }; - c.state.messages.push(assistantMsg); - c.broadcast("messageReceived", assistantMsg); - return assistantMsg; - } + return Loop9.continue(void 0); + }); + }), + actions: { + enqueueJob: async (c, payload2) => { + const job = { id: crypto.randomUUID(), payload: payload2 }; + await c.queue.send(WORKFLOW_TIMEOUT_QUEUE_NAME, job); + return job; + }, + setTimeoutMs: (c, timeoutMs) => { + c.state.timeoutMs = Math.max(100, Math.floor(timeoutMs)); + return c.state.timeoutMs; + }, + getState: (c) => c.state } }); +function incrementWorkflowCounter(ctx) { + ctx.state.runCount += 1; + ctx.state.history.push(ctx.state.runCount); +} +async function storeWorkflowQueueMessage(ctx, body, complete) { + ctx.state.received.push(body); + await complete({ echo: body }); +} +function incrementWorkflowSleepTick(ctx) { + ctx.state.ticks += 1; +} +function readWorkflowTimeoutMs(ctx) { + return ctx.state.timeoutMs; +} +function recordWorkflowTimeoutTick(ctx) { + const at = Date.now(); + ctx.state.ticks += 1; + ctx.state.lastTickAt = at; + ctx.broadcast("tick", { + ticks: ctx.state.ticks, + at + }); +} +function processWorkflowTimeoutJob(ctx, job) { + ctx.state.processed += 1; + ctx.state.lastJob = job; + ctx.broadcast("jobProcessed", { + processed: ctx.state.processed, + job + }); +} + +// src/mode.ts +function resolveMode() { + const explicit = process.env.RIVET_KITCHEN_SINK_MODE; + if (explicit === "serverless" || explicit === "serverful" || explicit === "serverless-local") { + return explicit; + } + if (explicit !== void 0 && explicit !== "") { + throw new Error( + `RIVET_KITCHEN_SINK_MODE must be one of "serverless", "serverful", or "serverless-local" (got "${explicit}")` + ); + } + if (process.env.RIVET_RUN_ENGINE === "1") return "serverless-local"; + if (process.env.RIVET_SERVERLESS_URL !== void 0) + return "serverless-local"; + if (process.env.KITCHEN_SINK_SERVERLESS_URL !== void 0) { + return "serverless-local"; + } + return "serverless"; +} // src/index.ts function numberFromEnv2(name, fallback) { @@ -12926,6 +13663,7 @@ var registry2 = setup2({ sqliteRealworldBench, rawSqliteFuzzer, sqliteMemoryPressure, + growDb, mockAgenticLoop, sleepCloseFuzz, loadTestAgent, @@ -12938,14 +13676,13 @@ var registry2 = setup2({ }); // src/server.ts -import { serve } from "@hono/node-server"; -import { Hono as Hono3 } from "hono"; -import * as v8 from "v8"; var app = new Hono3(); var port = Number.parseInt(process.env.PORT ?? "3000", 10); var mode = resolveMode(); process.on("exit", (code) => { - console.log(JSON.stringify({ kind: "process_exit", code, pid: process.pid })); + console.log( + JSON.stringify({ kind: "process_exit", code, pid: process.pid }) + ); }); if (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS === "1") { for (const signal of ["SIGINT", "SIGTERM"]) { @@ -12964,7 +13701,9 @@ if (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS === "1") { } } process.on("beforeExit", (code) => { - console.log(JSON.stringify({ kind: "process_before_exit", code, pid: process.pid })); + console.log( + JSON.stringify({ kind: "process_before_exit", code, pid: process.pid }) + ); }); process.on("uncaughtException", (error) => { console.error( @@ -13030,7 +13769,7 @@ app.get("/debug/memory", async (c) => { const forceGc = c.req.query("gc") === "1"; return c.json(await memoryBreakdown(forceGc)); }); -app.get("/health", () => registry2.routes.health()); +app.get("/health", (c) => c.json({ ok: true })); app.get("/metadata", () => registry2.routes.metadata()); app.get("/metrics", (c) => registry2.routes.prometheusMetrics(c.req.raw)); app.post("/debug/heap-snapshot", (c) => { @@ -13044,8 +13783,8 @@ app.post("/debug/heap-snapshot", (c) => { const writtenPath = v8.writeHeapSnapshot(path); return c.json({ path: writtenPath }); }); -app.use("*", async (c, next) => { - const startedAt = Date.now(); +app.use("*", async (_c, next) => { + const _startedAt = Date.now(); await next(); }); if (mode === "serverful") { @@ -13054,6 +13793,18 @@ if (mode === "serverful") { app.all("/api/rivet/*", (c) => registry2.handler(c.req.raw)); app.all("/api/rivet", (c) => registry2.handler(c.req.raw)); } +var distDir = "dist"; +var indexPath = resolve(process.cwd(), distDir, "index.html"); +if (existsSync(indexPath)) { + app.use("/*", serveStatic({ root: distDir })); + const indexHtml = readFileSync(indexPath, "utf8"); + app.get("/*", (c) => { + const path = new URL(c.req.url).pathname; + const last = path.slice(path.lastIndexOf("/") + 1); + if (last.includes(".")) return c.notFound(); + return c.html(indexHtml); + }); +} var server = serve({ fetch: app.fetch, port }, () => { if (mode === "serverful") { console.log( diff --git a/examples/kitchen-sink/dist-server/server.mjs.map b/examples/kitchen-sink/dist-server/server.mjs.map index 5eaf6d86f0..90ec9250e1 100644 --- a/examples/kitchen-sink/dist-server/server.mjs.map +++ b/examples/kitchen-sink/dist-server/server.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/mode.ts","../src/actors/counter/counter.ts","../src/actors/counter/counter-conn.ts","../src/actors/counter/conn-params.ts","../src/actors/counter/lifecycle.ts","../src/actors/counter/ping-pong-counter.ts","../src/actors/actions/action-inputs.ts","../src/actors/actions/action-types.ts","../src/actors/actions/action-timeout.ts","../src/actors/actions/error-handling.ts","../src/actors/state/actor-onstatechange.ts","../src/actors/state/metadata.ts","../src/actors/state/vars.ts","../src/actors/state/kv.ts","../src/actors/state/large-payloads.ts","../src/actors/state/sqlite-raw.ts","../src/actors/state/sqlite-drizzle/mod.ts","../src/actors/state/sqlite-drizzle/schema.ts","../src/actors/state/sqlite-drizzle/drizzle/meta/_journal.json","../src/actors/state/sqlite-drizzle/drizzle/0000_left_wrecking_crew.sql","../src/actors/state/sqlite-drizzle/drizzle/migrations.js","../src/actors/state/parallelism-test.ts","../src/actors/connections/conn-state.ts","../src/actors/connections/reject-connection.ts","../src/actors/connections/request-access.ts","../src/actors/http/raw-http.ts","../src/actors/http/raw-http-request-properties.ts","../src/actors/http/raw-websocket.ts","../src/actors/http/raw-fetch-counter.ts","../src/actors/http/raw-websocket-chat-room.ts","../src/actors/http/raw-websocket-serverless-smoke.ts","../src/actors/http/tunnel-stress.ts","../src/actors/lifecycle/run.ts","../src/actors/lifecycle/sleep.ts","../src/actors/lifecycle/scheduled.ts","../src/actors/lifecycle/destroy.ts","../src/actors/lifecycle/hibernation.ts","../src/actors/queue/worker.ts","../src/actors/queue/worker-timeout.ts","../src/actors/workflow/workflow-fixtures.ts","../src/actors/workflow/timer.ts","../src/actors/workflow/_helpers.ts","../src/actors/workflow/order.ts","../src/actors/workflow/batch.ts","../src/actors/workflow/approval.ts","../src/actors/workflow/dashboard.ts","../src/actors/workflow/race.ts","../src/actors/workflow/payment.ts","../src/actors/workflow/history-examples.ts","../src/actors/inter-actor/cross-actor-actions.ts","../src/actors/testing/inline-client.ts","../src/actors/testing/test-counter.ts","../src/actors/testing/test-counter-sqlite.ts","../src/actors/testing/test-sqlite-load.ts","../src/actors/testing/test-sqlite-bench.ts","../src/actors/testing/sqlite-cold-start-bench.ts","../src/actors/testing/sqlite-realworld-bench.ts","../src/actors/testing/raw-sqlite-fuzzer.ts","../src/actors/testing/sqlite-memory-pressure.ts","../src/actors/testing/mock-agentic-loop.ts","../src/actors/testing/sleep-close-fuzz.ts","../src/actors/testing/load-test-agent.ts","../src/actors/testing/load-test-agent-2.ts","../src/actors/testing/sigterm-sleep-probe.ts","../src/actors/testing/slow-reconnect-actor.ts","../src/actors/ai/ai-agent.ts","../src/actors/ai/my-tools.ts","../src/server.ts"],"sourcesContent":["import { setup } from \"rivetkit\";\nimport { resolveMode } from \"./mode.ts\";\n// Counter\nimport { counter } from \"./actors/counter/counter.ts\";\nimport { counterConn } from \"./actors/counter/counter-conn.ts\";\nimport { counterWithParams } from \"./actors/counter/conn-params.ts\";\nimport { counterWithLifecycle } from \"./actors/counter/lifecycle.ts\";\nimport { pingPongCounter } from \"./actors/counter/ping-pong-counter.ts\";\n// Actions\nimport { inputActor } from \"./actors/actions/action-inputs.ts\";\nimport {\n\tsyncActionActor,\n\tasyncActionActor,\n\tpromiseActor,\n} from \"./actors/actions/action-types.ts\";\nimport {\n\tshortTimeoutActor,\n\tlongTimeoutActor,\n\tdefaultTimeoutActor,\n\tsyncTimeoutActor,\n} from \"./actors/actions/action-timeout.ts\";\nimport {\n\terrorHandlingActor,\n\tcustomTimeoutActor,\n} from \"./actors/actions/error-handling.ts\";\n// State\nimport { onStateChangeActor } from \"./actors/state/actor-onstatechange.ts\";\nimport { metadataActor } from \"./actors/state/metadata.ts\";\nimport {\n\tstaticVarActor,\n\tnestedVarActor,\n\tdynamicVarActor,\n\tuniqueVarActor,\n\tdriverCtxActor,\n} from \"./actors/state/vars.ts\";\nimport { kvActor } from \"./actors/state/kv.ts\";\nimport {\n\tlargePayloadActor,\n\tlargePayloadConnActor,\n} from \"./actors/state/large-payloads.ts\";\nimport { sqliteRawActor } from \"./actors/state/sqlite-raw.ts\";\nimport { sqliteDrizzleActor } from \"./actors/state/sqlite-drizzle/mod.ts\";\nimport { parallelismTest } from \"./actors/state/parallelism-test.ts\";\n// Connections\nimport { connStateActor } from \"./actors/connections/conn-state.ts\";\nimport { rejectConnectionActor } from \"./actors/connections/reject-connection.ts\";\nimport { requestAccessActor } from \"./actors/connections/request-access.ts\";\n// HTTP\nimport {\n\trawHttpActor,\n\trawHttpNoHandlerActor,\n\trawHttpVoidReturnActor,\n\trawHttpHonoActor,\n} from \"./actors/http/raw-http.ts\";\nimport { rawHttpRequestPropertiesActor } from \"./actors/http/raw-http-request-properties.ts\";\nimport {\n\trawWebSocketActor,\n\trawWebSocketBinaryActor,\n} from \"./actors/http/raw-websocket.ts\";\nimport { rawFetchCounter } from \"./actors/http/raw-fetch-counter.ts\";\nimport { rawWebSocketChatRoom } from \"./actors/http/raw-websocket-chat-room.ts\";\nimport { rawWebSocketServerlessSmoke } from \"./actors/http/raw-websocket-serverless-smoke.ts\";\nimport { tunnelStress } from \"./actors/http/tunnel-stress.ts\";\n// Lifecycle\nimport {\n\trunWithTicks,\n\trunWithQueueConsumer,\n\trunWithEarlyExit,\n\trunWithError,\n\trunWithoutHandler,\n} from \"./actors/lifecycle/run.ts\";\nimport {\n\tsleep,\n\tsleepWithLongRpc,\n\tsleepWithNoSleepOption,\n\tsleepWithRawHttp,\n\tsleepWithRawWebSocket,\n} from \"./actors/lifecycle/sleep.ts\";\nimport { scheduled } from \"./actors/lifecycle/scheduled.ts\";\nimport {\n\tdestroyActor,\n\tdestroyObserver,\n} from \"./actors/lifecycle/destroy.ts\";\nimport { hibernationActor } from \"./actors/lifecycle/hibernation.ts\";\n// Queues\nimport { worker } from \"./actors/queue/worker.ts\";\nimport { workerTimeout } from \"./actors/queue/worker-timeout.ts\";\n// Workflows\nimport {\n\tworkflowCounterActor,\n\tworkflowQueueActor,\n\tworkflowSleepActor,\n\tworkflowQueueTimeoutActor,\n} from \"./actors/workflow/workflow-fixtures.ts\";\nimport { timer } from \"./actors/workflow/timer.ts\";\nimport { order } from \"./actors/workflow/order.ts\";\nimport { batch } from \"./actors/workflow/batch.ts\";\nimport { approval } from \"./actors/workflow/approval.ts\";\nimport { dashboard } from \"./actors/workflow/dashboard.ts\";\nimport { race } from \"./actors/workflow/race.ts\";\nimport { payment } from \"./actors/workflow/payment.ts\";\nimport {\n\tworkflowHistorySimple,\n\tworkflowHistoryLoop,\n\tworkflowHistoryJoin,\n\tworkflowHistoryRace,\n\tworkflowHistoryFull,\n\tworkflowHistoryInProgress,\n\tworkflowHistoryRetrying,\n\tworkflowHistoryFailed,\n} from \"./actors/workflow/history-examples.ts\";\n// Inter-actor\nimport {\n\tinventory,\n\tcheckout,\n} from \"./actors/inter-actor/cross-actor-actions.ts\";\n// Testing\nimport { inlineClientActor } from \"./actors/testing/inline-client.ts\";\nimport { testCounter } from \"./actors/testing/test-counter.ts\";\nimport { testCounterSqlite } from \"./actors/testing/test-counter-sqlite.ts\";\nimport { testSqliteLoad } from \"./actors/testing/test-sqlite-load.ts\";\nimport { testSqliteBench } from \"./actors/testing/test-sqlite-bench.ts\";\nimport { sqliteColdStartBench } from \"./actors/testing/sqlite-cold-start-bench.ts\";\nimport { sqliteRealworldBench } from \"./actors/testing/sqlite-realworld-bench.ts\";\nimport { rawSqliteFuzzer } from \"./actors/testing/raw-sqlite-fuzzer.ts\";\nimport { sqliteMemoryPressure } from \"./actors/testing/sqlite-memory-pressure.ts\";\nimport { mockAgenticLoop } from \"./actors/testing/mock-agentic-loop.ts\";\nimport { sleepCloseFuzz } from \"./actors/testing/sleep-close-fuzz.ts\";\nimport { loadTestAgent } from \"./actors/testing/load-test-agent.ts\";\nimport { loadTestAgent2 } from \"./actors/testing/load-test-agent-2.ts\";\nimport { sigtermSleepProbe } from \"./actors/testing/sigterm-sleep-probe.ts\";\nimport { slowReconnectActor } from \"./actors/testing/slow-reconnect-actor.ts\";\n// AI\nimport { aiAgent } from \"./actors/ai/ai-agent.ts\";\n\nfunction numberFromEnv(name: string, fallback: number): number {\n\tconst value = process.env[name];\n\tif (value === undefined || value === \"\") return fallback;\n\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed)) {\n\t\tthrow new Error(`${name} must be a finite number`);\n\t}\n\n\treturn parsed;\n}\n\nfunction serverlessPoolConfig() {\n\t// Only the local serverless mode self-registers its pool with the engine.\n\t// In the deployed `serverless` mode the pool is configured externally on\n\t// the engine cluster, and the `serverful` mode uses a long-lived runner\n\t// connection rather than a serverless pool.\n\tif (resolveMode() !== \"serverless-local\") return undefined;\n\n\tconst url =\n\t\tprocess.env.RIVET_SERVERLESS_URL ??\n\t\tprocess.env.KITCHEN_SINK_SERVERLESS_URL ??\n\t\t\"http://127.0.0.1:3000/api/rivet\";\n\n\treturn {\n\t\tname: process.env.RIVET_POOL,\n\t\turl,\n\t\trequestLifespan: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_REQUEST_LIFESPAN\",\n\t\t\t15 * 60,\n\t\t),\n\t\tdrainGracePeriod: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_DRAIN_GRACE_PERIOD\",\n\t\t\t15 * 60,\n\t\t),\n\t\tmetadataPollInterval: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_METADATA_POLL_INTERVAL_MS\",\n\t\t\t1000,\n\t\t),\n\t\tmetadata: {\n\t\t\tsource: \"kitchen-sink\",\n\t\t\tsmoke: \"raw-websocket-serverless\",\n\t\t},\n\t};\n}\n\nexport const registry = setup({\n\tsqlite:\n\t\tprocess.env.RIVET_KITCHEN_SINK_REMOTE_SQLITE === \"1\"\n\t\t\t? { backend: \"remote\" }\n\t\t\t: undefined,\n\tconfigurePool: serverlessPoolConfig(),\n\tserverless: {\n\t\tpublicToken:\n\t\t\tprocess.env.RIVET_PUBLIC_TOKEN ?? process.env.RIVET_TOKEN ?? \"dev\",\n\t\tmaxStartPayloadBytes: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_MAX_START_PAYLOAD_BYTES\",\n\t\t\t16 * 1024 * 1024,\n\t\t),\n\t},\n\tuse: {\n\t\t// Overview + state basics\n\t\tcounter,\n\t\tcounterConn,\n\t\tcounterWithParams,\n\t\tcounterWithLifecycle,\n\t\tpingPongCounter,\n\t\t// Core API\n\t\tinputActor,\n\t\tsyncActionActor,\n\t\tasyncActionActor,\n\t\tpromiseActor,\n\t\tshortTimeoutActor,\n\t\tlongTimeoutActor,\n\t\tdefaultTimeoutActor,\n\t\tsyncTimeoutActor,\n\t\tcustomTimeoutActor,\n\t\terrorHandlingActor,\n\t\t// State and storage\n\t\tonStateChangeActor,\n\t\tmetadataActor,\n\t\tstaticVarActor,\n\t\tnestedVarActor,\n\t\tdynamicVarActor,\n\t\tuniqueVarActor,\n\t\tdriverCtxActor,\n\t\tkvActor,\n\t\tlargePayloadActor,\n\t\tlargePayloadConnActor,\n\t\tsqliteRawActor,\n\t\tsqliteDrizzleActor,\n\t\tparallelismTest,\n\t\t// Realtime and connections\n\t\tconnStateActor,\n\t\trejectConnectionActor,\n\t\trequestAccessActor,\n\t\t// HTTP and WebSocket\n\t\trawHttpActor,\n\t\trawHttpNoHandlerActor,\n\t\trawHttpVoidReturnActor,\n\t\trawHttpHonoActor,\n\t\trawHttpRequestPropertiesActor,\n\t\trawWebSocketActor,\n\t\trawWebSocketBinaryActor,\n\t\trawFetchCounter,\n\t\trawWebSocketChatRoom,\n\t\trawWebSocketServerlessSmoke,\n\t\ttunnelStress,\n\t\t// Lifecycle and scheduling\n\t\trunWithTicks,\n\t\trunWithQueueConsumer,\n\t\trunWithEarlyExit,\n\t\trunWithError,\n\t\trunWithoutHandler,\n\t\tsleep,\n\t\tsleepWithLongRpc,\n\t\tsleepWithNoSleepOption,\n\t\tsleepWithRawHttp,\n\t\tsleepWithRawWebSocket,\n\t\tscheduled,\n\t\tdestroyActor,\n\t\tdestroyObserver,\n\t\thibernationActor,\n\t\t// Queues\n\t\tworker,\n\t\tworkerTimeout,\n\t\t// Workflows\n\t\ttimer,\n\t\torder,\n\t\tbatch,\n\t\tapproval,\n\t\tdashboard,\n\t\trace,\n\t\tpayment,\n\t\tworkflowHistorySimple,\n\t\tworkflowHistoryLoop,\n\t\tworkflowHistoryJoin,\n\t\tworkflowHistoryRace,\n\t\tworkflowHistoryFull,\n\t\tworkflowHistoryInProgress,\n\t\tworkflowHistoryRetrying,\n\t\tworkflowHistoryFailed,\n\t\tworkflowCounterActor,\n\t\tworkflowQueueActor,\n\t\tworkflowSleepActor,\n\t\tworkflowQueueTimeoutActor,\n\t\t// Inter-actor\n\t\tinventory,\n\t\tcheckout,\n\t\t// Testing fixtures\n\t\tinlineClientActor,\n\t\ttestCounter,\n\t\ttestCounterSqlite,\n\t\ttestSqliteLoad,\n\t\ttestSqliteBench,\n\t\tsqliteColdStartBench,\n\t\tsqliteRealworldBench,\n\t\trawSqliteFuzzer,\n\t\tsqliteMemoryPressure,\n\t\tmockAgenticLoop,\n\t\tsleepCloseFuzz,\n\t\tloadTestAgent,\n\t\tloadTestAgent2,\n\t\tsigtermSleepProbe,\n\t\tslowReconnectActor,\n\t\t// AI\n\t\taiAgent,\n\t},\n});\n","export type KitchenSinkMode = \"serverless\" | \"serverful\" | \"serverless-local\";\n\nexport function resolveMode(): KitchenSinkMode {\n\tconst explicit = process.env.RIVET_KITCHEN_SINK_MODE;\n\tif (\n\t\texplicit === \"serverless\" ||\n\t\texplicit === \"serverful\" ||\n\t\texplicit === \"serverless-local\"\n\t) {\n\t\treturn explicit;\n\t}\n\tif (explicit !== undefined && explicit !== \"\") {\n\t\tthrow new Error(\n\t\t\t`RIVET_KITCHEN_SINK_MODE must be one of \"serverless\", \"serverful\", or \"serverless-local\" (got \"${explicit}\")`,\n\t\t);\n\t}\n\n\tif (process.env.RIVET_RUN_ENGINE === \"1\") return \"serverless-local\";\n\tif (process.env.RIVET_SERVERLESS_URL !== undefined) return \"serverless-local\";\n\tif (process.env.KITCHEN_SINK_SERVERLESS_URL !== undefined) {\n\t\treturn \"serverless-local\";\n\t}\n\n\treturn \"serverless\";\n}\n","import { actor, event, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\n\nexport const counter = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: { count: 0 },\n\tevents: {\n\t\tnewCount: event(),\n\t},\n\tonWebSocket(_c, websocket: UniversalWebSocket) {\n\t\t// Plain echo for the rtt counter-latency harness. Any message in →\n\t\t// the same payload back out. No state mutation, no awaits — keeps the\n\t\t// echo path as close to raw WS RTT as possible.\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tif (websocket.readyState !== 1) return;\n\t\t\twebsocket.send(event.data as string | ArrayBuffer);\n\t\t});\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\tc.broadcast(\"newCount\", c.state.count);\n\t\t\treturn c.state.count;\n\t\t},\n\t\tsetCount: (c, x: number) => {\n\t\t\tc.state.count = x;\n\t\t\tc.broadcast(\"newCount\", x);\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.state.count;\n\t\t},\n\t\tnoop: (_c) => {\n\t\t\treturn { ok: true };\n\t\t},\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const counterConn = actor({\n\tstate: {\n\t\tconnectionCount: 0,\n\t},\n\tevents: {\n\t\tnewCount: event(),\n\t},\n\tconnState: { count: 0 },\n\tonConnect: (c, conn) => {\n\t\tc.state.connectionCount += 1;\n\t},\n\tonDisconnect: (c, conn) => {\n\t\t// Note: We can't determine if disconnect was graceful from here\n\t\t// For testing purposes, we'll decrement on all disconnects\n\t\t// In real scenarios, you'd use connection tracking with timeouts\n\t\tc.state.connectionCount -= 1;\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.conn.state.count += x;\n\t\t\tc.broadcast(\"newCount\", c.conn.state.count);\n\t\t},\n\t\tsetCount: (c, x: number) => {\n\t\t\tc.conn.state.count = x;\n\t\t\tc.broadcast(\"newCount\", x);\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\tgetConnectionCount: (c) => {\n\t\t\treturn c.state.connectionCount;\n\t\t},\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const counterWithParams = actor({\n\tstate: { count: 0, initializers: [] as string[] },\n\tevents: {\n\t\tnewCount: event<{ count: number; by: string }>(),\n\t},\n\tcreateConnState: (c, params: { name?: string }) => {\n\t\treturn {\n\t\t\tname: params.name || \"anonymous\",\n\t\t};\n\t},\n\tonConnect: (c, conn) => {\n\t\t// Record connection name\n\t\tc.state.initializers.push(conn.state.name);\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\tc.broadcast(\"newCount\", {\n\t\t\t\tcount: c.state.count,\n\t\t\t\tby: c.conn.state.name,\n\t\t\t});\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetInitializers: (c) => {\n\t\t\treturn c.state.initializers;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\ntype ConnParams = { trackLifecycle?: boolean } | undefined;\n\nexport const counterWithLifecycle = actor({\n\tstate: {\n\t\tcount: 0,\n\t\tevents: [] as string[],\n\t},\n\tcreateConnState: (c, params: ConnParams) => ({\n\t\tjoinTime: Date.now(),\n\t}),\n\tonWake: (c) => {\n\t\tc.state.events.push(\"onWake\");\n\t},\n\tonSleep: async (c) => {\n\t\tc.state.events.push(\"onSleep:start\");\n\t\tawait new Promise((resolve) => setTimeout(resolve, 1000));\n\t\tc.state.events.push(\"onSleep:end\");\n\t},\n\tonBeforeConnect: (c, params: ConnParams) => {\n\t\tif (params?.trackLifecycle) c.state.events.push(\"onBeforeConnect\");\n\t},\n\tonConnect: (c, conn) => {\n\t\tif (conn.params?.trackLifecycle) c.state.events.push(\"onConnect\");\n\t},\n\tonDisconnect: (c, conn) => {\n\t\tif (conn.params?.trackLifecycle) c.state.events.push(\"onDisconnect\");\n\t},\n\tactions: {\n\t\tgetEvents: (c) => {\n\t\t\treturn c.state.events;\n\t\t},\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\treturn c.state.count;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const pingPongCounter = actor({\n\tstate: {\n\t\tpingCount: 0,\n\t},\n\tonWebSocket(ctx, websocket) {\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data !== \"string\") return;\n\n\t\t\tlet parsed: any;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(data);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (parsed?.type === \"ping\") {\n\t\t\t\tctx.state.pingCount = ctx.state.pingCount + 1;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\tpingCount: ctx.state.pingCount,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n\tactions: {\n\t\tgetPingCount(c) {\n\t\t\treturn c.state.pingCount;\n\t\t},\n\t\tresetPingCount(c) {\n\t\t\tc.state.pingCount = 0;\n\t\t\treturn c.state.pingCount;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport interface State {\n\tinitialInput?: unknown;\n\tonCreateInput?: unknown;\n}\n\n// Test actor that can capture input during creation\nexport const inputActor = actor({\n\tcreateState: (c, input): State => {\n\t\treturn {\n\t\t\tinitialInput: input,\n\t\t\tonCreateInput: undefined,\n\t\t};\n\t},\n\n\tonCreate: (c, input) => {\n\t\tc.state.onCreateInput = input;\n\t},\n\n\tactions: {\n\t\tgetInputs: (c) => {\n\t\t\treturn {\n\t\t\t\tinitialInput: c.state.initialInput,\n\t\t\t\tonCreateInput: c.state.onCreateInput,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\n// Actor with synchronous actions\nexport const syncActionActor = actor({\n\tstate: { value: 0 },\n\tactions: {\n\t\t// Simple synchronous action that returns a value directly\n\t\tincrement: (c, amount = 1) => {\n\t\t\tc.state.value += amount;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Synchronous action that returns an object\n\t\tgetInfo: (c) => {\n\t\t\treturn {\n\t\t\t\tcurrentValue: c.state.value,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t},\n\t\t// Synchronous action with no return value (void)\n\t\treset: (c) => {\n\t\t\tc.state.value = 0;\n\t\t},\n\t},\n});\n\n// Actor with asynchronous actions\nexport const asyncActionActor = actor({\n\tstate: { value: 0, data: null as any },\n\tactions: {\n\t\t// Async action with a delay\n\t\tdelayedIncrement: async (c, amount = 1) => {\n\t\t\tawait Promise.resolve();\n\t\t\tc.state.value += amount;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Async action that simulates an API call\n\t\tfetchData: async (c, id: string) => {\n\t\t\tawait Promise.resolve();\n\n\t\t\t// Simulate response data\n\t\t\tconst data = { id, timestamp: Date.now() };\n\t\t\tc.state.data = data;\n\t\t\treturn data;\n\t\t},\n\t\t// Async action with error handling\n\t\tasyncWithError: async (c, shouldError: boolean) => {\n\t\t\tawait Promise.resolve();\n\n\t\t\tif (shouldError) {\n\t\t\t\tthrow new UserError(\"Intentional error\");\n\t\t\t}\n\n\t\t\treturn \"Success\";\n\t\t},\n\t},\n});\n\n// Actor with promise actions\nexport const promiseActor = actor({\n\tstate: { results: [] as string[] },\n\tactions: {\n\t\t// Action that returns a resolved promise\n\t\tresolvedPromise: (c) => {\n\t\t\treturn Promise.resolve(\"resolved value\");\n\t\t},\n\t\t// Action that returns a promise that resolves after a delay\n\t\tdelayedPromise: (c): Promise => {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tc.state.results.push(\"delayed\");\n\t\t\t\tresolve(\"delayed value\");\n\t\t\t});\n\t\t},\n\t\t// Action that returns a rejected promise\n\t\trejectedPromise: (c) => {\n\t\t\treturn Promise.reject(new UserError(\"promised rejection\"));\n\t\t},\n\t\t// Action to check the collected results\n\t\tgetResults: (c) => {\n\t\t\treturn c.state.results;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Short timeout actor\nexport const shortTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 50, // 50ms timeout\n\t},\n\tactions: {\n\t\tquickAction: async (c) => {\n\t\t\treturn \"quick response\";\n\t\t},\n\t\tslowAction: async (c) => {\n\t\t\t// This action should timeout\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\treturn \"slow response\";\n\t\t},\n\t},\n});\n\n// Long timeout actor\nexport const longTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 200, // 200ms timeout\n\t},\n\tactions: {\n\t\tdelayedAction: async (c) => {\n\t\t\t// This action should complete within timeout\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\treturn \"delayed response\";\n\t\t},\n\t},\n});\n\n// Default timeout actor\nexport const defaultTimeoutActor = actor({\n\tstate: { value: 0 },\n\tactions: {\n\t\tnormalAction: async (c) => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t\treturn \"normal response\";\n\t\t},\n\t},\n});\n\n// Sync actor (timeout shouldn't apply)\nexport const syncTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 50, // 50ms timeout\n\t},\n\tactions: {\n\t\tsyncAction: (c) => {\n\t\t\treturn \"sync response\";\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\nexport const errorHandlingActor = actor({\n\tstate: {\n\t\terrorLog: [] as string[],\n\t},\n\tactions: {\n\t\t// Action that throws a UserError with just a message\n\t\tthrowSimpleError: () => {\n\t\t\tthrow new UserError(\"Simple error message\");\n\t\t},\n\n\t\t// Action that throws a UserError with code and metadata\n\t\tthrowDetailedError: () => {\n\t\t\tthrow new UserError(\"Detailed error message\", {\n\t\t\t\tcode: \"detailed_error\",\n\t\t\t\tmetadata: {\n\t\t\t\t\treason: \"test\",\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\n\t\t// Action that throws an internal error\n\t\tthrowInternalError: () => {\n\t\t\tthrow new Error(\"This is an internal error\");\n\t\t},\n\n\t\t// Action that returns successfully\n\t\tsuccessfulAction: () => {\n\t\t\treturn \"success\";\n\t\t},\n\n\t\t// Action that times out (simulated with a long delay)\n\t\ttimeoutAction: async (c) => {\n\t\t\t// This action should time out if the timeout is configured\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tresolve(\"This should not be reached if timeout works\");\n\t\t\t\t}, 10000); // 10 seconds\n\t\t\t});\n\t\t},\n\n\t\t// Action with configurable delay to test timeout edge cases\n\t\tdelayedAction: async (c, delayMs: number) => {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tresolve(`Completed after ${delayMs}ms`);\n\t\t\t\t}, delayMs);\n\t\t\t});\n\t\t},\n\n\t\t// Log an error for inspection\n\t\tlogError: (c, error: string) => {\n\t\t\tc.state.errorLog.push(error);\n\t\t\treturn c.state.errorLog;\n\t\t},\n\n\t\t// Get the error log\n\t\tgetErrorLog: (c) => {\n\t\t\treturn c.state.errorLog;\n\t\t},\n\n\t\t// Clear the error log\n\t\tclearErrorLog: (c) => {\n\t\t\tc.state.errorLog = [];\n\t\t\treturn true;\n\t\t},\n\t},\n\toptions: {\n\t\tactionTimeout: 500, // 500ms timeout for actions\n\t},\n});\n\n// Actor with custom timeout\nexport const customTimeoutActor = actor({\n\tstate: {},\n\tactions: {\n\t\tquickAction: async () => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t\treturn \"Quick action completed\";\n\t\t},\n\t\tslowAction: async () => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 300));\n\t\t\treturn \"Slow action completed\";\n\t\t},\n\t},\n\toptions: {\n\t\tactionTimeout: 200, // 200ms timeout\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const onStateChangeActor = actor({\n\tstate: {\n\t\tvalue: 0,\n\t},\n\tvars: {\n\t\tchangeCount: 0,\n\t},\n\tactions: {\n\t\t// Action that modifies state - should trigger onStateChange\n\t\tsetValue: (c, newValue: number) => {\n\t\t\tc.state.value = newValue;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that modifies state multiple times - should trigger onStateChange for each change\n\t\tincrementMultiple: (c, times: number) => {\n\t\t\tfor (let i = 0; i < times; i++) {\n\t\t\t\tc.state.value++;\n\t\t\t}\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that doesn't modify state - should NOT trigger onStateChange\n\t\tgetValue: (c) => {\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that reads and returns without modifying - should NOT trigger onStateChange\n\t\tgetDoubled: (c) => {\n\t\t\tconst doubled = c.state.value * 2;\n\t\t\treturn doubled;\n\t\t},\n\t\t// Get the count of how many times onStateChange was called\n\t\tgetChangeCount: (c) => {\n\t\t\treturn c.vars.changeCount;\n\t\t},\n\t\t// Reset change counter for testing\n\t\tresetChangeCount: (c) => {\n\t\t\tc.vars.changeCount = 0;\n\t\t},\n\t},\n\t// Track onStateChange calls\n\tonStateChange: (c) => {\n\t\tc.vars.changeCount++;\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Note: For testing only - metadata API will need to be mocked\n// in tests since this is implementation-specific\nexport const metadataActor = actor({\n\tstate: {\n\t\tlastMetadata: null as any,\n\t\tactorName: \"\",\n\t\t// Store tags and region in state for testing since they may not be\n\t\t// available in the context in all environments\n\t\tstoredTags: {} as Record,\n\t\tstoredRegion: null as string | null,\n\t},\n\tonWake: (c) => {\n\t\t// Store the actor name during initialization\n\t\tc.state.actorName = c.name;\n\t},\n\tactions: {\n\t\t// Set up test tags - this will be called by tests to simulate tags\n\t\tsetupTestTags: (c, tags: Record) => {\n\t\t\tc.state.storedTags = tags;\n\t\t\treturn tags;\n\t\t},\n\n\t\t// Set up test region - this will be called by tests to simulate region\n\t\tsetupTestRegion: (c, region: string) => {\n\t\t\tc.state.storedRegion = region;\n\t\t\treturn region;\n\t\t},\n\n\t\t// Get all available metadata\n\t\tgetMetadata: (c) => {\n\t\t\t// Create metadata object from stored values\n\t\t\tconst metadata = {\n\t\t\t\tname: c.name,\n\t\t\t\ttags: c.state.storedTags,\n\t\t\t\tregion: c.state.storedRegion,\n\t\t\t};\n\n\t\t\t// Store for later inspection\n\t\t\tc.state.lastMetadata = metadata;\n\t\t\treturn metadata;\n\t\t},\n\n\t\t// Get the actor name\n\t\tgetActorName: (c) => {\n\t\t\treturn c.name;\n\t\t},\n\n\t\t// Get a specific tag by key\n\t\tgetTag: (c, key: string) => {\n\t\t\treturn c.state.storedTags[key] || null;\n\t\t},\n\n\t\t// Get all tags\n\t\tgetTags: (c) => {\n\t\t\treturn c.state.storedTags;\n\t\t},\n\n\t\t// Get the region\n\t\tgetRegion: (c) => {\n\t\t\treturn c.state.storedRegion;\n\t\t},\n\n\t\t// Get the stored actor name (from onWake)\n\t\tgetStoredActorName: (c) => {\n\t\t\treturn c.state.actorName;\n\t\t},\n\n\t\t// Get last retrieved metadata\n\t\tgetLastMetadata: (c) => {\n\t\t\treturn c.state.lastMetadata;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Actor with static vars\nexport const staticVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tvars: { counter: 42, name: \"test-actor\" },\n\tactions: {\n\t\tgetVars: (c) => {\n\t\t\treturn c.vars;\n\t\t},\n\t\tgetName: (c) => {\n\t\t\treturn c.vars.name;\n\t\t},\n\t},\n});\n\n// Actor with nested vars\nexport const nestedVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tvars: {\n\t\tcounter: 42,\n\t\tnested: {\n\t\t\tvalue: \"original\",\n\t\t\tarray: [1, 2, 3],\n\t\t\tobj: { key: \"value\" },\n\t\t},\n\t},\n\tactions: {\n\t\tgetVars: (c) => {\n\t\t\treturn c.vars;\n\t\t},\n\t\tmodifyNested: (c) => {\n\t\t\t// Attempt to modify the nested object\n\t\t\tc.vars.nested.value = \"modified\";\n\t\t\tc.vars.nested.array.push(4);\n\t\t\tc.vars.nested.obj.key = \"new-value\";\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor with dynamic vars\nexport const dynamicVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: () => {\n\t\treturn {\n\t\t\trandom: Math.random(),\n\t\t\tcomputed: `Actor-${Math.floor(Math.random() * 1000)}`,\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor with unique vars per instance\nexport const uniqueVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: () => {\n\t\treturn {\n\t\t\tid: Math.floor(Math.random() * 1000000),\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor that uses driver context\nexport const driverCtxActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: (c, driverCtx: any) => {\n\t\treturn {\n\t\t\thasDriverCtx: Boolean(driverCtx?.isTest),\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n","import { actor, type ActorContext } from \"rivetkit\";\n\nexport const kvActor = actor({\n\tactions: {\n\t\tputText: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t\tvalue: string,\n\t\t) => {\n\t\t\tawait c.kv.put(key, value);\n\t\t\treturn true;\n\t\t},\n\t\tgetText: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t) => {\n\t\t\treturn await c.kv.get(key);\n\t\t},\n\t\tlistText: async (\n\t\t\tc: ActorContext,\n\t\t\tprefix: string,\n\t\t) => {\n\t\t\tconst results = await c.kv.list(prefix, { keyType: \"text\" });\n\t\t\treturn results.map(([key, value]) => ({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t}));\n\t\t},\n\t\troundtripArrayBuffer: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t\tvalues: number[],\n\t\t) => {\n\t\t\tconst buffer = new Uint8Array(values).buffer;\n\t\t\tawait c.kv.put(key, buffer, { type: \"arrayBuffer\" });\n\t\t\tconst result = await c.kv.get(key, { type: \"arrayBuffer\" });\n\t\t\tif (!result) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn Array.from(new Uint8Array(result));\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n/**\n * Actor for testing large payloads without connections\n */\nexport const largePayloadActor = actor({\n\tstate: {},\n\tactions: {\n\t\t/**\n\t\t * Accepts a large request payload and returns its size\n\t\t */\n\t\tprocessLargeRequest: (c, data: { items: string[] }) => {\n\t\t\treturn {\n\t\t\t\titemCount: data.items.length,\n\t\t\t\tfirstItem: data.items[0],\n\t\t\t\tlastItem: data.items[data.items.length - 1],\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Returns a large response payload\n\t\t */\n\t\tgetLargeResponse: (c, itemCount: number) => {\n\t\t\tconst items: string[] = [];\n\t\t\tfor (let i = 0; i < itemCount; i++) {\n\t\t\t\titems.push(`Item ${i} with some additional text to increase size`);\n\t\t\t}\n\t\t\treturn { items };\n\t\t},\n\n\t\t/**\n\t\t * Echo back the request data\n\t\t */\n\t\techo: (c, data: unknown) => {\n\t\t\treturn data;\n\t\t},\n\t},\n});\n\n/**\n * Actor for testing large payloads with connections\n */\nexport const largePayloadConnActor = actor({\n\tstate: {},\n\tconnState: {\n\t\tlastRequestSize: 0,\n\t},\n\tactions: {\n\t\t/**\n\t\t * Accepts a large request payload and returns its size\n\t\t */\n\t\tprocessLargeRequest: (c, data: { items: string[] }) => {\n\t\t\tc.conn.state.lastRequestSize = data.items.length;\n\t\t\treturn {\n\t\t\t\titemCount: data.items.length,\n\t\t\t\tfirstItem: data.items[0],\n\t\t\t\tlastItem: data.items[data.items.length - 1],\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Returns a large response payload\n\t\t */\n\t\tgetLargeResponse: (c, itemCount: number) => {\n\t\t\tconst items: string[] = [];\n\t\t\tfor (let i = 0; i < itemCount; i++) {\n\t\t\t\titems.push(`Item ${i} with some additional text to increase size`);\n\t\t\t}\n\t\t\treturn { items };\n\t\t},\n\n\t\t/**\n\t\t * Echo back the request data\n\t\t */\n\t\techo: (c, data: unknown) => {\n\t\t\treturn data;\n\t\t},\n\n\t\t/**\n\t\t * Get the last request size\n\t\t */\n\t\tgetLastRequestSize: (c) => {\n\t\t\treturn c.conn.state.lastRequestSize;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const sqliteRawActor = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS todos (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\ttitle TEXT NOT NULL,\n\t\t\t\t\tcompleted INTEGER DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\taddTodo: async (c, title: string) => {\n\t\t\tconst createdAt = Date.now();\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO todos (title, created_at) VALUES (?, ?)\",\n\t\t\t\ttitle,\n\t\t\t\tcreatedAt,\n\t\t\t);\n\t\t\treturn { title, createdAt };\n\t\t},\n\t\tgetTodos: async (c) => {\n\t\t\treturn await c.db.execute(\"SELECT * FROM todos ORDER BY created_at DESC\");\n\t\t},\n\t\ttoggleTodo: async (c, id: number) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE todos SET completed = NOT completed WHERE id = ?\",\n\t\t\t\tid,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM todos WHERE id = ?\", id);\n\t\t\treturn rows[0];\n\t\t},\n\t\tdeleteTodo: async (c, id: number) => {\n\t\t\tawait c.db.execute(\"DELETE FROM todos WHERE id = ?\", id);\n\t\t\treturn { deleted: id };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db/drizzle\";\nimport { eq } from \"drizzle-orm\";\nimport * as schema from \"./schema.ts\";\nimport migrations from \"./drizzle/migrations.js\";\n\nconst { todos } = schema;\n\nexport const sqliteDrizzleActor = actor({\n\tdb: db({ schema, migrations }),\n\tactions: {\n\t\taddTodo: async (c, title: string) => {\n\t\t\tconst result = await c.db.insert(todos).values({\n\t\t\t\ttitle,\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t}).returning();\n\t\t\treturn result[0];\n\t\t},\n\t\tgetTodos: async (c) => {\n\t\t\treturn await c.db.select().from(todos).orderBy(todos.createdAt);\n\t\t},\n\t\ttoggleTodo: async (c, id: number) => {\n\t\t\tconst existing = await c.db.select().from(todos).where(eq(todos.id, id));\n\t\t\tif (!existing[0]) return null;\n\t\t\tconst newCompleted = existing[0].completed ? 0 : 1;\n\t\t\tconst result = await c.db.update(todos)\n\t\t\t\t.set({ completed: newCompleted })\n\t\t\t\t.where(eq(todos.id, id))\n\t\t\t\t.returning();\n\t\t\treturn result[0];\n\t\t},\n\t\tdeleteTodo: async (c, id: number) => {\n\t\t\tawait c.db.delete(todos).where(eq(todos.id, id));\n\t\t\treturn { deleted: id };\n\t\t},\n\t},\n});\n","import { sqliteTable, text, integer } from \"rivetkit/db/drizzle\";\n\nexport const todos = sqliteTable(\"todos\", {\n\tid: integer(\"id\").primaryKey({ autoIncrement: true }),\n\ttitle: text(\"title\").notNull(),\n\tcompleted: integer(\"completed\").default(0),\n\tcreatedAt: integer(\"created_at\").notNull(),\n});\n","{\n \"version\": \"7\",\n \"dialect\": \"sqlite\",\n \"entries\": [\n {\n \"idx\": 0,\n \"version\": \"6\",\n \"when\": 1770921282251,\n \"tag\": \"0000_left_wrecking_crew\",\n \"breakpoints\": true\n }\n ]\n}","CREATE TABLE `todos` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`title` text NOT NULL,\n\t`completed` integer DEFAULT 0,\n\t`created_at` integer NOT NULL\n);\n","import journal from './meta/_journal.json' with { type: 'json' };\nimport m0000 from './0000_left_wrecking_crew.sql';\n\n export default {\n journal,\n migrations: {\n m0000\n }\n }\n \n","import { actor, event } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const parallelismTest = actor({\n\tstate: {\n\t\tstateCount: 0,\n\t},\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tcount INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(`\n\t\t\t\tINSERT OR IGNORE INTO counter (id, count) VALUES (1, 0)\n\t\t\t`);\n\t\t},\n\t}),\n\tevents: {\n\t\tstateCountChanged: event<{ count: number }>(),\n\t\tsqliteCountChanged: event<{ count: number }>(),\n\t},\n\tactions: {\n\t\tincrementState: (c) => {\n\t\t\tc.state.stateCount += 1;\n\t\t\tc.broadcast(\"stateCountChanged\", { count: c.state.stateCount });\n\t\t\treturn { count: c.state.stateCount };\n\t\t},\n\t\tgetStateCount: (c) => {\n\t\t\treturn { count: c.state.stateCount };\n\t\t},\n\t\tincrementSqlite: async (c) => {\n\t\t\tawait c.db.execute(`UPDATE counter SET count = count + 1 WHERE id = 1`);\n\t\t\tconst results = await c.db.execute<{ count: number }>(\n\t\t\t\t`SELECT count FROM counter WHERE id = 1`,\n\t\t\t);\n\t\t\tconst count = results[0].count;\n\t\t\tc.broadcast(\"sqliteCountChanged\", { count });\n\t\t\treturn { count };\n\t\t},\n\t\tgetSqliteCount: async (c) => {\n\t\t\tconst results = await c.db.execute<{ count: number }>(\n\t\t\t\t`SELECT count FROM counter WHERE id = 1`,\n\t\t\t);\n\t\t\treturn { count: results[0].count };\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: 30_000,\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport type ConnState = {\n\tusername: string;\n\trole: string;\n\tcounter: number;\n\tcreatedAt: number;\n\tnoCount: boolean;\n};\n\nexport const connStateActor = actor({\n\tstate: {\n\t\tsharedCounter: 0,\n\t\tdisconnectionCount: 0,\n\t},\n\tevents: {\n\t\tuserConnected: event<{ id: string; username: string; role: string }>(),\n\t\tuserDisconnected: event<{ id: string }>(),\n\t\tdirectMessage: event<{ from: string; message: string }>(),\n\t},\n\t// Define connection state\n\tcreateConnState: (\n\t\tc,\n\t\tparams: { username?: string; role?: string; noCount?: boolean },\n\t): ConnState => {\n\t\treturn {\n\t\t\tusername: params?.username || \"anonymous\",\n\t\t\trole: params?.role || \"user\",\n\t\t\tcounter: 0,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tnoCount: params?.noCount ?? false,\n\t\t};\n\t},\n\t// Lifecycle hook when a connection is established\n\tonConnect: (c, conn) => {\n\t\t// Broadcast event about the new connection\n\t\tc.broadcast(\"userConnected\", {\n\t\t\tid: conn.id,\n\t\t\tusername: \"anonymous\",\n\t\t\trole: \"user\",\n\t\t});\n\t},\n\t// Lifecycle hook when a connection is closed\n\tonDisconnect: (c, conn) => {\n\t\tif (!conn.state?.noCount) {\n\t\t\tc.state.disconnectionCount += 1;\n\t\t\tc.broadcast(\"userDisconnected\", {\n\t\t\t\tid: conn.id,\n\t\t\t});\n\t\t}\n\t},\n\tactions: {\n\t\t// Action to increment the connection's counter\n\t\tincrementConnCounter: (c, amount = 1) => {\n\t\t\tc.conn.state.counter += amount;\n\t\t},\n\n\t\t// Action to increment the shared counter\n\t\tincrementSharedCounter: (c, amount = 1) => {\n\t\t\tc.state.sharedCounter += amount;\n\t\t\treturn c.state.sharedCounter;\n\t\t},\n\n\t\t// Get the connection state\n\t\tgetConnectionState: (c) => {\n\t\t\treturn { id: c.conn.id, ...c.conn.state };\n\t\t},\n\n\t\t// Check all active connections\n\t\tgetConnectionIds: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.filter((c) => !c[1].state?.noCount)\n\t\t\t\t.map((x) => x[0])\n\t\t\t\t.toArray();\n\t\t},\n\n\t\t// Get disconnection count\n\t\tgetDisconnectionCount: (c) => {\n\t\t\treturn c.state.disconnectionCount;\n\t\t},\n\n\t\t// Get all active connection states\n\t\tgetAllConnectionStates: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.map(([id, conn]) => ({ id, ...conn.state }))\n\t\t\t\t.toArray();\n\t\t},\n\n\t\t// Send message to a specific connection with matching ID\n\t\tsendToConnection: (c, targetId: string, message: string) => {\n\t\t\tif (c.conns.has(targetId)) {\n\t\t\t\tc.conns\n\t\t\t\t\t.get(targetId)!\n\t\t\t\t\t.send(\"directMessage\", { from: c.conn.id, message });\n\t\t\t\treturn true;\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\t// Update connection state (simulated for tests)\n\t\tupdateConnection: (\n\t\t\tc,\n\t\t\tupdates: Partial<{ username: string; role: string }>,\n\t\t) => {\n\t\t\tif (updates.username) c.conn.state.username = updates.username;\n\t\t\tif (updates.role) c.conn.state.role = updates.role;\n\t\t\treturn c.conn.state;\n\t\t},\n\t\tdisconnectSelf: (c, reason?: string) => {\n\t\t\tc.conn.disconnect(reason ?? \"test.disconnect\");\n\t\t\treturn true;\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\nexport const rejectConnectionActor = actor({\n\tonBeforeConnect: async (_c, params: { reject?: boolean }) => {\n\t\tif (params?.reject) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\tthrow new UserError(\"Rejected connection\", {\n\t\t\t\tcode: \"rejected\",\n\t\t\t});\n\t\t}\n\t},\n\tactions: {\n\t\tping: () => \"pong\",\n\t},\n});\n","import { actor, type RivetMessageEvent } from \"rivetkit\";\n\n/**\n * Test fixture to verify request object access in all lifecycle hooks\n */\nexport const requestAccessActor = actor({\n\tstate: {\n\t\t// Track request info from different hooks\n\t\tonBeforeConnectRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tcreateConnStateRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tonRequestRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tonWebSocketRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t},\n\tcreateConnState: (c, params: { trackRequest?: boolean }) => {\n\t\t// In createConnState, the state isn't available yet.\n\n\t\tlet requestInfo: {\n\t\t\thasRequest: boolean;\n\t\t\trequestUrl: string;\n\t\t\trequestMethod: string;\n\t\t\trequestHeaders: Record;\n\t\t} | null = null;\n\n\t\tif (params?.trackRequest && c.request) {\n\t\t\tconst headers: Record = {};\n\t\t\tc.request.headers.forEach((value, key) => {\n\t\t\t\theaders[key] = value;\n\t\t\t});\n\t\t\trequestInfo = {\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: c.request.url,\n\t\t\t\trequestMethod: c.request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\ttrackRequest: params?.trackRequest || false,\n\t\t\trequestInfo,\n\t\t};\n\t},\n\tonConnect: (c, conn) => {\n\t\t// Copy request info from connection state if it was tracked\n\t\tif (conn.state.requestInfo) {\n\t\t\tc.state.createConnStateRequest = conn.state.requestInfo;\n\t\t}\n\t},\n\tonBeforeConnect: (c, params) => {\n\t\tif (params?.trackRequest) {\n\t\t\tif (c.request) {\n\t\t\t\tc.state.onBeforeConnectRequest.hasRequest = true;\n\t\t\t\tc.state.onBeforeConnectRequest.requestUrl = c.request.url;\n\t\t\t\tc.state.onBeforeConnectRequest.requestMethod = c.request.method;\n\n\t\t\t\t// Store select headers\n\t\t\t\tconst headers: Record = {};\n\t\t\t\tc.request.headers.forEach((value, key) => {\n\t\t\t\t\theaders[key] = value;\n\t\t\t\t});\n\t\t\t\tc.state.onBeforeConnectRequest.requestHeaders = headers;\n\t\t\t} else {\n\t\t\t\t// Track that we tried but request was not available\n\t\t\t\tc.state.onBeforeConnectRequest.hasRequest = false;\n\t\t\t}\n\t\t}\n\t},\n\tonRequest: (c, request) => {\n\t\t// Store request info\n\t\tc.state.onRequestRequest.hasRequest = true;\n\t\tc.state.onRequestRequest.requestUrl = request.url;\n\t\tc.state.onRequestRequest.requestMethod = request.method;\n\n\t\t// Store select headers\n\t\tconst headers: Record = {};\n\t\trequest.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tc.state.onRequestRequest.requestHeaders = headers;\n\n\t\t// Return response with request info\n\t\treturn new Response(\n\t\t\tJSON.stringify({\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: request.url,\n\t\t\t\trequestMethod: request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t}),\n\t\t\t{\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t},\n\t\t);\n\t},\n\tonWebSocket: (c, websocket) => {\n\t\tif (!c.request) throw \"Missing request\";\n\t\t// Store request info\n\t\tc.state.onWebSocketRequest.hasRequest = true;\n\t\tc.state.onWebSocketRequest.requestUrl = c.request.url;\n\t\tc.state.onWebSocketRequest.requestMethod = c.request.method;\n\n\t\t// Store select headers\n\t\tconst headers: Record = {};\n\t\tc.request.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tc.state.onWebSocketRequest.requestHeaders = headers;\n\n\t\t// Send request info on connection\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: c.request.url,\n\t\t\t\trequestMethod: c.request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t}),\n\t\t);\n\n\t\t// Echo messages back\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\twebsocket.send(event.data);\n\t\t});\n\t},\n\tactions: {\n\t\tgetRequestInfo: (c) => {\n\t\t\treturn {\n\t\t\t\tonBeforeConnect: c.state.onBeforeConnectRequest,\n\t\t\t\tcreateConnState: c.state.createConnStateRequest,\n\t\t\t\tonRequest: c.state.onRequestRequest,\n\t\t\t\tonWebSocket: c.state.onWebSocketRequest,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { Hono } from \"hono\";\nimport { actor, type RequestContext } from \"rivetkit\";\n\nexport const rawHttpActor = actor({\n\tstate: {\n\t\trequestCount: 0,\n\t},\n\tonRequest(\n\t\tctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\tconst url = new URL(request.url);\n\t\tconst method = request.method;\n\n\t\t// Track request count\n\t\tctx.state.requestCount++;\n\n\t\t// Handle different endpoints\n\t\tif (url.pathname === \"/api/hello\") {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ message: \"Hello from actor!\" }),\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (url.pathname === \"/api/echo\" && method === \"POST\") {\n\t\t\treturn new Response(request.body, {\n\t\t\t\theaders: request.headers,\n\t\t\t});\n\t\t}\n\n\t\tif (url.pathname === \"/api/state\") {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\trequestCount: ctx.state.requestCount,\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (url.pathname === \"/api/headers\") {\n\t\t\tconst headers: Record = {};\n\t\t\trequest.headers.forEach((value, key) => {\n\t\t\t\theaders[key] = value;\n\t\t\t});\n\t\t\treturn new Response(JSON.stringify(headers), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\t// Return 404 for unhandled paths\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t},\n\tactions: {},\n});\n\nexport const rawHttpNoHandlerActor = actor({\n\tactions: {},\n});\n\nexport const rawHttpVoidReturnActor = actor({\n\tonRequest(ctx, request) {\n\t\t// Intentionally return void to test error handling\n\t\treturn undefined as any;\n\t},\n\tactions: {},\n});\n\nexport const rawHttpHonoActor = actor({\n\tcreateVars() {\n\t\tconst router = new Hono();\n\n\t\t// Set up routes\n\t\trouter.get(\"/\", (c: any) =>\n\t\t\tc.json({ message: \"Welcome to Hono actor!\" }),\n\t\t);\n\n\t\trouter.get(\"/users\", (c: any) =>\n\t\t\tc.json([\n\t\t\t\t{ id: 1, name: \"Alice\" },\n\t\t\t\t{ id: 2, name: \"Bob\" },\n\t\t\t]),\n\t\t);\n\n\t\trouter.get(\"/users/:id\", (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\treturn c.json({\n\t\t\t\tid: parseInt(id),\n\t\t\t\tname: id === \"1\" ? \"Alice\" : \"Bob\",\n\t\t\t});\n\t\t});\n\n\t\trouter.post(\"/users\", async (c: any) => {\n\t\t\tconst body = await c.req.json();\n\t\t\treturn c.json({ id: 3, ...body }, 201);\n\t\t});\n\n\t\trouter.put(\"/users/:id\", async (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\tconst body = await c.req.json();\n\t\t\treturn c.json({ id: parseInt(id), ...body });\n\t\t});\n\n\t\trouter.delete(\"/users/:id\", (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\treturn c.json({ message: `User ${id} deleted` });\n\t\t});\n\n\t\t// Return the router as a var\n\t\treturn { router };\n\t},\n\tonRequest(\n\t\tctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\t// Use the Hono router from vars\n\t\treturn ctx.vars.router.fetch(request);\n\t},\n\tactions: {},\n});\n","import { actor, type RequestContext } from \"rivetkit\";\n\nexport const rawHttpRequestPropertiesActor = actor({\n\tactions: {},\n\tonRequest(\n\t\tctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\t// Extract all relevant Request properties\n\t\tconst url = new URL(request.url);\n\t\tconst method = request.method;\n\n\t\t// Get all headers\n\t\tconst headers: Record = {};\n\t\trequest.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\n\t\t// Handle body based on content type\n\t\tconst handleBody = async () => {\n\t\t\tif (!request.body) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\n\t\t\ttry {\n\t\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\t\tconst text = await request.text();\n\t\t\t\t\treturn text ? JSON.parse(text) : null;\n\t\t\t\t} else {\n\t\t\t\t\t// For non-JSON, return as text\n\t\t\t\t\tconst text = await request.text();\n\t\t\t\t\treturn text || null; // Return null for empty bodies\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If body parsing fails, return null\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\n\t\t// Special handling for HEAD and OPTIONS requests\n\t\tif (method === \"HEAD\") {\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 200,\n\t\t\t});\n\t\t}\n\n\t\tif (method === \"OPTIONS\") {\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 204,\n\t\t\t});\n\t\t}\n\n\t\t// Return all request properties as JSON\n\t\treturn handleBody().then((body) => {\n\t\t\tconst responseData = {\n\t\t\t\t// URL properties\n\t\t\t\turl: request.url,\n\t\t\t\tpathname: url.pathname,\n\t\t\t\tsearch: url.search,\n\t\t\t\tsearchParams: Object.fromEntries(url.searchParams.entries()),\n\t\t\t\thash: url.hash,\n\n\t\t\t\t// Method\n\t\t\t\tmethod: request.method,\n\n\t\t\t\t// Headers\n\t\t\t\theaders: headers,\n\n\t\t\t\t// Body\n\t\t\t\tbody,\n\t\t\t\tbodyText:\n\t\t\t\t\ttypeof body === \"string\"\n\t\t\t\t\t\t? body\n\t\t\t\t\t\t: body === null && request.body !== null\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: null,\n\n\t\t\t\t// Additional properties that might be available\n\t\t\t\t// Note: Some properties like cache, credentials, mode, etc.\n\t\t\t\t// might not be available in all environments\n\t\t\t\tcache: request.cache || null,\n\t\t\t\tcredentials: request.credentials || null,\n\t\t\t\tmode: request.mode || null,\n\t\t\t\tredirect: request.redirect || null,\n\t\t\t\treferrer: request.referrer || null,\n\t\t\t};\n\n\t\t\treturn new Response(JSON.stringify(responseData), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t});\n\t},\n});\n","import { type ActorContext, actor, type UniversalWebSocket } from \"rivetkit\";\n\nexport const rawWebSocketActor = actor({\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t},\n\tonWebSocket(ctx, websocket) {\n\t\tctx.state.connectionCount = ctx.state.connectionCount + 1;\n\t\tconsole.log(\n\t\t\t`[ACTOR] New connection, count: ${ctx.state.connectionCount}`,\n\t\t);\n\n\t\t// Send welcome message\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t}),\n\t\t);\n\t\tconsole.log(\"[ACTOR] Sent welcome message\");\n\n\t\t// Echo messages back\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tctx.state.messageCount = ctx.state.messageCount + 1;\n\t\t\tconsole.log(\n\t\t\t\t`[ACTOR] Message received, total count: ${ctx.state.messageCount}, data:`,\n\t\t\t\tevent.data,\n\t\t\t);\n\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\tif (parsed.type === \"ping\") {\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"getStats\") {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`[ACTOR] Sending stats - connections: ${ctx.state.connectionCount}, messages: ${ctx.state.messageCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"stats\",\n\t\t\t\t\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t\t\t\t\t\tmessageCount: ctx.state.messageCount,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"getRequestInfo\") {\n\t\t\t\t\t\t// Send back the request URL info if available\n\t\t\t\t\t\tconst url = ctx.request?.url || \"ws://actor/websocket\";\n\t\t\t\t\t\tconst urlObj = new URL(url);\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"requestInfo\",\n\t\t\t\t\t\t\t\turl: url,\n\t\t\t\t\t\t\t\tpathname: urlObj.pathname,\n\t\t\t\t\t\t\t\tsearch: urlObj.search,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Echo back\n\t\t\t\t\t\twebsocket.send(data);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// If not JSON, just echo it back\n\t\t\t\t\twebsocket.send(data);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Echo binary data\n\t\t\t\twebsocket.send(data);\n\t\t\t}\n\t\t});\n\n\t\t// Handle close\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tctx.state.connectionCount = ctx.state.connectionCount - 1;\n\t\t\tconsole.log(\n\t\t\t\t`[ACTOR] Connection closed, count: ${ctx.state.connectionCount}`,\n\t\t\t);\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(ctx: any) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t\tmessageCount: ctx.state.messageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n\nexport const rawWebSocketBinaryActor = actor({\n\tonWebSocket(ctx, websocket) {\n\t\t// Handle binary data\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (data instanceof ArrayBuffer || data instanceof Uint8Array) {\n\t\t\t\t// Reverse the bytes and send back\n\t\t\t\tconst bytes = new Uint8Array(data);\n\t\t\t\tconst reversed = new Uint8Array(bytes.length);\n\t\t\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\t\t\treversed[i] = bytes[bytes.length - 1 - i];\n\t\t\t\t}\n\t\t\t\twebsocket.send(reversed);\n\t\t\t}\n\t\t});\n\t},\n\tactions: {},\n});\n","import { Hono } from \"hono\";\nimport { type ActorContextOf, actor } from \"rivetkit\";\n\nexport const rawFetchCounter = actor({\n\tstate: {\n\t\tcount: 0,\n\t},\n\tcreateVars: () => {\n\t\t// Setup router\n\t\treturn { router: createCounterRouter() };\n\t},\n\tonRequest: (c, request) => {\n\t\treturn c.vars.router.fetch(request, { actor: c });\n\t},\n\tactions: {\n\t\t// ...actions...\n\t},\n});\n\nfunction createCounterRouter(): Hono {\n\tconst app = new Hono<{\n\t\tBindings: { actor: ActorContextOf };\n\t}>();\n\n\tapp.get(\"/count\", (c) => {\n\t\tconst { actor } = c.env;\n\n\t\treturn c.json({\n\t\t\tcount: actor.state.count,\n\t\t});\n\t});\n\n\tapp.post(\"/increment\", (c) => {\n\t\tconst { actor } = c.env;\n\n\t\tactor.state.count++;\n\t\treturn c.json({\n\t\t\tcount: actor.state.count,\n\t\t});\n\t});\n\n\treturn app;\n}\n","import { actor } from \"rivetkit\";\n\nexport const rawWebSocketChatRoom = actor({\n\tstate: {\n\t\tmessages: [] as Array<{\n\t\t\tid: string;\n\t\t\ttext: string;\n\t\t\ttimestamp: number;\n\t\t}>,\n\t},\n\tcreateVars: () => {\n\t\treturn {\n\t\t\tsockets: new Set(),\n\t\t};\n\t},\n\tonWebSocket(ctx, socket) {\n\t\t// Add socket to the set\n\t\tctx.vars.sockets.add(socket);\n\n\t\t// Send recent messages to new connection\n\t\tsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"init\",\n\t\t\t\tmessages: ctx.state.messages,\n\t\t\t}),\n\t\t);\n\n\t\t// Handle incoming messages\n\t\tsocket.addEventListener(\"message\", (event: any) => {\n\t\t\ttry {\n\t\t\t\tconst data = JSON.parse(event.data);\n\n\t\t\t\tif (data.type === \"message\" && data.text) {\n\t\t\t\t\tconst message = {\n\t\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\t\ttext: data.text,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t};\n\n\t\t\t\t\t// Add to state\n\t\t\t\t\tctx.state.messages.push(message);\n\t\t\t\t\tctx.saveState({});\n\n\t\t\t\t\t// Keep only last 50 messages\n\t\t\t\t\tif (ctx.state.messages.length > 50) {\n\t\t\t\t\t\tctx.state.messages.shift();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Broadcast to all connected clients\n\t\t\t\t\tconst broadcast = JSON.stringify({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\t...message,\n\t\t\t\t\t});\n\n\t\t\t\t\tfor (const ws of ctx.vars.sockets) {\n\t\t\t\t\t\tif (ws.readyState === 1) {\n\t\t\t\t\t\t\t// OPEN\n\t\t\t\t\t\t\tws.send(broadcast);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Failed to process message:\", e);\n\t\t\t}\n\t\t});\n\n\t\t// Remove socket on close\n\t\tsocket.addEventListener(\"close\", () => {\n\t\t\tctx.vars.sockets.delete(socket);\n\t\t});\n\t},\n\tactions: {},\n});\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport const rawWebSocketServerlessSmoke = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tsleepCount: 0,\n\t\ttotalTickCount: 0,\n\t\ttotalMessageCount: 0,\n\t},\n\tasync onSleep(c) {\n\t\tconst delayMs = 10 + Math.floor(Math.random() * 1_991);\n\t\tc.state.sleepCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"raw websocket serverless smoke onSleep delay\",\n\t\t\tdelayMs,\n\t\t\tsleepCount: c.state.sleepCount,\n\t\t});\n\t\tawait sleep(delayMs);\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\t\tlet index = 0;\n\n\t\tconst sendTick = () => {\n\t\t\tif (websocket.readyState !== 1) return;\n\n\t\t\tconst timestamp = Date.now();\n\t\t\tconst message = {\n\t\t\t\ttype: \"tick\",\n\t\t\t\tconnectionId,\n\t\t\t\tindex,\n\t\t\t\ttimestamp,\n\t\t\t\tiso: new Date(timestamp).toISOString(),\n\t\t\t\ttotalTickCount: c.state.totalTickCount,\n\t\t\t};\n\n\t\t\tc.state.totalTickCount += 1;\n\t\t\tindex += 1;\n\t\t\twebsocket.send(JSON.stringify(message));\n\t\t};\n\n\t\tc.log.info({\n\t\t\tmsg: \"raw websocket serverless smoke connected\",\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\tsendTick();\n\t\tconst interval = setInterval(sendTick, 1_000);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.totalMessageCount += 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"raw websocket serverless smoke received message\",\n\t\t\t\tconnectionId,\n\t\t\t\ttotalMessageCount: c.state.totalMessageCount,\n\t\t\t});\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"ack\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\tindex,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\treceived: event.data,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(interval);\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"raw websocket serverless smoke disconnected\",\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\ttotalTickCount: c.state.totalTickCount,\n\t\t\t\ttotalMessageCount: c.state.totalMessageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\n\nexport const tunnelStress = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t\theartbeatCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tconst sendHeartbeat = () => {\n\t\t\tif (websocket.readyState !== 1) return;\n\n\t\t\tc.state.heartbeatCount += 1;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"heartbeat\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\theartbeatCount: c.state.heartbeatCount,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t};\n\n\t\tconst heartbeat = setInterval(sendHeartbeat, 1_000);\n\t\tsendHeartbeat();\n\n\t\twebsocket.addEventListener(\"message\", async (event: RivetMessageEvent) => {\n\t\t\t// Fast-path ping: echo back without touching KV so the client can measure raw RTT\n\t\t\t// without the per-message storage write. Used by the counter-latency client's first\n\t\t\t// two probes after WS open.\n\t\t\tif (typeof event.data === \"string\") {\n\t\t\t\tlet parsed: unknown;\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(event.data);\n\t\t\t\t} catch {\n\t\t\t\t\tparsed = undefined;\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tparsed &&\n\t\t\t\t\ttypeof parsed === \"object\" &&\n\t\t\t\t\t(parsed as { type?: unknown }).type === \"ping\"\n\t\t\t\t) {\n\t\t\t\t\tconst id = (parsed as { id?: unknown }).id;\n\t\t\t\t\tif (websocket.readyState === 1) {\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tc.state.messageCount += 1;\n\t\t\tawait c.kv.put(\"counter\", String(c.state.messageCount));\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"reply\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\treceived: event.data,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(heartbeat);\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\theartbeatCount: c.state.heartbeatCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\nexport const RUN_SLEEP_TIMEOUT = 500;\n\n// Actor that tracks tick counts and respects abort signal\nexport const runWithTicks = actor({\n\tstate: {\n\t\ttickCount: 0,\n\t\tlastTickAt: 0,\n\t\trunStarted: false,\n\t\trunExited: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started\");\n\n\t\twhile (!c.aborted) {\n\t\t\tc.state.tickCount += 1;\n\t\t\tc.state.lastTickAt = Date.now();\n\t\t\tc.log.info({ msg: \"tick\", tickCount: c.state.tickCount });\n\n\t\t\t// Wait 50ms between ticks, or exit early if aborted\n\t\t\tawait new Promise((resolve) => {\n\t\t\t\tconst timeout = setTimeout(resolve, 50);\n\t\t\t\tc.abortSignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\n\t\tc.state.runExited = true;\n\t\tc.log.info(\"run handler exiting gracefully\");\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\ttickCount: c.state.tickCount,\n\t\t\tlastTickAt: c.state.lastTickAt,\n\t\t\trunStarted: c.state.runStarted,\n\t\t\trunExited: c.state.runExited,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that consumes from a queue in the run handler\nexport const runWithQueueConsumer = actor({\n\tstate: {\n\t\tmessagesReceived: [] as Array<{ name: string; body: unknown }>,\n\t\trunStarted: false,\n\t},\n\tqueues: {\n\t\tmessages: queue(),\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, waiting for messages\");\n\n\t\tfor await (const message of c.queue.iter()) {\n\t\t\tc.log.info({ msg: \"received message\", body: message.body });\n\t\t\tc.state.messagesReceived.push({\n\t\t\t\tname: message.name,\n\t\t\t\tbody: message.body,\n\t\t\t});\n\t\t}\n\n\t\tc.log.info(\"run handler exiting gracefully\");\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\tmessagesReceived: c.state.messagesReceived,\n\t\t\trunStarted: c.state.runStarted,\n\t\t}),\n\t\tsendMessage: async (c, body: unknown) => {\n\t\t\tconst client = c.client();\n\t\t\tconst handle = client.runWithQueueConsumer.getForId(c.actorId);\n\t\t\tawait handle.send(\"messages\", body);\n\t\t\treturn true;\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that exits the run handler after a short delay to test crash behavior\nexport const runWithEarlyExit = actor({\n\tstate: {\n\t\trunStarted: false,\n\t\tdestroyCalled: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, will exit after delay\");\n\t\t// Wait a bit so we can observe the runStarted state before exit\n\t\tawait new Promise((resolve) => setTimeout(resolve, 200));\n\t\tc.log.info(\"run handler exiting early\");\n\t\t// Exit without respecting abort signal\n\t},\n\tonDestroy: (c) => {\n\t\tc.state.destroyCalled = true;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\trunStarted: c.state.runStarted,\n\t\t\tdestroyCalled: c.state.destroyCalled,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that throws an error in the run handler to test crash behavior\nexport const runWithError = actor({\n\tstate: {\n\t\trunStarted: false,\n\t\tdestroyCalled: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, will throw error\");\n\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\tthrow new Error(\"intentional error in run handler\");\n\t},\n\tonDestroy: (c) => {\n\t\tc.state.destroyCalled = true;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\trunStarted: c.state.runStarted,\n\t\t\tdestroyCalled: c.state.destroyCalled,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor without a run handler for comparison\nexport const runWithoutHandler = actor({\n\tstate: {\n\t\twakeCount: 0,\n\t},\n\tonWake: (c) => {\n\t\tc.state.wakeCount += 1;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\twakeCount: c.state.wakeCount,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n","import { actor, event, type UniversalWebSocket } from \"rivetkit\";\nimport { promiseWithResolvers } from \"rivetkit/utils\";\n\nexport const SLEEP_TIMEOUT = 1000;\n\nexport const sleep = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\ttriggerSleep: (c) => {\n\t\t\tc.sleep();\n\t\t},\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t\tsetAlarm: async (c, duration: number) => {\n\t\t\tawait c.schedule.after(duration, \"onAlarm\");\n\t\t},\n\t\tonAlarm: (c) => {\n\t\t\tc.log.info(\"alarm called\");\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithLongRpc = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tcreateVars: () =>\n\t\t({}) as { longRunningResolve: PromiseWithResolvers },\n\tevents: {\n\t\twaiting: event<[]>(),\n\t},\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t\tlongRunningRpc: async (c) => {\n\t\t\tc.log.info(\"starting long running rpc\");\n\t\t\tc.vars.longRunningResolve = promiseWithResolvers(() => {});\n\t\t\tc.broadcast(\"waiting\");\n\t\t\tawait c.vars.longRunningResolve.promise;\n\t\t\tc.log.info(\"finished long running rpc\");\n\t\t},\n\t\tfinishLongRunningRpc: (c) => c.vars.longRunningResolve?.resolve(),\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithRawHttp = actor({\n\tstate: { startCount: 0, sleepCount: 0, requestCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonRequest: async (c, request) => {\n\t\tc.state.requestCount += 1;\n\t\tconst url = new URL(request.url);\n\n\t\tif (url.pathname === \"/long-request\") {\n\t\t\tconst duration = parseInt(\n\t\t\t\turl.searchParams.get(\"duration\") || \"1000\",\n\t\t\t);\n\t\t\tc.log.info({ msg: \"starting long fetch request\", duration });\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, duration));\n\t\t\tc.log.info(\"finished long fetch request\");\n\t\t\treturn new Response(JSON.stringify({ completed: true }), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\trequestCount: c.state.requestCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithRawWebSocket = actor({\n\tstate: { startCount: 0, sleepCount: 0, connectionCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tc.state.connectionCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"websocket connected\",\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"connected\",\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\tif (parsed.type === \"getCounts\") {\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"counts\",\n\t\t\t\t\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\t\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\t\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"keepAlive\") {\n\t\t\t\t\t\t// Just acknowledge to keep connection alive\n\t\t\t\t\t\twebsocket.send(JSON.stringify({ type: \"ack\" }));\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Echo non-JSON messages\n\t\t\t\t\twebsocket.send(data);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"websocket disconnected\",\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithNoSleepOption = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t\tnoSleep: true,\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const scheduled = actor({\n\tstate: {\n\t\tlastRun: 0,\n\t\tscheduledCount: 0,\n\t\ttaskHistory: [] as string[],\n\t},\n\tevents: {\n\t\tscheduled: event<{ time: number; count: number }>(),\n\t\tscheduledWithId: event<{ taskId: string; time: number; count: number }>(),\n\t},\n\tactions: {\n\t\t// Schedule using 'at' with specific timestamp\n\t\tscheduleTaskAt: (c, timestamp: number) => {\n\t\t\tc.schedule.at(timestamp, \"onScheduledTask\");\n\t\t\treturn timestamp;\n\t\t},\n\n\t\t// Schedule using 'after' with delay\n\t\tscheduleTaskAfter: (c, delayMs: number) => {\n\t\t\tc.schedule.after(delayMs, \"onScheduledTask\");\n\t\t\treturn Date.now() + delayMs;\n\t\t},\n\n\t\t// Schedule with a task ID for ordering tests\n\t\tscheduleTaskAfterWithId: (c, taskId: string, delayMs: number) => {\n\t\t\tc.schedule.after(delayMs, \"onScheduledTaskWithId\", taskId);\n\t\t\treturn { taskId, scheduledFor: Date.now() + delayMs };\n\t\t},\n\n\t\t// Original method for backward compatibility\n\t\tscheduleTask: (c, delayMs: number) => {\n\t\t\tconst timestamp = Date.now() + delayMs;\n\t\t\tc.schedule.at(timestamp, \"onScheduledTask\");\n\t\t\treturn timestamp;\n\t\t},\n\n\t\t// Getters for state\n\t\tgetLastRun: (c) => {\n\t\t\treturn c.state.lastRun;\n\t\t},\n\n\t\tgetScheduledCount: (c) => {\n\t\t\treturn c.state.scheduledCount;\n\t\t},\n\n\t\tgetTaskHistory: (c) => {\n\t\t\treturn c.state.taskHistory;\n\t\t},\n\n\t\tclearHistory: (c) => {\n\t\t\tc.state.taskHistory = [];\n\t\t\tc.state.scheduledCount = 0;\n\t\t\tc.state.lastRun = 0;\n\t\t\treturn true;\n\t\t},\n\n\t\t// Scheduled task handlers\n\t\tonScheduledTask: (c) => {\n\t\t\tc.state.lastRun = Date.now();\n\t\t\tc.state.scheduledCount++;\n\t\t\tc.broadcast(\"scheduled\", {\n\t\t\t\ttime: c.state.lastRun,\n\t\t\t\tcount: c.state.scheduledCount,\n\t\t\t});\n\t\t},\n\n\t\tonScheduledTaskWithId: (c, taskId: string) => {\n\t\t\tc.state.lastRun = Date.now();\n\t\t\tc.state.scheduledCount++;\n\t\t\tc.state.taskHistory.push(taskId);\n\t\t\tc.broadcast(\"scheduledWithId\", {\n\t\t\t\ttaskId,\n\t\t\t\ttime: c.state.lastRun,\n\t\t\t\tcount: c.state.scheduledCount,\n\t\t\t});\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\nexport const destroyObserver = actor({\n\tstate: { destroyedActors: [] as string[] },\n\tactions: {\n\t\tnotifyDestroyed: (c, actorKey: string) => {\n\t\t\tc.state.destroyedActors.push(actorKey);\n\t\t},\n\t\twasDestroyed: (c, actorKey: string) => {\n\t\t\treturn c.state.destroyedActors.includes(actorKey);\n\t\t},\n\t\treset: (c) => {\n\t\t\tc.state.destroyedActors = [];\n\t\t},\n\t},\n});\n\nexport const destroyActor = actor({\n\tstate: { value: 0, key: \"\" },\n\tonWake: (c) => {\n\t\t// Store the actor key so we can reference it in onDestroy\n\t\tc.state.key = c.key.join(\"/\");\n\t},\n\tonDestroy: async (c) => {\n\t\tconst client = c.client();\n\t\tconst observer = client.destroyObserver.getOrCreate([\"observer\"]);\n\t\tawait observer.notifyDestroyed(c.state.key);\n\t},\n\tactions: {\n\t\tsetValue: async (c, newValue: number) => {\n\t\t\tc.state.value = newValue;\n\t\t\tawait c.saveState({ immediate: true });\n\t\t\treturn c.state.value;\n\t\t},\n\t\tgetValue: (c) => {\n\t\t\treturn c.state.value;\n\t\t},\n\t\tdestroy: (c) => {\n\t\t\tc.destroy();\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const HIBERNATION_SLEEP_TIMEOUT = 500;\n\nexport type HibernationConnState = {\n\tcount: number;\n\tconnectCount: number;\n\tdisconnectCount: number;\n};\n\nexport const hibernationActor = actor({\n\tstate: {\n\t\tsleepCount: 0,\n\t\twakeCount: 0,\n\t},\n\tcreateConnState: (c): HibernationConnState => {\n\t\treturn {\n\t\t\tcount: 0,\n\t\t\tconnectCount: 0,\n\t\t\tdisconnectCount: 0,\n\t\t};\n\t},\n\tonWake: (c) => {\n\t\tc.state.wakeCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonConnect: (c, conn) => {\n\t\tconn.state.connectCount += 1;\n\t},\n\tonDisconnect: (c, conn) => {\n\t\tconn.state.disconnectCount += 1;\n\t},\n\tactions: {\n\t\t// Basic RPC that returns a simple value\n\t\tping: (c) => {\n\t\t\treturn \"pong\";\n\t\t},\n\t\t// Increment the connection's count\n\t\tconnIncrement: (c) => {\n\t\t\tc.conn.state.count += 1;\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\t// Get the connection's count\n\t\tgetConnCount: (c) => {\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\t// Get the connection's lifecycle counts\n\t\tgetConnLifecycleCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tconnectCount: c.conn.state.connectCount,\n\t\t\t\tdisconnectCount: c.conn.state.disconnectCount,\n\t\t\t};\n\t\t},\n\t\t// Get all connection IDs\n\t\tgetConnectionIds: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.map((x) => x[0])\n\t\t\t\t.toArray();\n\t\t},\n\t\t// Get actor sleep/wake counts\n\t\tgetActorCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t};\n\t\t},\n\t\t// Trigger sleep\n\t\ttriggerSleep: (c) => {\n\t\t\tc.sleep();\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: HIBERNATION_SLEEP_TIMEOUT,\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\n\nexport interface WorkerJob {\n\tid: string;\n\tpayload: string;\n}\n\nexport interface WorkerState {\n\tstatus: \"idle\" | \"running\";\n\tprocessed: number;\n\tlastJob: WorkerJob | null;\n}\n\nexport const worker = actor({\n\tstate: {\n\t\tstatus: \"idle\" as \"idle\" | \"running\",\n\t\tprocessed: 0,\n\t\tlastJob: null as WorkerJob | null,\n\t},\n\tevents: {\n\t\tstatusChanged: event<{ status: \"idle\" | \"running\"; processed: number }>(),\n\t\tjobProcessed: event<{ processed: number; job: WorkerJob }>(),\n\t},\n\tqueues: {\n\t\tjobs: queue(),\n\t},\n\tasync run(c) {\n\t\tc.state.status = \"running\";\n\t\tc.broadcast(\"statusChanged\", {\n\t\t\tstatus: c.state.status,\n\t\t\tprocessed: c.state.processed,\n\t\t});\n\n\t\tfor await (const job of c.queue.iter()) {\n\t\t\tc.state.processed += 1;\n\t\t\tc.state.lastJob = job.body;\n\t\t\tc.broadcast(\"jobProcessed\", {\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tjob: job.body,\n\t\t\t});\n\t\t}\n\n\t\tc.state.status = \"idle\";\n\t},\n\tactions: {\n\t\tgetState(c): WorkerState {\n\t\t\treturn {\n\t\t\t\tstatus: c.state.status,\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tlastJob: c.state.lastJob,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\n\nconst DEFAULT_TIMEOUT_MS = 2_000;\n\nexport interface WorkerTimeoutJob {\n\tid: string;\n\tpayload: string;\n}\n\nexport interface WorkerTimeoutState {\n\tstatus: \"idle\" | \"running\";\n\tprocessed: number;\n\tticks: number;\n\tlastTickAt: number | null;\n\tlastJob: WorkerTimeoutJob | null;\n\ttimeoutMs: number;\n}\n\nexport const workerTimeout = actor({\n\tstate: {\n\t\tstatus: \"idle\" as \"idle\" | \"running\",\n\t\tprocessed: 0,\n\t\tticks: 0,\n\t\tlastTickAt: null as number | null,\n\t\tlastJob: null as WorkerTimeoutJob | null,\n\t\ttimeoutMs: DEFAULT_TIMEOUT_MS,\n\t},\n\tevents: {\n\t\ttick: event<{ ticks: number; at: number }>(),\n\t\tjobProcessed: event<{ processed: number; job: WorkerTimeoutJob }>(),\n\t},\n\tqueues: {\n\t\tjobs: queue(),\n\t},\n\trun: async (c) => {\n\t\tc.state.status = \"running\";\n\n\t\twhile (!c.aborted) {\n\t\t\tconst message = await c.queue.next({\n\t\t\t\tnames: [\"jobs\"],\n\t\t\t\ttimeout: c.state.timeoutMs,\n\t\t\t});\n\n\t\t\tif (!message) {\n\t\t\t\tconst at = Date.now();\n\t\t\t\tc.state.ticks += 1;\n\t\t\t\tc.state.lastTickAt = at;\n\t\t\t\tc.broadcast(\"tick\", {\n\t\t\t\t\tticks: c.state.ticks,\n\t\t\t\t\tat,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tc.state.processed += 1;\n\t\t\tc.state.lastJob = message.body;\n\t\t\tc.broadcast(\"jobProcessed\", {\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tjob: message.body,\n\t\t\t});\n\t\t}\n\n\t\tc.state.status = \"idle\";\n\t},\n\tactions: {\n\t\tenqueueJob: async (c, payload: string) => {\n\t\t\tconst job = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tpayload,\n\t\t\t};\n\t\t\tawait c.queue.send(\"jobs\", job);\n\t\t\treturn job;\n\t\t},\n\t\tsetTimeoutMs: (c, timeoutMs: number) => {\n\t\t\tc.state.timeoutMs = Math.max(100, Math.floor(timeoutMs));\n\t\t\treturn c.state.timeoutMs;\n\t\t},\n\t\tgetState: (c): WorkerTimeoutState => c.state,\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\nimport { Loop, type WorkflowLoopContextOf, workflow } from \"rivetkit/workflow\";\n\nconst WORKFLOW_GUARD_KV_KEY = \"__rivet_actor_workflow_guard_triggered\";\n\nconst WORKFLOW_QUEUE_NAME = \"workflow-default\";\nconst WORKFLOW_TIMEOUT_QUEUE_NAME = \"workflow-timeout\";\n\nexport const workflowCounterActor = actor({\n\tstate: {\n\t\trunCount: 0,\n\t\tguardTriggered: false,\n\t\thistory: [] as number[],\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"counter\", async (loopCtx) => {\n\t\t\t\ttry {\n\t\t\t\t\t// Accessing state outside a step should throw.\n\t\t\t\t\t// biome-ignore lint/style/noUnusedExpressions: intentionally checking accessor.\n\t\t\t\t\tloopCtx.state;\n\t\t\t\t} catch {}\n\n\t\t\t\tawait loopCtx.step(\"increment\", async () => {\n\t\t\t\t\tincrementWorkflowCounter(loopCtx);\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.sleep(\"idle\", 25);\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t});\n\t}),\n\tactions: {\n\t\tgetState: async (c) => {\n\t\t\tconst guardFlag = await c.kv.get(WORKFLOW_GUARD_KV_KEY);\n\t\t\tif (guardFlag === \"true\") {\n\t\t\t\tc.state.guardTriggered = true;\n\t\t\t}\n\t\t\treturn c.state;\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: 50,\n\t},\n});\n\nexport const workflowQueueActor = actor({\n\tstate: {\n\t\treceived: [] as unknown[],\n\t},\n\tqueues: {\n\t\t[WORKFLOW_QUEUE_NAME]: queue(),\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"queue\", async (loopCtx) => {\n\t\t\t\tconst message = await loopCtx.queue.next(\"queue-wait\", {\n\t\t\t\t\tnames: [WORKFLOW_QUEUE_NAME],\n\t\t\t\t\tcompletable: true,\n\t\t\t\t});\n\t\t\t\tif (!message.complete) {\n\t\t\t\t\treturn Loop.continue(undefined);\n\t\t\t\t}\n\t\t\t\tconst complete = message.complete;\n\t\t\t\tawait loopCtx.step(\"store-message\", async () => {\n\t\t\t\t\tawait storeWorkflowQueueMessage(loopCtx, message.body, complete);\n\t\t\t\t});\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t});\n\t}),\n\tactions: {\n\t\tgetMessages: (c) => c.state.received,\n\t},\n});\n\nexport const workflowSleepActor = actor({\n\tstate: {\n\t\tticks: 0,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"sleep\", async (loopCtx) => {\n\t\t\t\tawait loopCtx.step(\"tick\", async () => {\n\t\t\t\t\tincrementWorkflowSleepTick(loopCtx);\n\t\t\t\t});\n\t\t\t\tawait loopCtx.sleep(\"delay\", 40);\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t});\n\t}),\n\tactions: {\n\t\tgetState: (c) => c.state,\n\t},\n\toptions: {\n\t\tsleepTimeout: 50,\n\t},\n});\n\nexport const workflowQueueTimeoutActor = actor({\n\tstate: {\n\t\tprocessed: 0,\n\t\tticks: 0,\n\t\tlastTickAt: null as number | null,\n\t\tlastJob: null as { id: string; payload: string } | null,\n\t\ttimeoutMs: 2_000,\n\t},\n\tevents: {\n\t\ttick: event<{ ticks: number; at: number }>(),\n\t\tjobProcessed: event<{ processed: number; job: { id: string; payload: string } }>(),\n\t},\n\tqueues: {\n\t\t[WORKFLOW_TIMEOUT_QUEUE_NAME]: queue<{ id: string; payload: string }>(),\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"queue-timeout-loop\", async (loopCtx) => {\n\t\t\t\tconst timeoutMs = await loopCtx.step(\"read-timeout\", async () => {\n\t\t\t\t\treturn readWorkflowTimeoutMs(loopCtx);\n\t\t\t\t});\n\n\t\t\t\tconst [message] = await loopCtx.queue.nextBatch(\"wait-job-or-timeout\", {\n\t\t\t\t\tnames: [WORKFLOW_TIMEOUT_QUEUE_NAME],\n\t\t\t\t\ttimeout: timeoutMs,\n\t\t\t\t});\n\n\t\t\t\tif (!message) {\n\t\t\t\t\tawait loopCtx.step(\"tick\", async () => {\n\t\t\t\t\t\trecordWorkflowTimeoutTick(loopCtx);\n\t\t\t\t\t});\n\t\t\t\t\treturn Loop.continue(undefined);\n\t\t\t\t}\n\n\t\t\t\tawait loopCtx.step(\"process-job\", async () => {\n\t\t\t\t\tprocessWorkflowTimeoutJob(loopCtx, message.body);\n\t\t\t\t});\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t});\n\t}),\n\tactions: {\n\t\tenqueueJob: async (c, payload: string) => {\n\t\t\tconst job = { id: crypto.randomUUID(), payload };\n\t\t\tawait c.queue.send(WORKFLOW_TIMEOUT_QUEUE_NAME, job);\n\t\t\treturn job;\n\t\t},\n\t\tsetTimeoutMs: (c, timeoutMs: number) => {\n\t\t\tc.state.timeoutMs = Math.max(100, Math.floor(timeoutMs));\n\t\t\treturn c.state.timeoutMs;\n\t\t},\n\t\tgetState: (c) => c.state,\n\t},\n});\n\nfunction incrementWorkflowCounter(\n\tctx: WorkflowLoopContextOf,\n): void {\n\tctx.state.runCount += 1;\n\tctx.state.history.push(ctx.state.runCount);\n}\n\nasync function storeWorkflowQueueMessage(\n\tctx: WorkflowLoopContextOf,\n\tbody: unknown,\n\tcomplete: (response: { echo: unknown }) => Promise,\n): Promise {\n\tctx.state.received.push(body);\n\tawait complete({ echo: body });\n}\n\nfunction incrementWorkflowSleepTick(\n\tctx: WorkflowLoopContextOf,\n): void {\n\tctx.state.ticks += 1;\n}\n\nfunction readWorkflowTimeoutMs(\n\tctx: WorkflowLoopContextOf,\n): number {\n\treturn ctx.state.timeoutMs;\n}\n\nfunction recordWorkflowTimeoutTick(\n\tctx: WorkflowLoopContextOf,\n): void {\n\tconst at = Date.now();\n\tctx.state.ticks += 1;\n\tctx.state.lastTickAt = at;\n\tctx.broadcast(\"tick\", {\n\t\tticks: ctx.state.ticks,\n\t\tat,\n\t});\n}\n\nfunction processWorkflowTimeoutJob(\n\tctx: WorkflowLoopContextOf,\n\tjob: { id: string; payload: string },\n): void {\n\tctx.state.processed += 1;\n\tctx.state.lastJob = job;\n\tctx.broadcast(\"jobProcessed\", {\n\t\tprocessed: ctx.state.processed,\n\t\tjob,\n\t});\n}\n\nexport { WORKFLOW_QUEUE_NAME, WORKFLOW_TIMEOUT_QUEUE_NAME };\n","// TIMER (Sleep Demo)\n// Demonstrates: Durable sleep that survives restarts\n// One actor per timer - actor key is the timer ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type Timer = {\n\tid: string;\n\tname: string;\n\tdurationMs: number;\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\ntype State = Timer;\n\nexport type TimerInput = {\n\tname?: string;\n\tdurationMs?: number;\n};\n\nexport const timer = actor({\n\tcreateState: (c, input?: TimerInput): Timer => ({\n\t\tid: c.key[0] as string,\n\t\tname: input?.name ?? \"Timer\",\n\t\tdurationMs: input?.durationMs ?? 10000,\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\ttimerStarted: event(),\n\t\ttimerCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTimer: (c): Timer => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"timer-loop\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\t// Get duration inside a step since state is only available in steps\n\t\t\t\tconst durationMs = await loopCtx.step(\"start-timer\", async () => {\n\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\tmsg: \"starting timer\",\n\t\t\t\t\t\ttimerId: c.state.id,\n\t\t\t\t\t\tdurationMs: c.state.durationMs,\n\t\t\t\t\t});\n\t\t\t\t\tc.broadcast(\"timerStarted\", c.state);\n\t\t\t\t\treturn c.state.durationMs;\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.sleep(\"countdown\", durationMs);\n\n\t\t\t\tawait loopCtx.step(\"complete-timer\", async () => {\n\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\tc.broadcast(\"timerCompleted\", c.state);\n\t\t\t\t\tctx.log.info({ msg: \"timer completed\", timerId: c.state.id });\n\t\t\t\t});\n\n\t\t\t\treturn Loop.break(undefined);\n\t\t\t});\n\t}),\n\n\toptions: {\n\t\tsleepTimeout: 1000,\n\t},\n});\n","// Type helper - cast loop context to access actor-specific properties\n// Only call these helpers INSIDE a step callback where state access is allowed\n// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround\nexport type ActorLoopContext = {\n\tstate: S;\n\tbroadcast: (name: string, ...args: unknown[]) => void;\n};\n\n// biome-ignore lint/suspicious/noExplicitAny: Workflow context typing workaround\nexport function actorCtx(ctx: unknown): ActorLoopContext {\n\treturn ctx as any;\n}\n","// ORDER PROCESSOR (Steps Demo)\n// Demonstrates: Sequential workflow steps with automatic retries\n// One actor per order - actor key is the order ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type OrderStatus =\n\t| \"pending\"\n\t| \"validating\"\n\t| \"charging\"\n\t| \"fulfilling\"\n\t| \"completed\"\n\t| \"failed\";\n\nexport type Order = {\n\tid: string;\n\tstatus: OrderStatus;\n\tstep: number;\n\terror?: string;\n\tcreatedAt: number;\n\tcompletedAt?: number;\n};\n\ntype State = Order;\n\nasync function simulateWork(name: string, failChance = 0.1): Promise {\n\tawait new Promise((resolve) =>\n\t\tsetTimeout(resolve, 500 + Math.random() * 1000)\n\t);\n\tif (Math.random() < failChance) {\n\t\tthrow new Error(`${name} failed (simulated)`);\n\t}\n}\n\nexport const order = actor({\n\tcreateState: (c): Order => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t\tstep: 0,\n\t\tcreatedAt: Date.now(),\n\t}),\n\tevents: {\n\t\torderUpdated: event(),\n\t},\n\n\tactions: {\n\t\tgetOrder: (c): Order => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"process-order\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\tawait loopCtx.step(\"validate\", async () => {\n\t\t\t\t\tctx.log.info({ msg: \"processing order\", orderId: c.state.id });\n\t\t\t\t\tc.state.status = \"validating\";\n\t\t\t\t\tc.state.step = 1;\n\t\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\t\tawait simulateWork(\"validation\", 0.05);\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(\"charge\", async () => {\n\t\t\t\t\tc.state.status = \"charging\";\n\t\t\t\t\tc.state.step = 2;\n\t\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\t\tawait simulateWork(\"payment\", 0.1);\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(\"fulfill\", async () => {\n\t\t\t\t\tc.state.status = \"fulfilling\";\n\t\t\t\t\tc.state.step = 3;\n\t\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\t\tawait simulateWork(\"fulfillment\", 0.05);\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(\"complete\", async () => {\n\t\t\t\t\tc.state.status = \"completed\";\n\t\t\t\t\tc.state.step = 4;\n\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\t\tctx.log.info({ msg: \"order completed\", orderId: c.state.id });\n\t\t\t\t});\n\n\t\t\t\treturn Loop.break(undefined);\n\t\t\t});\n\t}),\n});\n","// BATCH PROCESSOR (Loops Demo)\n// Demonstrates: Loop with persistent state (cursor) for batch processing\n// One actor per batch job - actor key is the job ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type BatchInfo = {\n\tid: number;\n\tcount: number;\n\tprocessedAt: number;\n};\n\nexport type BatchJob = {\n\tid: string;\n\ttotalItems: number;\n\tbatchSize: number;\n\tstatus: \"running\" | \"stopped\" | \"completed\";\n\tprocessedTotal: number;\n\tcurrentBatch: number;\n\tbatches: BatchInfo[];\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\ntype State = BatchJob;\n\nfunction fetchBatch(\n\tcursor: number,\n\tbatchSize: number,\n\ttotalItems: number\n): { items: number[]; hasMore: boolean } {\n\tconst start = cursor * batchSize;\n\tconst end = Math.min(start + batchSize, totalItems);\n\tconst items = [];\n\tfor (let i = start; i < end; i++) {\n\t\titems.push(i);\n\t}\n\treturn {\n\t\titems,\n\t\thasMore: end < totalItems,\n\t};\n}\n\nexport type BatchJobInput = {\n\ttotalItems?: number;\n\tbatchSize?: number;\n};\n\nexport const batch = actor({\n\tcreateState: (c, input?: BatchJobInput): BatchJob => ({\n\t\tid: c.key[0] as string,\n\t\ttotalItems: input?.totalItems ?? 50,\n\t\tbatchSize: input?.batchSize ?? 5,\n\t\tstatus: \"running\",\n\t\tprocessedTotal: 0,\n\t\tcurrentBatch: 0,\n\t\tbatches: [],\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\tbatchProcessed: event(),\n\t\tstateChanged: event(),\n\t\tprocessingComplete: event<{ totalBatches: number; totalItems: number }>(),\n\t},\n\n\tactions: {\n\t\tgetJob: (c): BatchJob => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop({\n\t\t\tname: \"batch-loop\",\n\t\t\tstate: { cursor: 0 },\n\t\t\trun: async (batchCtx, loopState: { cursor: number }) => {\n\t\t\t\tconst c = actorCtx(batchCtx);\n\n\t\t\t\tconst batch = await batchCtx.step(\"fetch-batch\", async () => {\n\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\tmsg: \"processing batch\",\n\t\t\t\t\t\tjobId: c.state.id,\n\t\t\t\t\t\tcursor: loopState.cursor,\n\t\t\t\t\t});\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 200 + Math.random() * 300));\n\t\t\t\t\treturn fetchBatch(loopState.cursor, c.state.batchSize, c.state.totalItems);\n\t\t\t\t});\n\n\t\t\t\tawait batchCtx.step(\"process-batch\", async () => {\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 300 + Math.random() * 500));\n\n\t\t\t\t\tconst batchInfo: BatchInfo = {\n\t\t\t\t\t\tid: loopState.cursor,\n\t\t\t\t\t\tcount: batch.items.length,\n\t\t\t\t\t\tprocessedAt: Date.now(),\n\t\t\t\t\t};\n\n\t\t\t\t\tc.state.currentBatch = loopState.cursor;\n\t\t\t\t\tc.state.processedTotal += batch.items.length;\n\t\t\t\t\tc.state.batches.push(batchInfo);\n\n\t\t\t\t\tc.broadcast(\"batchProcessed\", batchInfo);\n\t\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\n\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\tmsg: \"batch processed\",\n\t\t\t\t\t\tjobId: c.state.id,\n\t\t\t\t\t\tcursor: loopState.cursor,\n\t\t\t\t\t\tcount: batch.items.length,\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tif (!batch.hasMore) {\n\t\t\t\t\tawait batchCtx.step(\"mark-complete\", async () => {\n\t\t\t\t\t\tc.state.status = \"completed\";\n\t\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\t\t\tc.broadcast(\"processingComplete\", {\n\t\t\t\t\t\t\ttotalBatches: loopState.cursor + 1,\n\t\t\t\t\t\t\ttotalItems: c.state.processedTotal,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\treturn Loop.break(loopState.cursor + 1);\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ cursor: loopState.cursor + 1 });\n\t\t\t},\n\t\t});\n\t}),\n});\n","// APPROVAL REQUEST (Queue Wait Demo)\n// Demonstrates: Queue waits with timeout for approval workflows\n// One actor per approval request - actor key is the request ID\n\nimport { actor, event, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type RequestStatus = \"pending\" | \"approved\" | \"rejected\" | \"timeout\";\n\nexport type ApprovalRequest = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\tstatus: RequestStatus;\n\tcreatedAt: number;\n\tdecidedAt?: number;\n\tdecidedBy?: string;\n\tdeciding?: boolean; // True when a decision is being processed\n};\n\ntype State = ApprovalRequest;\n\nconst QUEUE_DECISION = \"decision\" as const;\n\nconst APPROVAL_TIMEOUT_MS = 30000;\n\nexport type ApprovalRequestInput = {\n\ttitle?: string;\n\tdescription?: string;\n};\n\ntype ApprovalDecision = {\n\tapproved: boolean;\n\tapprover: string;\n};\n\nexport const approval = actor({\n\tcreateState: (c, input?: ApprovalRequestInput): ApprovalRequest => ({\n\t\tid: c.key[0] as string,\n\t\ttitle: input?.title ?? \"Untitled Request\",\n\t\tdescription: input?.description ?? \"\",\n\t\tstatus: \"pending\",\n\t\tcreatedAt: Date.now(),\n\t}),\n\tqueues: {\n\t\tdecision: queue(),\n\t},\n\tevents: {\n\t\trequestUpdated: event(),\n\t\trequestCreated: event(),\n\t},\n\n\tactions: {\n\t\tgetRequest: (c): ApprovalRequest => c.state,\n\n\t\tapprove: async (c, approver: string) => {\n\t\t\tif (c.state.status !== \"pending\") return;\n\t\t\tc.state.deciding = true;\n\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\tawait c.queue.send(QUEUE_DECISION, { approved: true, approver });\n\t\t},\n\n\t\treject: async (c, approver: string) => {\n\t\t\tif (c.state.status !== \"pending\") return;\n\t\t\tc.state.deciding = true;\n\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\tawait c.queue.send(QUEUE_DECISION, { approved: false, approver });\n\t\t},\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"approval-loop\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\tawait loopCtx.step(\"init-request\", async () => {\n\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\tmsg: \"waiting for approval decision\",\n\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t\ttitle: c.state.title,\n\t\t\t\t\t});\n\t\t\t\t\tc.broadcast(\"requestCreated\", c.state);\n\t\t\t\t});\n\n\t\t\t\tconst [decisionMessage] = await loopCtx.queue.nextBatch(\n\t\t\t\t\t\"wait-decision\",\n\t\t\t\t\t{\n\t\t\t\t\t\tnames: [QUEUE_DECISION],\n\t\t\t\t\t\ttimeout: APPROVAL_TIMEOUT_MS,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tconst decision = decisionMessage?.body ?? null;\n\n\t\t\t\tawait loopCtx.step(\"update-status\", async () => {\n\t\t\t\t\tc.state.deciding = false;\n\t\t\t\t\tif (decision === null) {\n\t\t\t\t\t\tc.state.status = \"timeout\";\n\t\t\t\t\t\tctx.log.info({ msg: \"request timed out\", requestId: c.state.id });\n\t\t\t\t\t} else if (decision.approved) {\n\t\t\t\t\t\tc.state.status = \"approved\";\n\t\t\t\t\t\tc.state.decidedBy = decision.approver;\n\t\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\t\tmsg: \"request approved\",\n\t\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t\t\tapprover: decision.approver,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tc.state.status = \"rejected\";\n\t\t\t\t\t\tc.state.decidedBy = decision.approver;\n\t\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\t\tmsg: \"request rejected\",\n\t\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t\t\tapprover: decision.approver,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tc.state.decidedAt = Date.now();\n\t\t\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\t\t});\n\n\t\t\t\treturn Loop.break(undefined);\n\t\t\t});\n\t}),\n});\n","// DASHBOARD (Join Demo)\n// Demonstrates: Parallel data fetching with join (wait-all)\n\nimport { actor, event, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type UserStats = {\n\tcount: number;\n\tactiveToday: number;\n\tnewThisWeek: number;\n};\n\nexport type OrderStats = {\n\tcount: number;\n\trevenue: number;\n\tavgOrderValue: number;\n};\n\nexport type MetricsStats = {\n\tpageViews: number;\n\tsessions: number;\n\tbounceRate: number;\n};\n\nexport type DashboardData = {\n\tusers: UserStats;\n\torders: OrderStats;\n\tmetrics: MetricsStats;\n\tfetchedAt: number;\n};\n\nexport type BranchStatus = \"pending\" | \"running\" | \"completed\" | \"failed\";\n\nexport type DashboardState = {\n\tdata: DashboardData | null;\n\tloading: boolean;\n\tbranches: {\n\t\tusers: BranchStatus;\n\t\torders: BranchStatus;\n\t\tmetrics: BranchStatus;\n\t};\n\tlastRefresh: number | null;\n};\n\ntype State = DashboardState;\n\nconst QUEUE_REFRESH = \"refresh\";\ntype RefreshMessage = Record;\n\nasync function fetchUserStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 800 + Math.random() * 1200));\n\treturn {\n\t\tcount: Math.floor(1000 + Math.random() * 500),\n\t\tactiveToday: Math.floor(100 + Math.random() * 200),\n\t\tnewThisWeek: Math.floor(20 + Math.random() * 80),\n\t};\n}\n\nasync function fetchOrderStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 600 + Math.random() * 1000));\n\tconst count = Math.floor(50 + Math.random() * 150);\n\tconst revenue = Math.floor(5000 + Math.random() * 15000);\n\treturn {\n\t\tcount,\n\t\trevenue,\n\t\tavgOrderValue: Math.round(revenue / count),\n\t};\n}\n\nasync function fetchMetricsStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 400 + Math.random() * 800));\n\treturn {\n\t\tpageViews: Math.floor(10000 + Math.random() * 50000),\n\t\tsessions: Math.floor(2000 + Math.random() * 8000),\n\t\tbounceRate: Math.round(30 + Math.random() * 40),\n\t};\n}\n\nexport const dashboard = actor({\n\tstate: {\n\t\tdata: null as DashboardData | null,\n\t\tloading: false,\n\t\tbranches: {\n\t\t\tusers: \"pending\" as BranchStatus,\n\t\t\torders: \"pending\" as BranchStatus,\n\t\t\tmetrics: \"pending\" as BranchStatus,\n\t\t},\n\t\tlastRefresh: null as number | null,\n\t},\n\tqueues: {\n\t\t[QUEUE_REFRESH]: queue(),\n\t},\n\tevents: {\n\t\tstateChanged: event(),\n\t\trefreshComplete: event(),\n\t},\n\n\tactions: {\n\t\trefresh: async (c) => {\n\t\t\tif (!c.state.loading) {\n\t\t\t\tc.state.loading = true;\n\t\t\t\tc.state.branches = {\n\t\t\t\t\tusers: \"pending\",\n\t\t\t\t\torders: \"pending\",\n\t\t\t\t\tmetrics: \"pending\",\n\t\t\t\t};\n\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\tawait c.queue.send(QUEUE_REFRESH, {});\n\t\t\t}\n\t\t},\n\n\t\tgetState: (c): DashboardState => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"refresh-loop\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\tawait loopCtx.queue.next(\"wait-refresh\", {\n\t\t\t\t\tnames: [QUEUE_REFRESH],\n\t\t\t\t});\n\n\t\t\t\tctx.log.info({ msg: \"starting dashboard refresh\" });\n\n\t\t\t\tconst results = await loopCtx.join(\"fetch-all\", {\n\t\t\t\t\tusers: {\n\t\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\t\tconst bc = actorCtx(branchCtx);\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.users = \"running\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst data = await branchCtx.step(\"fetch-users\", async () => {\n\t\t\t\t\t\t\t\treturn await fetchUserStats();\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.users = \"completed\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn data;\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\torders: {\n\t\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\t\tconst bc = actorCtx(branchCtx);\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.orders = \"running\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst data = await branchCtx.step(\"fetch-orders\", async () => {\n\t\t\t\t\t\t\t\treturn await fetchOrderStats();\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.orders = \"completed\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn data;\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tmetrics: {\n\t\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\t\tconst bc = actorCtx(branchCtx);\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.metrics = \"running\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst data = await branchCtx.step(\"fetch-metrics\", async () => {\n\t\t\t\t\t\t\t\treturn await fetchMetricsStats();\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async () => {\n\t\t\t\t\t\t\t\tbc.state.branches.metrics = \"completed\";\n\t\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\treturn data;\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(\"save-data\", async () => {\n\t\t\t\t\tc.state.data = {\n\t\t\t\t\t\tusers: results.users,\n\t\t\t\t\t\torders: results.orders,\n\t\t\t\t\t\tmetrics: results.metrics,\n\t\t\t\t\t\tfetchedAt: Date.now(),\n\t\t\t\t\t};\n\t\t\t\t\tc.state.loading = false;\n\t\t\t\t\tc.state.lastRefresh = Date.now();\n\t\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\t\tc.broadcast(\"refreshComplete\", c.state.data);\n\t\t\t\t});\n\n\t\t\t\tctx.log.info({ msg: \"dashboard refresh complete\" });\n\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t});\n\t}),\n});\n","// RACE RUNNER (Race Demo)\n// Demonstrates: Race (parallel first-wins) for timeout patterns\n// One actor per race task - actor key is the task ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type RaceTask = {\n\tid: string;\n\tworkDurationMs: number;\n\ttimeoutMs: number;\n\tstatus: \"running\" | \"work_won\" | \"timeout_won\";\n\tresult?: string;\n\tstartedAt: number;\n\tcompletedAt?: number;\n\tactualDurationMs?: number;\n};\n\ntype State = RaceTask;\n\nexport type RaceTaskInput = {\n\tworkDurationMs?: number;\n\ttimeoutMs?: number;\n};\n\nexport const race = actor({\n\tcreateState: (c, input?: RaceTaskInput): RaceTask => ({\n\t\tid: c.key[0] as string,\n\t\tworkDurationMs: input?.workDurationMs ?? 2000,\n\t\ttimeoutMs: input?.timeoutMs ?? 3000,\n\t\tstatus: \"running\",\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\traceStarted: event(),\n\t\traceCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTask: (c): RaceTask => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"race-loop\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\t// Get durations inside a step since state is only available in steps\n\t\t\t\tconst { workDurationMs, timeoutMs, taskId } = await loopCtx.step(\n\t\t\t\t\t\"start-race\",\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\t\tmsg: \"starting race\",\n\t\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\t\tworkDurationMs: c.state.workDurationMs,\n\t\t\t\t\t\t\ttimeoutMs: c.state.timeoutMs,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tc.broadcast(\"raceStarted\", c.state);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tworkDurationMs: c.state.workDurationMs,\n\t\t\t\t\t\t\ttimeoutMs: c.state.timeoutMs,\n\t\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t\tconst { winner, value } = await loopCtx.race(\"work-vs-timeout\", [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"work\",\n\t\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\t\tawait branchCtx.sleep(\"simulate-work\", workDurationMs);\n\t\t\t\t\t\t\treturn await branchCtx.step(\"complete-work\", async () => {\n\t\t\t\t\t\t\t\treturn `Result for task ${taskId}`;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"timeout\",\n\t\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\t\tawait branchCtx.sleep(\"timeout-wait\", timeoutMs);\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t]);\n\n\t\t\t\tawait loopCtx.step(\"save-result\", async () => {\n\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\tc.state.actualDurationMs = c.state.completedAt - c.state.startedAt;\n\n\t\t\t\t\tif (winner === \"work\") {\n\t\t\t\t\t\tc.state.status = \"work_won\";\n\t\t\t\t\t\tc.state.result = value as string;\n\t\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\t\tmsg: \"work completed before timeout\",\n\t\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\t\tdurationMs: c.state.actualDurationMs,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tc.state.status = \"timeout_won\";\n\t\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\t\tmsg: \"timeout won the race\",\n\t\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\t\tdurationMs: c.state.actualDurationMs,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tc.broadcast(\"raceCompleted\", c.state);\n\t\t\t\t});\n\n\t\t\t\treturn Loop.break(undefined);\n\t\t\t});\n\t}),\n});\n","// PAYMENT PROCESSOR (Rollback Demo)\n// Demonstrates: Rollback checkpoints with compensating actions\n// One actor per transaction - actor key is the transaction ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nexport type TransactionStep = {\n\tname: string;\n\tstatus: \"pending\" | \"completed\" | \"rolled_back\";\n\tcompletedAt?: number;\n\trolledBackAt?: number;\n};\n\nexport type Transaction = {\n\tid: string;\n\tamount: number;\n\tshouldFail: boolean;\n\tstatus:\n\t\t| \"pending\"\n\t\t| \"reserving\"\n\t\t| \"charging\"\n\t\t| \"completing\"\n\t\t| \"completed\"\n\t\t| \"rolling_back\"\n\t\t| \"failed\";\n\tsteps: TransactionStep[];\n\terror?: string;\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\ntype State = Transaction;\n\nexport type TransactionInput = {\n\tamount?: number;\n\tshouldFail?: boolean;\n};\n\nexport const payment = actor({\n\tcreateState: (c, input?: TransactionInput): Transaction => ({\n\t\tid: c.key[0] as string,\n\t\tamount: input?.amount ?? 100,\n\t\tshouldFail: input?.shouldFail ?? false,\n\t\tstatus: \"pending\",\n\t\tsteps: [\n\t\t\t{ name: \"reserve-inventory\", status: \"pending\" },\n\t\t\t{ name: \"charge-card\", status: \"pending\" },\n\t\t\t{ name: \"complete-order\", status: \"pending\" },\n\t\t],\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\ttransactionStarted: event(),\n\t\ttransactionUpdated: event(),\n\t\ttransactionCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTransaction: (c): Transaction => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"payment-loop\", async (loopCtx) => {\n\t\t\t\tconst c = actorCtx(loopCtx);\n\n\t\t\t\tawait loopCtx.step(\"init-payment\", async () => {\n\t\t\t\t\tctx.log.info({\n\t\t\t\t\t\tmsg: \"starting payment processing\",\n\t\t\t\t\t\ttxId: c.state.id,\n\t\t\t\t\t\tamount: c.state.amount,\n\t\t\t\t\t\tshouldFail: c.state.shouldFail,\n\t\t\t\t\t});\n\t\t\t\t\tc.broadcast(\"transactionStarted\", c.state);\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.rollbackCheckpoint(\"payment-checkpoint\");\n\n\t\t\t\t// Step 1: Reserve inventory\n\t\t\t\tawait loopCtx.step({\n\t\t\t\t\tname: \"reserve-inventory\",\n\t\t\t\t\trun: async () => {\n\t\t\t\t\t\tc.state.status = \"reserving\";\n\t\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t\t(s) => s.name === \"reserve-inventory\"\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (step) {\n\t\t\t\t\t\t\tstep.status = \"completed\";\n\t\t\t\t\t\t\tstep.completedAt = Date.now();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\t\tsetTimeout(r, 500 + Math.random() * 500)\n\t\t\t\t\t\t);\n\t\t\t\t\t\tctx.log.info({ msg: \"inventory reserved\", txId: c.state.id });\n\t\t\t\t\t\treturn { reserved: true };\n\t\t\t\t\t},\n\t\t\t\t\trollback: async () => {\n\t\t\t\t\t\t// Set rolling_back status on first rollback\n\t\t\t\t\t\tc.state.status = \"rolling_back\";\n\t\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t\t(s) => s.name === \"reserve-inventory\"\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (step) {\n\t\t\t\t\t\t\tstep.status = \"rolled_back\";\n\t\t\t\t\t\t\tstep.rolledBackAt = Date.now();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx.log.info({ msg: \"inventory released\", txId: c.state.id });\n\t\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\t\t\t\t\t\t// Small delay so UI can show the rollback\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 400));\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Step 2: Charge card\n\t\t\t\tawait loopCtx.step({\n\t\t\t\t\tname: \"charge-card\",\n\t\t\t\t\trun: async () => {\n\t\t\t\t\t\tc.state.status = \"charging\";\n\t\t\t\t\t\tconst step = c.state.steps.find((s) => s.name === \"charge-card\");\n\t\t\t\t\t\tif (step) {\n\t\t\t\t\t\t\tstep.status = \"completed\";\n\t\t\t\t\t\t\tstep.completedAt = Date.now();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\t\tsetTimeout(r, 500 + Math.random() * 500)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (c.state.shouldFail) {\n\t\t\t\t\t\t\tthrow new Error(\"Payment declined (simulated)\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tctx.log.info({ msg: \"card charged\", txId: c.state.id });\n\t\t\t\t\t\treturn { chargeId: `ch_${c.state.id}` };\n\t\t\t\t\t},\n\t\t\t\t\trollback: async () => {\n\t\t\t\t\t\tc.state.status = \"rolling_back\";\n\t\t\t\t\t\tconst step = c.state.steps.find((s) => s.name === \"charge-card\");\n\t\t\t\t\t\tif (step) {\n\t\t\t\t\t\t\tstep.status = \"rolled_back\";\n\t\t\t\t\t\t\tstep.rolledBackAt = Date.now();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctx.log.info({ msg: \"charge refunded\", txId: c.state.id });\n\t\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\t\t\t\t\t\t// Small delay so UI can show the rollback\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 400));\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Step 3: Complete order\n\t\t\t\tawait loopCtx.step(\"complete-order\", async () => {\n\t\t\t\t\t\tc.state.status = \"completing\";\n\t\t\t\t\t\tconst step = c.state.steps.find((s) => s.name === \"complete-order\");\n\t\t\t\t\t\tif (step) step.status = \"completed\";\n\t\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\t\tsetTimeout(r, 300 + Math.random() * 300)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tc.state.status = \"completed\";\n\t\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\t\tctx.log.info({ msg: \"order completed\", txId: c.state.id });\n\t\t\t\t\t\tc.broadcast(\"transactionCompleted\", c.state);\n\t\t\t\t\t});\n\n\t\t\t\treturn Loop.break(undefined);\n\t\t\t});\n\t}),\n});\n","import { actor, event, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\nimport { actorCtx } from \"./_helpers.ts\";\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type WorkflowHistorySimpleState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"completed\";\n\tlastStep?: string;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n\toutput?: { success: boolean; processedItems: number };\n};\n\nexport const workflowHistorySimple = actor({\n\tcreateState: (c): WorkflowHistorySimpleState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistorySimpleState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\tc.state.lastStep = \"start\";\n\t\t\tc.state.startedAt = Date.now();\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait delay(700);\n\n\t\tawait ctx.step(\"process\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"process\";\n\t\t\treturn { processed: true, items: 5 };\n\t\t});\n\n\t\tawait delay(2200);\n\n\t\tawait ctx.step(\"validate\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"validate\";\n\t\t\treturn { valid: true };\n\t\t});\n\n\t\tawait delay(600);\n\n\t\tawait ctx.step(\"complete\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"complete\";\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\tc.state.output = { success: true, processedItems: 3 };\n\t\t\treturn { success: true };\n\t\t});\n\t}),\n});\n\nconst LOOP_ITEMS = [\"A\", \"B\", \"C\"];\n\nexport type WorkflowHistoryLoopState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tprocessed: number;\n\tbatches: Array<{ index: number; item: string }>;\n\tcompletedAt?: number;\n};\n\nexport const workflowHistoryLoop = actor({\n\tcreateState: (c): WorkflowHistoryLoopState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tprocessed: 0,\n\t\tbatches: [],\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryLoopState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { batchSize: LOOP_ITEMS.length };\n\t\t});\n\n\t\tawait ctx.loop({\n\t\t\tname: \"batch-loop\",\n\t\t\tstate: { index: 0 },\n\t\t\tcommitInterval: 1,\n\t\t\thistoryEvery: 1,\n\t\t\thistoryKeep: LOOP_ITEMS.length,\n\t\t\trun: async (loopCtx, loopState: { index: number }) => {\n\t\t\t\tconst item = LOOP_ITEMS[loopState.index];\n\n\t\t\t\tawait loopCtx.step(`process-${loopState.index}`, async () => {\n\t\t\t\t\tconst c = actorCtx(loopCtx);\n\t\t\t\t\tc.state.processed += 1;\n\t\t\t\t\tc.state.batches.push({ index: loopState.index, item });\n\t\t\t\t\treturn { item, status: \"done\" };\n\t\t\t\t});\n\n\t\t\t\tif (loopState.index >= LOOP_ITEMS.length - 1) {\n\t\t\t\t\treturn Loop.break({ processed: LOOP_ITEMS.length });\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ index: loopState.index + 1 });\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.step(\"finalize\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn { allProcessed: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryJoinState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"completed\";\n\tresult?: {\n\t\tapi: string;\n\t\trows: number;\n\t\tcacheHit: boolean;\n\t};\n};\n\nexport const workflowHistoryJoin = actor({\n\tcreateState: (c): WorkflowHistoryJoinState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryJoinState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { ready: true };\n\t\t});\n\n\t\tconst results = await ctx.join(\"parallel-tasks\", {\n\t\t\t\"fetch-api\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-a\", async () => {\n\t\t\t\t\t\tawait delay(120);\n\t\t\t\t\t\treturn { fetched: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { data: \"api-response\" };\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"query-db\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-b\", async () => {\n\t\t\t\t\t\tawait delay(200);\n\t\t\t\t\t\treturn { queried: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { rows: 42 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"check-cache\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-c\", async () => {\n\t\t\t\t\t\tawait delay(60);\n\t\t\t\t\t\treturn { checked: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { hit: true };\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.step(\"merge-results\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.result = {\n\t\t\t\tapi: results[\"fetch-api\"].data,\n\t\t\t\trows: results[\"query-db\"].rows,\n\t\t\t\tcacheHit: results[\"check-cache\"].hit,\n\t\t\t};\n\t\t\treturn { merged: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryRaceState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\twinner?: string;\n\tresult?: string;\n};\n\nexport const workflowHistoryRace = actor({\n\tcreateState: (c): WorkflowHistoryRaceState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryRaceState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"begin\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { started: true };\n\t\t});\n\n\t\tconst { winner, value } = await ctx.race<{\n\t\t\tprovider: string;\n\t\t\tlatency: number;\n\t\t}>(\"race-providers\", [\n\t\t\t{\n\t\t\t\tname: \"provider-fast\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"provider-fast-step\", 50);\n\t\t\t\t\treturn { provider: \"cdn-edge\", latency: 12 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"provider-slow\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"provider-slow-step\", 200);\n\t\t\t\t\treturn { provider: \"origin\", latency: 120 };\n\t\t\t\t},\n\t\t\t},\n\t\t]);\n\n\t\tawait ctx.step(\"use-result\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.winner = winner;\n\t\t\tc.state.result = value.provider;\n\t\t\treturn { used: value.provider };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryFullState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"waiting\" | \"completed\" | \"failed\";\n\tseededMessages: boolean;\n\tlastStep?: string;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n};\n\nconst QUEUE_ORDER_CREATED = \"order:created\";\nconst QUEUE_ORDER_UPDATED = \"order:updated\";\nconst QUEUE_ORDER_ITEM = \"order:item\";\nconst QUEUE_ORDER_ARTIFACT = \"order:artifact\";\nconst QUEUE_ORDER_READY = \"order:ready\";\nconst QUEUE_ORDER_OPTIONAL = \"order:optional\";\n\ntype OrderCreatedMessage = { id: string };\ntype OrderUpdatedMessage = { id: string; status: string };\ntype OrderItemMessage = { sku: string; qty: number };\ntype OrderArtifactMessage = { artifactId: string };\ntype OrderReadyMessage = { batch: number };\ntype OrderOptionalMessage = { note?: string };\n\ntype MessageSeed =\n\t| { name: typeof QUEUE_ORDER_CREATED; payload: OrderCreatedMessage }\n\t| { name: typeof QUEUE_ORDER_UPDATED; payload: OrderUpdatedMessage }\n\t| { name: typeof QUEUE_ORDER_ITEM; payload: OrderItemMessage }\n\t| { name: typeof QUEUE_ORDER_ARTIFACT; payload: OrderArtifactMessage }\n\t| { name: typeof QUEUE_ORDER_READY; payload: OrderReadyMessage };\n\nconst FULL_WORKFLOW_MESSAGE_SEEDS: MessageSeed[] = [\n\t{ name: QUEUE_ORDER_CREATED, payload: { id: \"order-1\" } },\n\t{ name: QUEUE_ORDER_UPDATED, payload: { id: \"order-1\", status: \"paid\" } },\n\t{ name: QUEUE_ORDER_ITEM, payload: { sku: \"sku-0\", qty: 1 } },\n\t{ name: QUEUE_ORDER_ITEM, payload: { sku: \"sku-4\", qty: 1 } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-0\" } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-1\" } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-2\" } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 3 } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 0 } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 2 } },\n];\n\nconst FULL_WORKFLOW_ITEMS = [\n\t{ id: \"item-1\", basePrice: 100, tax: 8 },\n\t{ id: \"item-2\", basePrice: 115, tax: 9 },\n\t{ id: \"item-3\", basePrice: 130, tax: 10 },\n\t{ id: \"item-4\", basePrice: 145, tax: 12 },\n];\n\nexport const workflowHistoryFull = actor({\n\tcreateState: (c): WorkflowHistoryFullState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t\tseededMessages: false,\n\t}),\n\tqueues: {\n\t\t[QUEUE_ORDER_CREATED]: queue(),\n\t\t[QUEUE_ORDER_UPDATED]: queue(),\n\t\t[QUEUE_ORDER_ITEM]: queue(),\n\t\t[QUEUE_ORDER_ARTIFACT]: queue(),\n\t\t[QUEUE_ORDER_READY]: queue(),\n\t\t[QUEUE_ORDER_OPTIONAL]: queue(),\n\t},\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryFullState => c.state,\n\t\tseedMessages: async (c) => {\n\t\t\tif (c.state.seededMessages) return;\n\t\t\tfor (const seed of FULL_WORKFLOW_MESSAGE_SEEDS) {\n\t\t\t\tawait c.queue.send(seed.name, seed.payload);\n\t\t\t}\n\t\t\tc.state.seededMessages = true;\n\t\t},\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"bootstrap\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\tc.state.lastStep = \"bootstrap\";\n\t\t\tc.state.startedAt = Date.now();\n\t\t\treturn {\n\t\t\t\trequestId: `req-${c.state.id}`,\n\t\t\t\tstartedAt: Date.now(),\n\t\t\t};\n\t\t});\n\n\t\tawait ctx.step(\"validate-input\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"validate-input\";\n\t\t\treturn true;\n\t\t});\n\n\t\tawait ctx.rollbackCheckpoint(\"checkpoint-after-validation\");\n\n\t\tawait ctx.step(\"load-user-profile\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"load-user-profile\";\n\t\t\treturn {\n\t\t\t\tid: \"user-123\",\n\t\t\t\ttier: \"standard\",\n\t\t\t\tflags: [\"email-verified\", \"promo-eligible\"],\n\t\t\t};\n\t\t});\n\n\t\tawait ctx.step(\"compute-discount\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"compute-discount\";\n\t\t\treturn { percent: 5, reason: \"tier-discount\" };\n\t\t});\n\n\t\tawait ctx.step(\"ephemeral-cache-check\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"ephemeral-cache-check\";\n\t\t\treturn { cacheHit: false, tier: \"standard\" };\n\t\t});\n\n\t\tawait ctx.rollbackCheckpoint(\"checkpoint-before-reserve\");\n\n\t\tawait ctx.loop({\n\t\t\tname: \"process-items-loop\",\n\t\t\tstate: { index: 0 },\n\t\t\tcommitInterval: 1,\n\t\t\thistoryEvery: 1,\n\t\t\thistoryKeep: 2,\n\t\t\trun: async (loopCtx, loopState: { index: number }) => {\n\t\t\t\tconst item = FULL_WORKFLOW_ITEMS[loopState.index];\n\t\t\t\tif (!item) {\n\t\t\t\t\treturn Loop.break({ count: FULL_WORKFLOW_ITEMS.length });\n\t\t\t\t}\n\n\t\t\t\tawait loopCtx.step(`fetch-item-${loopState.index}`, async () => {\n\t\t\t\t\treturn { itemId: item.id, basePrice: item.basePrice };\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(`compute-tax-${loopState.index}`, async () => {\n\t\t\t\t\treturn item.tax;\n\t\t\t\t});\n\n\t\t\t\tawait loopCtx.step(\n\t\t\t\t\t`reserve-inventory-${loopState.index}`,\n\t\t\t\t\tasync () => ({\n\t\t\t\t\t\treservationId: `res-${loopState.index}`,\n\t\t\t\t\t\titemId: item.id,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tif (loopState.index >= FULL_WORKFLOW_ITEMS.length - 1) {\n\t\t\t\t\treturn Loop.break({\n\t\t\t\t\t\tcount: FULL_WORKFLOW_ITEMS.length,\n\t\t\t\t\t\ttotal: 504,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ index: loopState.index + 1 });\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.sleep(\"short-cooldown\", 40);\n\t\tawait ctx.sleep(\"cooldown-sleep\", 60);\n\t\tawait ctx.sleep(\"wait-until-deadline\", 45);\n\n\t\tawait ctx.step(\"compute-deadlines\", async () => {\n\t\t\tconst readyBy = Date.now() + 800;\n\t\t\tconst readyBatchBy = Date.now() + 1100;\n\t\t\treturn { readyBy, readyBatchBy };\n\t\t});\n\n\t\tawait ctx.queue.next(\"listen-order-created\", {\n\t\t\tnames: [QUEUE_ORDER_CREATED],\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-order-updated-timeout\", {\n\t\t\tnames: [QUEUE_ORDER_UPDATED],\n\t\t\ttimeout: 250,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-batch-two\", {\n\t\t\tnames: [QUEUE_ORDER_ITEM],\n\t\t\tcount: 2,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-artifacts-timeout\", {\n\t\t\tnames: [QUEUE_ORDER_ARTIFACT],\n\t\t\tcount: 3,\n\t\t\ttimeout: 300,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-optional\", {\n\t\t\tnames: [QUEUE_ORDER_OPTIONAL],\n\t\t\ttimeout: 200,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-until\", {\n\t\t\tnames: [QUEUE_ORDER_READY],\n\t\t\ttimeout: 300,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-batch-until\", {\n\t\t\tnames: [QUEUE_ORDER_READY],\n\t\t\tcount: 2,\n\t\t\ttimeout: 400,\n\t\t});\n\n\t\tawait ctx.join(\"join-dependencies\", {\n\t\t\tinventory: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst reserved = await branchCtx.step(\n\t\t\t\t\t\t\"inventory-audit\",\n\t\t\t\t\t\tasync () => 4,\n\t\t\t\t\t);\n\t\t\t\t\tawait branchCtx.sleep(\"join-inventory-sleep\", 35);\n\t\t\t\t\treturn {\n\t\t\t\t\t\treserved,\n\t\t\t\t\t\tchecked: 4,\n\t\t\t\t\t\tnotes: [\"inventory-ok\", \"items=4\"],\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t\tpricing: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst method = await branchCtx.step(\n\t\t\t\t\t\t\"pricing-method\",\n\t\t\t\t\t\tasync () => \"promo\",\n\t\t\t\t\t);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsubtotal: 504,\n\t\t\t\t\t\tdiscount: 25,\n\t\t\t\t\t\ttotal: 479,\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t\tshipping: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst zone = await branchCtx.step(\n\t\t\t\t\t\t\"shipping-zone\",\n\t\t\t\t\t\tasync () => \"us-east\",\n\t\t\t\t\t);\n\t\t\t\t\tawait branchCtx.sleep(\"join-shipping-sleep\", 35);\n\t\t\t\t\treturn { method: \"ground\", etaDays: 4, zone };\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.race(\"race-fulfillment\", [\n\t\t\t{\n\t\t\t\tname: \"race-fast\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"race-fast-sleep\", 70);\n\t\t\t\t\treturn { method: \"express\", cost: 18, etaDays: 1 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"race-slow\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"race-slow-sleep\", 250);\n\t\t\t\t\treturn { method: \"ground\", cost: 8, etaDays: 4 };\n\t\t\t\t},\n\t\t\t},\n\t\t]);\n\n\t\tawait ctx.removed(\"legacy-step-placeholder\", \"step\");\n\n\t\tawait ctx.step(\"finalize\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.lastStep = \"finalize\";\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn true;\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryInProgressState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tprocessingDurationMs: number;\n\tprogress: number;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n};\n\nexport type WorkflowHistoryInProgressInput = {\n\tprocessingDurationMs?: number;\n};\n\nexport const workflowHistoryInProgress = actor({\n\tcreateState: (\n\t\tc,\n\t\tinput?: WorkflowHistoryInProgressInput,\n\t): WorkflowHistoryInProgressState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tprocessingDurationMs: input?.processingDurationMs ?? 30000,\n\t\tprogress: 0,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryInProgressState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.startedAt = Date.now();\n\t\t\tc.state.progress = 10;\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait ctx.step(\"fetch-data\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.progress = 25;\n\t\t\treturn { fetched: true, records: 100 };\n\t\t});\n\n\t\tawait ctx.step(\"process\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.progress = 42;\n\t\t\tawait delay(c.state.processingDurationMs);\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn { processed: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryRetryingState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tattempts: number;\n\tlastError?: string;\n\tsucceedAfter: number;\n};\n\nconst RETRY_MAX_RETRIES = 20;\n\nexport const workflowHistoryRetrying = actor({\n\tcreateState: (c): WorkflowHistoryRetryingState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tattempts: 0,\n\t\tsucceedAfter: 999,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryRetryingState => c.state,\n\t\tallowSuccess: (c, afterAttempt?: number) => {\n\t\t\tc.state.succeedAfter = afterAttempt ?? c.state.attempts + 1;\n\t\t},\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { ready: true };\n\t\t});\n\n\t\tawait ctx.step({\n\t\t\tname: \"api-call\",\n\t\t\tmaxRetries: RETRY_MAX_RETRIES,\n\t\t\tretryBackoffBase: 250,\n\t\t\tretryBackoffMax: 1500,\n\t\t\trun: async () => {\n\t\t\t\tconst c = actorCtx(ctx);\n\t\t\t\tc.state.attempts += 1;\n\t\t\t\tif (c.state.attempts < c.state.succeedAfter) {\n\t\t\t\t\tconst error = new Error(\"Connection timeout after 5000ms\");\n\t\t\t\t\tc.state.lastError = error.message;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tc.state.status = \"completed\";\n\t\t\t\tc.state.lastError = undefined;\n\t\t\t\treturn { success: true };\n\t\t\t},\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryFailedState = {\n\tid: string;\n\tstatus: \"running\" | \"failed\";\n\tattempts: number;\n\tlastError?: string;\n};\n\nconst FAILED_MAX_RETRIES = 3;\n\nexport const workflowHistoryFailed = actor({\n\tcreateState: (c): WorkflowHistoryFailedState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tattempts: 0,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryFailedState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async () => {\n\t\t\tconst c = actorCtx(ctx);\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait ctx.step(\"validate\", async () => {\n\t\t\treturn { valid: true };\n\t\t});\n\n\t\tawait ctx.step({\n\t\t\tname: \"process\",\n\t\t\tmaxRetries: FAILED_MAX_RETRIES,\n\t\t\tretryBackoffBase: 200,\n\t\t\tretryBackoffMax: 800,\n\t\t\trun: async () => {\n\t\t\t\tconst c = actorCtx(ctx);\n\t\t\t\tc.state.attempts += 1;\n\t\t\t\tconst error = new Error(\n\t\t\t\t\t\"Database connection failed: ECONNREFUSED\",\n\t\t\t\t);\n\t\t\t\tc.state.lastError = error.message;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t});\n\t}),\n});\n","import { actor } from \"rivetkit\";\n\nexport interface InventoryInput {\n\tinitialStock: number;\n\titemName: string;\n}\n\nexport interface InventoryState {\n\titemName: string;\n\tstock: number;\n\treservations: string[]; // Track which checkouts have reserved items\n}\n\nexport interface CheckoutInput {\n\tcustomerName: string;\n}\n\nexport interface CheckoutItem {\n\titemId: string;\n\titemName: string;\n\tquantity: number;\n}\n\nexport interface CheckoutResult {\n\tsuccess: boolean;\n\tmessage: string;\n\tremainingStock?: number;\n}\n\nexport interface CheckoutState {\n\tcustomerName: string;\n\titems: CheckoutItem[];\n\tcompleted: boolean;\n}\n\n// Inventory actor manages stock for a specific item\nexport const inventory = actor({\n\t// Each item has its own inventory actor instance\n\tcreateState: (_c, input?: InventoryInput): InventoryState => ({\n\t\titemName: input?.itemName ?? \"Widget\",\n\t\tstock: input?.initialStock ?? 100,\n\t\treservations: [],\n\t}),\n\n\tactions: {\n\t\t// Check current stock\n\t\tgetStock: (c) => ({\n\t\t\titemName: c.state.itemName,\n\t\t\tstock: c.state.stock,\n\t\t}),\n\n\t\t// Reserve items for checkout (called by checkout actor)\n\t\treserveItems: (c, checkoutId: string, quantity: number) => {\n\t\t\tif (c.state.stock < quantity) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tmessage: `Insufficient stock. Available: ${c.state.stock}, Requested: ${quantity}`,\n\t\t\t\t\tavailableStock: c.state.stock,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Reserve the items\n\t\t\tc.state.stock -= quantity;\n\t\t\tc.state.reservations.push(checkoutId);\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: `Reserved ${quantity} items for checkout ${checkoutId}`,\n\t\t\t\tremainingStock: c.state.stock,\n\t\t\t};\n\t\t},\n\n\t\t// Release reserved items if checkout is cancelled\n\t\treleaseItems: (c, checkoutId: string, quantity: number) => {\n\t\t\tconst index = c.state.reservations.indexOf(checkoutId);\n\t\t\tif (index > -1) {\n\t\t\t\tc.state.reservations.splice(index, 1);\n\t\t\t\tc.state.stock += quantity;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tremainingStock: c.state.stock,\n\t\t\t};\n\t\t},\n\t},\n});\n\n// Checkout actor manages the checkout process and communicates with inventory\nexport const checkout = actor({\n\tcreateState: (_c, input?: CheckoutInput): CheckoutState => ({\n\t\tcustomerName: input?.customerName ?? \"Guest\",\n\t\titems: [],\n\t\tcompleted: false,\n\t}),\n\n\tactions: {\n\t\t// Add item to checkout and reserve from inventory\n\t\taddItem: async (\n\t\t\tc,\n\t\t\titemId: string,\n\t\t\tquantity: number,\n\t\t): Promise => {\n\t\t\t// Use server-side client to communicate with inventory actor\n\t\t\t// https://rivet.dev/docs/actors/communicating-between-actors\n\t\t\tconst inventoryActor = c.client().inventory.getOrCreate([itemId]);\n\n\t\t\t// Get item details\n\t\t\tconst itemInfo = await inventoryActor.getStock();\n\n\t\t\t// Try to reserve items from inventory\n\t\t\tconst reservation = await inventoryActor.reserveItems(\n\t\t\t\tc.actorId, // Use checkout ID as reservation ID\n\t\t\t\tquantity,\n\t\t\t);\n\n\t\t\tif (!reservation.success) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tmessage: reservation.message,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Add item to checkout\n\t\t\tc.state.items.push({\n\t\t\t\titemId,\n\t\t\t\titemName: itemInfo.itemName,\n\t\t\t\tquantity,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: `Added ${quantity} ${itemInfo.itemName} to checkout`,\n\t\t\t\tremainingStock: reservation.remainingStock,\n\t\t\t};\n\t\t},\n\n\t\t// Get checkout summary\n\t\tgetSummary: (c) => ({\n\t\t\tcustomerName: c.state.customerName,\n\t\t\titems: c.state.items,\n\t\t\tcompleted: c.state.completed,\n\t\t\ttotalItems: c.state.items.reduce(\n\t\t\t\t(sum, item) => sum + item.quantity,\n\t\t\t\t0,\n\t\t\t),\n\t\t}),\n\n\t\t// Complete the checkout\n\t\tcompleteCheckout: (c) => {\n\t\t\tc.state.completed = true;\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Checkout completed successfully\",\n\t\t\t};\n\t\t},\n\n\t\t// Cancel checkout and release all reservations\n\t\tcancelCheckout: async (c) => {\n\t\t\t// Release all reserved items\n\t\t\tfor (const item of c.state.items) {\n\t\t\t\tconst inventoryActor = c\n\t\t\t\t\t.client()\n\t\t\t\t\t.inventory.getOrCreate([item.itemId]);\n\t\t\t\tawait inventoryActor.releaseItems(c.actorId, item.quantity);\n\t\t\t}\n\n\t\t\t// Clear the cart\n\t\t\tc.state.items = [];\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Checkout cancelled, items returned to inventory\",\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\n// The dynamic-sandbox WebSocket connect path is failing intermittently and\n// silently swapping to HTTP-only would skip event-bridge validation entirely.\n// Re-enable WebSocket coverage on every runtime; if the dynamic-sandbox\n// regression returns, root-cause it instead of restoring this gate.\nfunction isDynamicSandboxRuntime(): boolean {\n\treturn false;\n}\n\nasync function waitForConnectionOpen(connection: {\n\tconnStatus: string;\n\tonOpen(callback: () => void): () => void;\n\tonError(callback: (error: unknown) => void): () => void;\n}) {\n\tif (connection.connStatus === \"connected\") {\n\t\treturn;\n\t}\n\n\tawait new Promise((resolve, reject) => {\n\t\tconst unsubscribeOpen = connection.onOpen(() => {\n\t\t\tunsubscribeOpen();\n\t\t\tunsubscribeError();\n\t\t\tresolve();\n\t\t});\n\t\tconst unsubscribeError = connection.onError((error) => {\n\t\t\tunsubscribeOpen();\n\t\t\tunsubscribeError();\n\t\t\treject(error);\n\t\t});\n\t});\n}\n\nexport const inlineClientActor = actor({\n\tstate: { messages: [] as string[] },\n\tactions: {\n\t\t// Action that uses client to call another actor (stateless)\n\t\tcallCounterIncrement: async (c, amount: number) => {\n\t\t\tconst client = c.client();\n\t\t\tconst result = await client.counter\n\t\t\t\t.getOrCreate([\"inline-test\"])\n\t\t\t\t.increment(amount);\n\t\t\tc.state.messages.push(\n\t\t\t\t`Called counter.increment(${amount}), result: ${result}`,\n\t\t\t);\n\t\t\treturn result;\n\t\t},\n\n\t\t// Action that uses client to get counter state (stateless)\n\t\tgetCounterState: async (c) => {\n\t\t\tconst client = c.client();\n\t\t\tconst count = await client.counter\n\t\t\t\t.getOrCreate([\"inline-test\"])\n\t\t\t\t.getCount();\n\t\t\tc.state.messages.push(`Got counter state: ${count}`);\n\t\t\treturn count;\n\t\t},\n\n\t\t// Action that uses client with .connect() for stateful communication\n\t\tconnectToCounterAndIncrement: async (c, amount: number) => {\n\t\t\tconst client = c.client();\n\t\t\tconst handle = client.counter.getOrCreate([\"inline-test-stateful\"]);\n\n\t\t\tif (isDynamicSandboxRuntime()) {\n\t\t\t\tconst events: number[] = [];\n\t\t\t\tconst result1 = await handle.increment(amount);\n\t\t\t\tevents.push(result1);\n\t\t\t\tconst result2 = await handle.increment(amount * 2);\n\t\t\t\tevents.push(result2);\n\n\t\t\t\tc.state.messages.push(\n\t\t\t\t\t`Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`,\n\t\t\t\t);\n\n\t\t\t\treturn { result1, result2, events };\n\t\t\t}\n\n\t\t\tawait handle.getCount();\n\t\t\tconst connection = handle.connect();\n\t\t\tawait waitForConnectionOpen(connection);\n\n\t\t\t// Set up event listener\n\t\t\tconst events: number[] = [];\n\t\t\tconnection.on(\"newCount\", (count: number) => {\n\t\t\t\tevents.push(count);\n\t\t\t});\n\n\t\t\t// Perform increments\n\t\t\tconst result1 = await connection.increment(amount);\n\t\t\tconst result2 = await connection.increment(amount * 2);\n\n\t\t\tawait connection.dispose();\n\n\t\t\tc.state.messages.push(\n\t\t\t\t`Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`,\n\t\t\t);\n\n\t\t\treturn { result1, result2, events };\n\t\t},\n\n\t\t// Get all messages from this actor's state\n\t\tgetMessages: (c) => {\n\t\t\treturn c.state.messages;\n\t\t},\n\n\t\t// Clear messages\n\t\tclearMessages: (c) => {\n\t\t\tc.state.messages = [];\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const testCounter = actor({\n\tstate: { count: 0 },\n\tactions: {\n\t\tincrement: (c, amount: number = 1) => {\n\t\t\tc.state.count += amount;\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.state.count;\n\t\t},\n\t\treset: (c) => {\n\t\t\tc.state.count = 0;\n\t\t\treturn c.state.count;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const testCounterSqlite = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tvalue INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tincrement: async (c, amount: number = 1) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE counter SET value = value + ? WHERE id = 1\",\n\t\t\t\tamount,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\tgetCount: async (c) => {\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"UPDATE counter SET value = 0 WHERE id = 1\");\n\t\t\treturn 0;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const testSqliteLoad = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\t// Migration 1: schema version tracking\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS schema_version (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tversion INTEGER NOT NULL DEFAULT 50\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, 50)\",\n\t\t\t);\n\n\t\t\t// Migration 2\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS users (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\temail TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 3\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS products (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tprice REAL NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 4\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS orders (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttotal REAL NOT NULL DEFAULT 0,\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 5\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS order_items (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tprice REAL NOT NULL,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 6\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS categories (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL UNIQUE,\n\t\t\t\t\tdescription TEXT\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 7\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_categories (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tcategory_id INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (product_id, category_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (category_id) REFERENCES categories(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 8\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS reviews (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\trating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),\n\t\t\t\t\tcomment TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 9\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS addresses (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tstreet TEXT NOT NULL,\n\t\t\t\t\tcity TEXT NOT NULL,\n\t\t\t\t\tstate TEXT,\n\t\t\t\t\tzip TEXT,\n\t\t\t\t\tcountry TEXT NOT NULL DEFAULT 'US',\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 10\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS payments (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\tamount REAL NOT NULL,\n\t\t\t\t\tmethod TEXT NOT NULL DEFAULT 'card',\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\t\tprocessed_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 11\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS inventory (\n\t\t\t\t\tproduct_id INTEGER PRIMARY KEY,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\treserved INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tlast_restocked_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 12\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS coupons (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tcode TEXT NOT NULL UNIQUE,\n\t\t\t\t\tdiscount_percent REAL NOT NULL,\n\t\t\t\t\tmax_uses INTEGER,\n\t\t\t\t\tused_count INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\texpires_at INTEGER\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 13\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS shipping (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\taddress_id INTEGER NOT NULL,\n\t\t\t\t\tcarrier TEXT,\n\t\t\t\t\ttracking_number TEXT,\n\t\t\t\t\tshipped_at INTEGER,\n\t\t\t\t\tdelivered_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id),\n\t\t\t\t\tFOREIGN KEY (address_id) REFERENCES addresses(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 14\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS tags (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL UNIQUE\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 15\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_tags (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\ttag_id INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (product_id, tag_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (tag_id) REFERENCES tags(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 16\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS wishlists (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tname TEXT NOT NULL DEFAULT 'Default',\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 17\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS wishlist_items (\n\t\t\t\t\twishlist_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tadded_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (wishlist_id, product_id),\n\t\t\t\t\tFOREIGN KEY (wishlist_id) REFERENCES wishlists(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 18\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS notifications (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttype TEXT NOT NULL,\n\t\t\t\t\tmessage TEXT NOT NULL,\n\t\t\t\t\tread INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 19\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS audit_log (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tentity_type TEXT NOT NULL,\n\t\t\t\t\tentity_id INTEGER NOT NULL,\n\t\t\t\t\taction TEXT NOT NULL,\n\t\t\t\t\tdetails TEXT,\n\t\t\t\t\tperformed_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 20\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS sessions (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttoken TEXT NOT NULL UNIQUE,\n\t\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 21\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)\",\n\t\t\t);\n\n\t\t\t// Migration 22\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 23\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)\",\n\t\t\t);\n\n\t\t\t// Migration 24\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_reviews_product ON reviews(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 25\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_reviews_user ON reviews(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 26\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 27\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id)\",\n\t\t\t);\n\n\t\t\t// Migration 28\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 29\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)\",\n\t\t\t);\n\n\t\t\t// Migration 30\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_inventory_quantity ON inventory(quantity)\",\n\t\t\t);\n\n\t\t\t// Migration 31\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS returns (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\treason TEXT NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'requested',\n\t\t\t\t\trequested_at INTEGER NOT NULL,\n\t\t\t\t\tprocessed_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 32\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS return_items (\n\t\t\t\t\treturn_id INTEGER NOT NULL,\n\t\t\t\t\torder_item_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (return_id, order_item_id),\n\t\t\t\t\tFOREIGN KEY (return_id) REFERENCES returns(id),\n\t\t\t\t\tFOREIGN KEY (order_item_id) REFERENCES order_items(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 33\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS suppliers (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tcontact_email TEXT,\n\t\t\t\t\tcountry TEXT\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 34\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_suppliers (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tsupplier_id INTEGER NOT NULL,\n\t\t\t\t\tcost REAL NOT NULL,\n\t\t\t\t\tlead_time_days INTEGER,\n\t\t\t\t\tPRIMARY KEY (product_id, supplier_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (supplier_id) REFERENCES suppliers(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 35\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS price_history (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\told_price REAL NOT NULL,\n\t\t\t\t\tnew_price REAL NOT NULL,\n\t\t\t\t\tchanged_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 36\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS user_preferences (\n\t\t\t\t\tuser_id INTEGER PRIMARY KEY,\n\t\t\t\t\ttheme TEXT NOT NULL DEFAULT 'dark',\n\t\t\t\t\tlanguage TEXT NOT NULL DEFAULT 'en',\n\t\t\t\t\tnotifications_enabled INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 37\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cart (\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tadded_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (user_id, product_id),\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 38\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS saved_searches (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tquery TEXT NOT NULL,\n\t\t\t\t\tfilters TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 39\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_images (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\turl TEXT NOT NULL,\n\t\t\t\t\talt_text TEXT,\n\t\t\t\t\tsort_order INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 40\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS discounts (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tdiscount_percent REAL NOT NULL,\n\t\t\t\t\tstarts_at INTEGER NOT NULL,\n\t\t\t\t\tends_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 41\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 42\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_order_items_product ON order_items(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 43\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 44\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_shipping_order ON shipping(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 45\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_returns_order ON returns(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 46\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_price_history_product ON price_history(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 47\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_product_images_product ON product_images(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 48\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_discounts_product ON discounts(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 49\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 50\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_saved_searches_user ON saved_searches(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 51: counter for benchmarking\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tvalue INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tincrement: async (c, amount: number = 1) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE counter SET value = value + ? WHERE id = 1\",\n\t\t\t\tamount,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\tgetCount: async (c) => {\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"UPDATE counter SET value = 0 WHERE id = 1\");\n\t\t\treturn 0;\n\t\t},\n\t\trunLoadTest: async (c) => {\n\t\t\tconst now = Date.now();\n\t\t\tconst results: string[] = [];\n\n\t\t\t// Query 1: Insert a user\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\"Load Test User\",\n\t\t\t\t`load-${now}@test.com`,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted user\");\n\n\t\t\t// Query 2: Get the user back\n\t\t\tconst users = await c.db.execute(\n\t\t\t\t\"SELECT * FROM users WHERE email = ?\",\n\t\t\t\t`load-${now}@test.com`,\n\t\t\t);\n\t\t\tresults.push(`fetched user: ${(users[0] as { name: string }).name}`);\n\t\t\tconst userId = (users[0] as { id: number }).id;\n\n\t\t\t// Query 3: Insert a product\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO products (name, price, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\"Test Widget\",\n\t\t\t\t29.99,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted product\");\n\n\t\t\t// Query 4: Get products\n\t\t\tconst products = await c.db.execute(\"SELECT * FROM products LIMIT 10\");\n\t\t\tresults.push(`fetched ${(products as unknown[]).length} products`);\n\t\t\tconst productId = (products[0] as { id: number }).id;\n\n\t\t\t// Query 5: Insert a category\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO categories (name, description) VALUES (?, ?)\",\n\t\t\t\t`test-cat-${now}`,\n\t\t\t\t\"A test category\",\n\t\t\t);\n\t\t\tresults.push(\"inserted category\");\n\n\t\t\t// Query 6: Get categories\n\t\t\tconst categories = await c.db.execute(\"SELECT * FROM categories\");\n\t\t\tresults.push(`fetched ${(categories as unknown[]).length} categories`);\n\n\t\t\t// Query 7: Insert an order\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO orders (user_id, total, status, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t29.99,\n\t\t\t\t\"pending\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted order\");\n\n\t\t\t// Query 8: Get orders for user\n\t\t\tconst orders = await c.db.execute(\n\t\t\t\t\"SELECT * FROM orders WHERE user_id = ?\",\n\t\t\t\tuserId,\n\t\t\t);\n\t\t\tresults.push(`fetched ${(orders as unknown[]).length} orders for user`);\n\t\t\tconst orderId = (orders[0] as { id: number }).id;\n\n\t\t\t// Query 9: Insert order item\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)\",\n\t\t\t\torderId,\n\t\t\t\tproductId,\n\t\t\t\t2,\n\t\t\t\t29.99,\n\t\t\t);\n\t\t\tresults.push(\"inserted order item\");\n\n\t\t\t// Query 10: Insert inventory\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT OR REPLACE INTO inventory (product_id, quantity, reserved, last_restocked_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tproductId,\n\t\t\t\t100,\n\t\t\t\t2,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted inventory\");\n\n\t\t\t// Query 11: Insert a review\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO reviews (user_id, product_id, rating, comment, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\tproductId,\n\t\t\t\t5,\n\t\t\t\t\"Great product!\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted review\");\n\n\t\t\t// Query 12: Get reviews with join\n\t\t\tconst reviews = await c.db.execute(\n\t\t\t\t\"SELECT r.*, u.name as reviewer FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.product_id = ?\",\n\t\t\t\tproductId,\n\t\t\t);\n\t\t\tresults.push(`fetched ${(reviews as unknown[]).length} reviews`);\n\n\t\t\t// Query 13: Insert notification\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO notifications (user_id, type, message, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t\"order\",\n\t\t\t\t\"Your order has been placed\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted notification\");\n\n\t\t\t// Query 14: Insert audit log entry\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO audit_log (entity_type, entity_id, action, details, performed_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\"order\",\n\t\t\t\torderId,\n\t\t\t\t\"created\",\n\t\t\t\t`Order created by user ${userId}`,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted audit log\");\n\n\t\t\t// Query 15: Aggregate query on orders\n\t\t\tconst orderStats = await c.db.execute(\n\t\t\t\t\"SELECT status, COUNT(*) as count, SUM(total) as total_value FROM orders GROUP BY status\",\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`order stats: ${(orderStats as unknown[]).length} statuses`,\n\t\t\t);\n\n\t\t\t// Query 16: Insert address\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO addresses (user_id, street, city, state, zip, country) VALUES (?, ?, ?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t\"123 Test St\",\n\t\t\t\t\"Testville\",\n\t\t\t\t\"CA\",\n\t\t\t\t\"90210\",\n\t\t\t\t\"US\",\n\t\t\t);\n\t\t\tresults.push(\"inserted address\");\n\n\t\t\t// Query 17: Complex join query\n\t\t\tconst orderDetails = await c.db.execute(`\n\t\t\t\tSELECT o.id, o.status, o.total, u.name as customer, COUNT(oi.id) as item_count\n\t\t\t\tFROM orders o\n\t\t\t\tJOIN users u ON o.user_id = u.id\n\t\t\t\tLEFT JOIN order_items oi ON oi.order_id = o.id\n\t\t\t\tGROUP BY o.id\n\t\t\t\tLIMIT 10\n\t\t\t`);\n\t\t\tresults.push(\n\t\t\t\t`fetched ${(orderDetails as unknown[]).length} order details`,\n\t\t\t);\n\n\t\t\t// Query 18: Update order status\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE orders SET status = ? WHERE id = ?\",\n\t\t\t\t\"completed\",\n\t\t\t\torderId,\n\t\t\t);\n\t\t\tresults.push(\"updated order status\");\n\n\t\t\t// Query 19: Get schema version\n\t\t\tconst version = await c.db.execute(\n\t\t\t\t\"SELECT version FROM schema_version WHERE id = 1\",\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`schema version: ${(version[0] as { version: number }).version}`,\n\t\t\t);\n\n\t\t\t// Query 20: Count all tables\n\t\t\tconst tableCounts = await c.db.execute(`\n\t\t\t\tSELECT 'users' as tbl, COUNT(*) as cnt FROM users\n\t\t\t\tUNION ALL SELECT 'products', COUNT(*) FROM products\n\t\t\t\tUNION ALL SELECT 'orders', COUNT(*) FROM orders\n\t\t\t\tUNION ALL SELECT 'reviews', COUNT(*) FROM reviews\n\t\t\t\tUNION ALL SELECT 'categories', COUNT(*) FROM categories\n\t\t\t`);\n\t\t\tresults.push(\n\t\t\t\t`table counts: ${(tableCounts as unknown[]).length} tables checked`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tqueriesRun: 20,\n\t\t\t\tresults,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst CHAT_LOG_CHUNK_BYTES = 4 * 1024;\nconst CHAT_LOG_INSERT_BATCH_SIZE = 50;\n\nfunction buildChatLogMessage(seq: number, targetBytes: number): string {\n\tconst prefix = `message-${seq}: `;\n\treturn prefix + \"x\".repeat(Math.max(0, targetBytes - prefix.length));\n}\n\nasync function seedChatLog(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tconst threadId = `chat-${crypto.randomUUID()}`;\n\tconst createdAtBase = Date.now();\n\tlet remainingBytes = targetBytes;\n\tlet rows = 0;\n\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\twhile (remainingBytes > 0) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\n\t\t\tfor (\n\t\t\t\tlet batchIndex = 0;\n\t\t\t\tbatchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0;\n\t\t\t\tbatchIndex++\n\t\t\t) {\n\t\t\t\tconst contentBytes = Math.min(CHAT_LOG_CHUNK_BYTES, remainingBytes);\n\t\t\t\tconst seq = rows;\n\t\t\t\tconst role = seq % 2 === 0 ? \"user\" : \"assistant\";\n\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\tthreadId,\n\t\t\t\t\tseq,\n\t\t\t\t\trole,\n\t\t\t\t\tbuildChatLogMessage(seq, contentBytes),\n\t\t\t\t\tcontentBytes,\n\t\t\t\t\tMath.ceil(contentBytes / 4),\n\t\t\t\t\tcreatedAtBase + seq,\n\t\t\t\t);\n\n\t\t\t\tremainingBytes -= contentBytes;\n\t\t\t\trows++;\n\t\t\t}\n\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\");\n\t\tthrow err;\n\t}\n\n\treturn { threadId, rows, totalBytes: targetBytes };\n}\n\nexport const testSqliteBench = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tkey TEXT NOT NULL,\n\t\t\t\tvalue TEXT NOT NULL,\n\t\t\t\tnum INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tpayload BLOB,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\"CREATE INDEX IF NOT EXISTS idx_bench_key ON bench(key)\");\n\t\t\tawait database.execute(\"CREATE INDEX IF NOT EXISTS idx_bench_num ON bench(num)\");\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench_json (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tdata TEXT NOT NULL DEFAULT '{}'\n\t\t\t)`);\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench_secondary (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tbench_id INTEGER NOT NULL,\n\t\t\t\tlabel TEXT NOT NULL,\n\t\t\t\tscore REAL NOT NULL DEFAULT 0,\n\t\t\t\tFOREIGN KEY (bench_id) REFERENCES bench(id)\n\t\t\t)`);\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS chat_log (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tthread_id TEXT NOT NULL,\n\t\t\t\tseq INTEGER NOT NULL,\n\t\t\t\trole TEXT NOT NULL,\n\t\t\t\tcontent TEXT NOT NULL,\n\t\t\t\tcontent_bytes INTEGER NOT NULL,\n\t\t\t\ttoken_estimate INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_chat_log_thread_seq ON chat_log(thread_id, seq DESC)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tnoop: (_c) => ({ ok: true }),\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\tinsertSingle: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`k-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tinsertTx: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`k-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tinsertBatch: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst placeholders = Array.from({ length: n }, () => \"(?, ?, ?, ?)\").join(\", \");\n\t\t\tconst args: unknown[] = [];\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\targs.push(`k-${i}`, `v-${i}`, i, Date.now());\n\t\t\t}\n\t\t\tawait c.db.execute(`INSERT INTO bench (key, value, num, created_at) VALUES ${placeholders}`, ...args);\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tpointRead: async (c, n: number) => {\n\t\t\tawait c.db.execute(\"INSERT INTO bench (key, value, num, created_at) VALUES ('pr', 'pr', 0, 0)\");\n\t\t\tconst rows = await c.db.execute(\"SELECT id FROM bench WHERE key = 'pr' LIMIT 1\");\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\"SELECT * FROM bench WHERE id = ?\", id);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tfullScan: async (c, seedRows: number) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < seedRows; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`scan-${i}`, `val-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM bench\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\trangeScanIndexed: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`rs-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM bench WHERE num BETWEEN 100 AND 300\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\trangeScanUnindexed: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`ru-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM bench WHERE value BETWEEN 'v-100' AND 'v-300'\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\tbulkUpdate: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`bu-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"UPDATE bench SET value = 'updated', num = num + 1000 WHERE key LIKE 'bu-%'\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, ops: 200 };\n\t\t},\n\n\t\tbulkDelete: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`bd-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"DELETE FROM bench WHERE key LIKE 'bd-%'\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, ops: 200 };\n\t\t},\n\n\t\thotRowUpdates: async (c, n: number) => {\n\t\t\tawait c.db.execute(\"INSERT INTO bench (key, value, num, created_at) VALUES ('hot', 'v', 0, 0)\");\n\t\t\tconst rows = await c.db.execute(\"SELECT id FROM bench WHERE key = 'hot' LIMIT 1\");\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\"UPDATE bench SET num = ? WHERE id = ?\", i, id);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tvacuumAfterDelete: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`vac-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tawait c.db.execute(\"DELETE FROM bench WHERE key LIKE 'vac-%'\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"VACUUM\");\n\t\t\treturn { ms: performance.now() - t0, seedMs };\n\t\t},\n\n\t\tlargePayloadInsert: async (c, n: number) => {\n\t\t\tconst blob = \"x\".repeat(32 * 1024);\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t`lp-${i}`, `v-${i}`, i, blob, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tmixedOltp: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"oltp\", \"initial\", 0, Date.now(),\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM bench WHERE key = 'oltp' LIMIT 1\");\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tawait c.db.execute(\"UPDATE bench SET value = 'updated', num = 1 WHERE id = ?\", id);\n\t\t\tawait c.db.execute(\"SELECT * FROM bench WHERE id = ?\", id);\n\t\t\treturn { ms: performance.now() - t0, ops: 4 };\n\t\t},\n\n\t\tjsonInsertAndQuery: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < 50; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench_json (data) VALUES (?)\",\n\t\t\t\t\tJSON.stringify({ name: `item-${i}`, tags: [\"a\", \"b\"], score: Math.random() * 100 }),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT id, json_extract(data, '$.name') as name, json_extract(data, '$.score') as score FROM bench_json ORDER BY json_extract(data, '$.score') DESC LIMIT 10\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, ops: 51, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\tjsonEachAgg: async (c) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench_json (data) VALUES (?)\",\n\t\t\t\tJSON.stringify({ items: Array.from({ length: 100 }, (_, i) => ({ id: i, val: i * 10 })) }),\n\t\t\t);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT SUM(json_extract(value, '$.val')) as total FROM bench_json, json_each(json_extract(data, '$.items')) LIMIT 1\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, total: (rows[0] as { total: number }).total };\n\t\t},\n\n\t\tcomplexAggregation: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`grp-${i % 10}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT key, COUNT(*) as cnt, AVG(num) as avg_num, MIN(num) as min_num, MAX(num) as max_num FROM bench WHERE key LIKE 'grp-%' GROUP BY key ORDER BY cnt DESC\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, seedMs, groups: (rows as unknown[]).length };\n\t\t},\n\n\t\tcomplexSubquery: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`sq-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM bench WHERE num > (SELECT AVG(num) FROM bench) ORDER BY num DESC LIMIT 50\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\tcomplexJoin: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`j-${i}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench_secondary (bench_id, label, score) VALUES (?, ?, ?)\",\n\t\t\t\t\ti + 1, `label-${i}`, Math.random() * 100,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT b.key, b.num, s.label, s.score FROM bench b INNER JOIN bench_secondary s ON s.bench_id = b.id WHERE b.key LIKE 'j-%' ORDER BY s.score DESC LIMIT 200\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\tcomplexCteWindow: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`cte-${i % 10}`, `v-${i}`, i, Date.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(`\n\t\t\t\tWITH ranked AS (\n\t\t\t\t\tSELECT key, num, ROW_NUMBER() OVER (PARTITION BY key ORDER BY num DESC) as rn,\n\t\t\t\t\t AVG(num) OVER (PARTITION BY key) as avg_num\n\t\t\t\t\tFROM bench\n\t\t\t\t\tWHERE key LIKE 'cte-%'\n\t\t\t\t)\n\t\t\t\tSELECT * FROM ranked WHERE rn <= 3 ORDER BY key, rn\n\t\t\t`);\n\t\t\treturn { ms: performance.now() - t0, seedMs, rows: (rows as unknown[]).length };\n\t\t},\n\n\t\tmigrationTables: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS mig_${i} (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tdata TEXT NOT NULL DEFAULT ''\n\t\t\t\t)`);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tchatLogInsert: async (c, totalBytes: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\treturn { ms: performance.now() - t0, ops: seeded.rows, bytes: seeded.totalBytes };\n\t\t},\n\n\t\tchatLogSelectLimit: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: (rows as unknown[]).length,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogSelectIndexed: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst lowerBound = Math.max(0, seeded.rows - 100);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT seq, role, content_bytes FROM chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\tseeded.threadId,\n\t\t\t\tlowerBound,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: (rows as unknown[]).length,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogCount: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT COUNT(*) AS count FROM chat_log WHERE thread_id = ?\",\n\t\t\t\tseeded.threadId,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: 1,\n\t\t\t\tcount: (rows[0] as { count: number }).count,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogSum: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM chat_log WHERE thread_id = ?\",\n\t\t\t\tseeded.threadId,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: 1,\n\t\t\t\ttotalBytes: (rows[0] as { total_bytes: number | null }).total_bytes ?? 0,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tlargeTxInsert500KB: async (c) => {\n\t\t\tconst targetBytes = 500 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\tlargeTxInsert1MB: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\t// 1 MiB total, 4096 × 256 B rows. Max NAPI crossings.\n\t\tlargeTxInsert1MBTinyRows: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 256;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\t// 1 MiB total, 256 × 4 KiB rows. Same shape as largeTxInsert1MB; kept as a sanity duplicate.\n\t\tlargeTxInsert1MBMediumRows: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\t// 1 MiB total, 1 × 1 MiB row. One NAPI crossing, exercises SQLite overflow-page chain.\n\t\tlargeTxInsert1MBOneRow: async (c) => {\n\t\t\tconst rowSize = 1024 * 1024;\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\trowSize,\n\t\t\t);\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 1, bytes: rowSize };\n\t\t},\n\n\t\tlargeTxInsert5MB: async (c) => {\n\t\t\tconst targetBytes = 5 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\tlargeTxInsert10MB: async (c) => {\n\t\t\tconst targetBytes = 10 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\tlargeTxInsert50MB: async (c) => {\n\t\t\tconst targetBytes = 50 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: rowCount, bytes: rowCount * rowSize };\n\t\t},\n\n\t\t// Stress test: insert 1000 rows, delete them all, repeat 10 times.\n\t\t// Tests freelist reuse and space reclamation patterns.\n\t\tchurnInsertDelete: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS churn (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst cycles = 10;\n\t\t\tconst perCycle = 1000;\n\t\t\tfor (let cycle = 0; cycle < cycles; cycle++) {\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tfor (let i = 0; i < perCycle; i++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"INSERT INTO churn (payload) VALUES (randomblob(1024))\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait c.db.execute(\"DELETE FROM churn\");\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: cycles * perCycle,\n\t\t\t\tcycles,\n\t\t\t};\n\t\t},\n\n\t\t// Interleave inserts, updates, deletes in same transaction. Tests how\n\t\t// the VFS handles mixed page dirtying patterns.\n\t\tmixedOltpLarge: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS mixed_oltp (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\tdata BLOB NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM mixed_oltp\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))\",\n\t\t\t\t\ti,\n\t\t\t\t\ti * 2,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))\",\n\t\t\t\t\t500 + i,\n\t\t\t\t\ti * 3,\n\t\t\t\t);\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"UPDATE mixed_oltp SET value = value + 1 WHERE id = ?\",\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t\tif (i % 5 === 0) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"DELETE FROM mixed_oltp WHERE id = ?\",\n\t\t\t\t\t\ti - 50 >= 0 ? i - 50 : i,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 500 * 2 + 100 };\n\t\t},\n\n\t\t// Growing aggregation: insert then SELECT SUM after each batch.\n\t\t// Tests cache invalidation and read-after-write patterns.\n\t\tgrowingAggregation: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS agg_test (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM agg_test\");\n\t\t\tconst t0 = performance.now();\n\t\t\tconst batches = 20;\n\t\t\tconst perBatch = 100;\n\t\t\tlet lastSum = 0;\n\t\t\tfor (let batch = 0; batch < batches; batch++) {\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tfor (let i = 0; i < perBatch; i++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"INSERT INTO agg_test (value) VALUES (?)\",\n\t\t\t\t\t\tbatch * perBatch + i,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\tconst rows = (await c.db.execute(\n\t\t\t\t\t\"SELECT SUM(value) AS s FROM agg_test\",\n\t\t\t\t)) as Array<{ s: number }>;\n\t\t\t\tlastSum = rows[0]?.s ?? 0;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: batches * perBatch,\n\t\t\t\tbatches,\n\t\t\t\tlastSum,\n\t\t\t};\n\t\t},\n\n\t\t// Create index on already-populated table. Tests large rewrite patterns.\n\t\tindexCreationOnLargeTable: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS idx_test (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tkey TEXT NOT NULL,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DROP INDEX IF EXISTS idx_test_key\");\n\t\t\tawait c.db.execute(\"DELETE FROM idx_test\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 10000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO idx_test (key, value) VALUES (?, ?)\",\n\t\t\t\t\t`key-${i % 1000}-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"CREATE INDEX idx_test_key ON idx_test(key)\");\n\t\t\treturn { ms: performance.now() - t0, ops: 10000 };\n\t\t},\n\n\t\t// Update 1000 different rows in separate UPDATEs in one transaction.\n\t\t// Stresses B-tree navigation and page dirtying.\n\t\tbulkUpdate1000Rows: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS bulk_update (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM bulk_update\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bulk_update (id, value) VALUES (?, ?)\",\n\t\t\t\t\ti,\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"UPDATE bulk_update SET value = value + 1 WHERE id = ?\",\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 1000 };\n\t\t},\n\n\t\t// Delete everything then re-insert. Tests truncate+regrow cycle.\n\t\ttruncateAndRegrow: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS regrow (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\t// Seed\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO regrow (payload) VALUES (randomblob(1024))\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"DELETE FROM regrow\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO regrow (payload) VALUES (randomblob(1024))\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 500 };\n\t\t},\n\n\t\t// Many small tables vs one large. Tests schema page growth.\n\t\tmanySmallTables: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 50; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t`CREATE TABLE IF NOT EXISTS small_t_${i} (id INTEGER PRIMARY KEY, value INTEGER)`,\n\t\t\t\t);\n\t\t\t\tfor (let j = 0; j < 10; j++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t`INSERT INTO small_t_${i} (id, value) VALUES (?, ?)`,\n\t\t\t\t\t\tj,\n\t\t\t\t\t\ti * j,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 50 * 10, tables: 50 };\n\t\t},\n\t},\n});\n","import { randomBytes } from \"node:crypto\";\nimport { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_TARGET_BYTES = 50 * 1024 * 1024;\nconst DEFAULT_ROW_BYTES = 16 * 1024;\nconst DEFAULT_BATCH_ROWS = 8;\nconst DEFAULT_TRANSACTION_BYTES = 64 * 1024;\nconst READ_BATCH_ROWS = 64;\nconst REVERSE_PROBE_ROWS = 32 * 1024;\nconst PAYLOAD_TABLE = \"cold_start_payload\";\nconst REVERSE_PROBE_TABLE = \"cold_start_reverse_probe\";\n\ninterface WriteInput {\n\ttargetBytes?: number;\n\trowBytes?: number;\n\tbatchRows?: number;\n\ttransactionBytes?: number;\n}\n\ninterface PayloadRow {\n\tmin_id?: number | null;\n\tmax_id?: number | null;\n\trows: number;\n\tbytes: number;\n\texpected_bytes: number;\n}\n\ninterface PayloadValueRow {\n\tbytes: number;\n\texpected_bytes: number;\n}\n\nfunction positiveInteger(value: number | undefined, fallback: number, name: string) {\n\tconst resolved = value ?? fallback;\n\tif (!Number.isInteger(resolved) || resolved < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\treturn resolved;\n}\n\nfunction randomAsciiString(bytes: number): string {\n\treturn randomBytes(Math.ceil(bytes / 2)).toString(\"hex\").slice(0, bytes);\n}\n\nasync function readPayloads(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tdirection: \"forward\" | \"backward\" = \"forward\",\n) {\n\tconst t0 = performance.now();\n\tconst [bounds] = (await database.execute(\n\t\t`\n\t\t\tSELECT\n\t\t\t\tMIN(id) AS min_id,\n\t\t\t\tMAX(id) AS max_id,\n\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t0 AS bytes,\n\t\t\t\t0 AS expected_bytes\n\t\t\tFROM ${PAYLOAD_TABLE}\n\t\t`,\n\t)) as PayloadRow[];\n\n\tif (!bounds) throw new Error(\"read query returned no rows\");\n\n\tlet rows = 0;\n\tlet bytes = 0;\n\tlet expectedBytes = 0;\n\tlet chunks = 0;\n\tconst minId = bounds.min_id ?? 0;\n\tconst maxId = bounds.max_id ?? 0;\n\n\tif (direction === \"backward\") {\n\t\tconst [probeBounds] = (await database.execute(\n\t\t\t`\n\t\t\t\tSELECT\n\t\t\t\t\tMIN(id) AS min_id,\n\t\t\t\t\tMAX(id) AS max_id,\n\t\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t\t0 AS bytes,\n\t\t\t\t\t0 AS expected_bytes\n\t\t\t\tFROM ${REVERSE_PROBE_TABLE}\n\t\t\t`,\n\t\t)) as PayloadRow[];\n\t\tif (!probeBounds) throw new Error(\"reverse probe query returned no rows\");\n\t\tconst probeMinId = probeBounds.min_id ?? 0;\n\t\tconst probeMaxId = probeBounds.max_id ?? 0;\n\n\t\tfor (\n\t\t\tlet upperId = probeMaxId;\n\t\t\tupperId >= probeMinId && upperId > 0;\n\t\t\tupperId -= READ_BATCH_ROWS\n\t\t) {\n\t\t\tconst lowerId = Math.max(probeMinId, upperId - READ_BATCH_ROWS + 1);\n\t\t\tconst chunkRows = (await database.execute(\n\t\t\t\t`\n\t\t\t\t\tSELECT\n\t\t\t\t\t\tmarker AS bytes,\n\t\t\t\t\t\tmarker AS expected_bytes\n\t\t\t\t\tFROM ${REVERSE_PROBE_TABLE}\n\t\t\t\t\tWHERE id BETWEEN ? AND ?\n\t\t\t\t\tORDER BY id DESC\n\t\t\t\t`,\n\t\t\t\tlowerId,\n\t\t\t\tupperId,\n\t\t\t)) as PayloadValueRow[];\n\n\t\t\tfor (const row of chunkRows) {\n\t\t\t\trows += 1;\n\t\t\t\tbytes += row.bytes;\n\t\t\t\texpectedBytes += row.expected_bytes;\n\t\t\t}\n\t\t\tchunks += 1;\n\t\t}\n\n\t\treturn {\n\t\t\tms: performance.now() - t0,\n\t\t\tops: rows,\n\t\t\trows,\n\t\t\tbytes,\n\t\t\texpectedBytes,\n\t\t\tchunks,\n\t\t\treadBatchRows: READ_BATCH_ROWS,\n\t\t\tdirection,\n\t\t};\n\t}\n\n\tfor (\n\t\tlet lowerId = minId;\n\t\tlowerId <= maxId;\n\t\tlowerId += READ_BATCH_ROWS\n\t) {\n\t\tconst upperId = lowerId + READ_BATCH_ROWS - 1;\n\t\tconst [chunk] = (await database.execute(\n\t\t\t`\n\t\t\t\tSELECT\n\t\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t\tCOALESCE(SUM(length(payload)), 0) AS bytes,\n\t\t\t\t\tCOALESCE(SUM(payload_bytes), 0) AS expected_bytes\n\t\t\t\tFROM ${PAYLOAD_TABLE}\n\t\t\t\tWHERE id BETWEEN ? AND ?\n\t\t\t`,\n\t\t\tlowerId,\n\t\t\tupperId,\n\t\t)) as PayloadRow[];\n\t\tif (!chunk) throw new Error(\"chunked read query returned no rows\");\n\n\t\trows += chunk.rows;\n\t\tbytes += chunk.bytes;\n\t\texpectedBytes += chunk.expected_bytes;\n\t\tchunks += 1;\n\t}\n\n\treturn {\n\t\tms: performance.now() - t0,\n\t\tops: rows,\n\t\trows,\n\t\tbytes,\n\t\texpectedBytes,\n\t\tchunks,\n\t\treadBatchRows: READ_BATCH_ROWS,\n\t};\n}\n\nexport const sqliteColdStartBench = actor({\n\toptions: {\n\t\tactionTimeout: 600_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cold_start_payload (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cold_start_reverse_probe (\n\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\tmarker INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(`DELETE FROM ${PAYLOAD_TABLE}`);\n\t\t\tawait c.db.execute(`DELETE FROM ${REVERSE_PROBE_TABLE}`);\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\twriteRandomStrings: async (c, input: WriteInput = {}) => {\n\t\t\tconst targetBytes = positiveInteger(\n\t\t\t\tinput.targetBytes,\n\t\t\t\tDEFAULT_TARGET_BYTES,\n\t\t\t\t\"targetBytes\",\n\t\t\t);\n\t\t\tconst rowBytes = positiveInteger(input.rowBytes, DEFAULT_ROW_BYTES, \"rowBytes\");\n\t\t\tconst batchRows = positiveInteger(\n\t\t\t\tinput.batchRows,\n\t\t\t\tDEFAULT_BATCH_ROWS,\n\t\t\t\t\"batchRows\",\n\t\t\t);\n\t\t\tconst transactionBytes = positiveInteger(\n\t\t\t\tinput.transactionBytes,\n\t\t\t\tDEFAULT_TRANSACTION_BYTES,\n\t\t\t\t\"transactionBytes\",\n\t\t\t);\n\t\t\tconst createdAt = Date.now();\n\t\t\tlet remainingBytes = targetBytes;\n\t\t\tlet rows = 0;\n\t\t\tlet transactions = 0;\n\t\t\tlet randomStringMs = 0;\n\t\t\tlet sqliteInsertMs = 0;\n\t\t\tlet commitMs = 0;\n\t\t\tlet inTransaction = false;\n\n\t\t\tconst wallT0 = performance.now();\n\t\t\ttry {\n\t\t\t\twhile (remainingBytes > 0) {\n\t\t\t\t\tlet transactionRemainingBytes = Math.min(\n\t\t\t\t\t\ttransactionBytes,\n\t\t\t\t\t\tremainingBytes,\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\t\tinTransaction = true;\n\t\t\t\t\ttransactions += 1;\n\n\t\t\t\t\twhile (transactionRemainingBytes > 0) {\n\t\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\t\tconst generateT0 = performance.now();\n\n\t\t\t\t\t\tfor (\n\t\t\t\t\t\t\tlet batchIndex = 0;\n\t\t\t\t\t\t\tbatchIndex < batchRows &&\n\t\t\t\t\t\t\ttransactionRemainingBytes > 0 &&\n\t\t\t\t\t\t\tremainingBytes > 0;\n\t\t\t\t\t\t\tbatchIndex += 1\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst payloadBytes = Math.min(\n\t\t\t\t\t\t\t\trowBytes,\n\t\t\t\t\t\t\t\ttransactionRemainingBytes,\n\t\t\t\t\t\t\t\tremainingBytes,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?)\");\n\t\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\t\trandomAsciiString(payloadBytes),\n\t\t\t\t\t\t\t\tpayloadBytes,\n\t\t\t\t\t\t\t\tcreatedAt + rows,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\ttransactionRemainingBytes -= payloadBytes;\n\t\t\t\t\t\t\tremainingBytes -= payloadBytes;\n\t\t\t\t\t\t\trows += 1;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trandomStringMs += performance.now() - generateT0;\n\t\t\t\t\t\tconst insertT0 = performance.now();\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`INSERT INTO ${PAYLOAD_TABLE} (payload, payload_bytes, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t\t...args,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsqliteInsertMs += performance.now() - insertT0;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst commitT0 = performance.now();\n\t\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\t\tcommitMs += performance.now() - commitT0;\n\t\t\t\t\tinTransaction = false;\n\t\t\t\t}\n\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tinTransaction = true;\n\t\t\t\tfor (\n\t\t\t\t\tlet lowerId = 1;\n\t\t\t\t\tlowerId <= REVERSE_PROBE_ROWS;\n\t\t\t\t\tlowerId += 256\n\t\t\t\t) {\n\t\t\t\t\tconst upperId = Math.min(REVERSE_PROBE_ROWS, lowerId + 255);\n\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\tfor (let id = lowerId; id <= upperId; id += 1) {\n\t\t\t\t\t\tplaceholders.push(\"(?, ?)\");\n\t\t\t\t\t\targs.push(id, 1);\n\t\t\t\t\t}\n\t\t\t\t\tconst insertT0 = performance.now();\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t`INSERT INTO ${REVERSE_PROBE_TABLE} (id, marker) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t...args,\n\t\t\t\t\t);\n\t\t\t\t\tsqliteInsertMs += performance.now() - insertT0;\n\t\t\t\t}\n\t\t\t\tconst reverseCommitT0 = performance.now();\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\tcommitMs += performance.now() - reverseCommitT0;\n\t\t\t\tinTransaction = false;\n\n\t\t\t\treturn {\n\t\t\t\t\tms: sqliteInsertMs + commitMs,\n\t\t\t\t\twriteWallMs: performance.now() - wallT0,\n\t\t\t\t\trandomStringMs,\n\t\t\t\t\tsqliteInsertMs,\n\t\t\t\t\tcommitMs,\n\t\t\t\t\tops: rows,\n\t\t\t\t\trows,\n\t\t\t\t\ttransactions,\n\t\t\t\t\tbytes: targetBytes,\n\t\t\t\t\trowBytes,\n\t\t\t\t\tbatchRows,\n\t\t\t\t\ttransactionBytes,\n\t\t\t\t\treverseProbeRows: REVERSE_PROBE_ROWS,\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (inTransaction) {\n\t\t\t\t\tawait c.db.execute(\"ROLLBACK\");\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\treadAll: async (c) => {\n\t\t\treturn readPayloads(c.db);\n\t\t},\n\n\t\treadAllReverse: async (c) => {\n\t\t\treturn readPayloads(c.db, \"backward\");\n\t\t},\n\n\t\twakeSqlite: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst [row] = (await c.db.execute(\n\t\t\t\t`SELECT COUNT(*) AS rows FROM ${PAYLOAD_TABLE} WHERE id = -1`,\n\t\t\t)) as Array<{ rows: number }>;\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\trows: row?.rows ?? 0,\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_ROW_BYTES = 2 * 1024;\nconst ORDER_BATCH_ROWS = 50;\nconst DOC_BATCH_ROWS = 75;\nconst LEDGER_BATCH_ROWS = 100;\nconst POINT_LOOKUP_OPS = 1_000;\nconst RANGE_CHUNK_ROWS = 512;\nconst SETUP_TRANSACTION_ROWS = 128;\nconst FEED_PAGE_ROWS = 100;\nconst CHAT_LOG_CHUNK_BYTES = 4 * 1024;\nconst CHAT_LOG_INSERT_BATCH_SIZE = 50;\nconst CHAT_THREAD_ID = \"rw-chat-main\";\nconst SQL_RUSH_MSGS_COUNT = 2_500;\nconst SQL_RUSH_TOOL_REFS_COUNT = 240;\nconst SQL_RUSH_EVENTS_COUNT = 700;\nconst SQL_RUSH_KV_COUNT = 40;\nconst SQL_RUSH_TOOLS_COUNT = 41;\nconst SQL_RUSH_META_COUNT = 12;\n\n// Keep this list in sync with the runner's workload catalog. The runner owns\n// the per-workload rationale and expected cache/VFS behavior so benchmark\n// result artifacts can preserve that intent over time.\nconst WORKLOADS = [\n\t\"small-rowid-point\",\n\t\"small-schema-read\",\n\t\"small-range-scan\",\n\t\"rowid-range-forward\",\n\t\"rowid-range-backward\",\n\t\"secondary-index-covering-range\",\n\t\"secondary-index-scattered-table\",\n\t\"aggregate-status\",\n\t\"aggregate-time-bucket\",\n\t\"aggregate-tenant-time-range\",\n\t\"parallel-read-aggregates\",\n\t\"parallel-read-write-transition\",\n\t\"feed-order-by-limit\",\n\t\"feed-pagination-adjacent\",\n\t\"join-order-items\",\n\t\"random-point-lookups\",\n\t\"hot-index-cold-table\",\n\t\"ledger-without-rowid-range\",\n\t\"chat-log-select-limit\",\n\t\"chat-log-select-indexed\",\n\t\"chat-log-count\",\n\t\"chat-log-sum\",\n\t\"chat-tool-read-fanout\",\n\t\"chat-tool-script\",\n\t\"write-batch-after-wake\",\n\t\"update-hot-partition\",\n\t\"delete-churn-range-read\",\n\t\"migration-create-indexes-large\",\n\t\"migration-create-indexes-skewed-large\",\n\t\"migration-table-rebuild-large\",\n\t\"migration-add-column-large\",\n\t\"migration-ddl-small\",\n] as const;\n\ntype WorkloadName = (typeof WORKLOADS)[number];\n\ninterface SetupInput {\n\tworkload: WorkloadName;\n\ttargetBytes?: number;\n\trowBytes?: number;\n}\n\ninterface RunInput {\n\tworkload: WorkloadName;\n\ttargetBytes?: number;\n}\n\ninterface CountRow {\n\trows: number;\n}\n\ninterface PageCountRow {\n\tpage_count: number;\n}\n\ninterface CacheSizeRow {\n\tcache_size: number;\n}\n\ninterface PageSizeRow {\n\tpage_size: number;\n}\n\ninterface BytesRow {\n\tbytes: number;\n\trows?: number;\n}\n\ninterface AggregateRow {\n\trows: number;\n\ttotal: number;\n}\n\nfunction positiveInteger(value: number | undefined, fallback: number, name: string) {\n\tconst resolved = value ?? fallback;\n\tif (!Number.isInteger(resolved) || resolved < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\treturn resolved;\n}\n\nfunction assertWorkload(workload: string): asserts workload is WorkloadName {\n\tif (!(WORKLOADS as readonly string[]).includes(workload)) {\n\t\tthrow new Error(`unknown SQLite benchmark workload: ${workload}`);\n\t}\n}\n\nfunction pseudoRandom(value: number) {\n\treturn Math.imul(value ^ 0x9e3779b9, 0x85ebca6b) >>> 0;\n}\n\nfunction paddedHex(value: number) {\n\treturn pseudoRandom(value).toString(16).padStart(8, \"0\");\n}\n\nfunction payload(prefix: string, bytes: number) {\n\treturn prefix + \"x\".repeat(Math.max(0, bytes - prefix.length));\n}\n\nfunction typedRows(rows: unknown[]): T[] {\n\treturn rows as T[];\n}\n\nasync function queryPageCount(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tconst [row] = typedRows(await database.execute(\"PRAGMA page_count\"));\n\treturn row?.page_count ?? 0;\n}\n\nasync function resetCommerce(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_order_items\");\n\tawait database.execute(\"DELETE FROM rw_orders\");\n\tawait database.execute(\"DELETE FROM rw_customers\");\n\tawait database.execute(\"DELETE FROM rw_events\");\n}\n\nasync function resetDocs(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_docs\");\n}\n\nasync function resetLedger(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_ledger\");\n}\n\nasync function resetChatLog(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_chat_log\");\n}\n\nasync function resetSqlRush(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM tool_refs\");\n\tawait database.execute(\"DELETE FROM msgs\");\n\tawait database.execute(\"DELETE FROM events\");\n\tawait database.execute(\"DELETE FROM kv\");\n\tawait database.execute(\"DELETE FROM tools\");\n\tawait database.execute(\"DELETE FROM meta\");\n}\n\nasync function resetMigration(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_rw_migration_source_account\");\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_rw_migration_source_created\");\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_rw_migration_source_status_total\");\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_rw_migration_source_skew_account\");\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_rw_migration_source_skew_status\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_source_rebuilt\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_source\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_audit\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_empty\");\n}\n\nasync function withTransaction(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tfn: () => Promise,\n) {\n\tlet inTransaction = false;\n\tawait database.execute(\"BEGIN\");\n\tinTransaction = true;\n\ttry {\n\t\tawait fn();\n\t\tawait database.execute(\"COMMIT\");\n\t\tinTransaction = false;\n\t} catch (err) {\n\t\tif (inTransaction) {\n\t\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function seedCommerce(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetCommerce(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst customerCount = Math.max(32, Math.ceil(rows / 16));\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\tfor (let offset = 0; offset < customerCount; offset += ORDER_BATCH_ROWS) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\t\t\tconst batchEnd = Math.min(customerCount, offset + ORDER_BATCH_ROWS);\n\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\ti + 1,\n\t\t\t\t\t`acct-${i % 64}`,\n\t\t\t\t\t`user-${paddedHex(i)}@example.test`,\n\t\t\t\t\t[\"free\", \"pro\", \"team\", \"enterprise\"][i % 4],\n\t\t\t\t\t[\"iad\", \"sfo\", \"fra\", \"sin\"][i % 4],\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO rw_customers (id, account_id, email, plan, region) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\t});\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) {\n\t\t\t\tconst orderPlaceholders: string[] = [];\n\t\t\t\tconst orderArgs: unknown[] = [];\n\t\t\t\tconst itemPlaceholders: string[] = [];\n\t\t\t\tconst itemArgs: unknown[] = [];\n\t\t\t\tconst eventPlaceholders: string[] = [];\n\t\t\t\tconst eventArgs: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS);\n\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst id = i + 1;\n\t\t\t\t\tconst customerId = (pseudoRandom(i) % customerCount) + 1;\n\t\t\t\t\tconst createdAt = 1_700_000_000_000 + i * 1000;\n\t\t\t\t\tconst status = [\"pending\", \"paid\", \"shipped\", \"refunded\"][i % 4];\n\t\t\t\t\tconst totalCents = 500 + (pseudoRandom(i + 17) % 25_000);\n\t\t\t\t\tconst note = payload(`order-${id}-${status}:`, rowBytes);\n\n\t\t\t\t\torderPlaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\torderArgs.push(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tcustomerId,\n\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\ttotalCents,\n\t\t\t\t\t\ti % 128,\n\t\t\t\t\t\tnote,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (let item = 0; item < 2; item += 1) {\n\t\t\t\t\t\titemPlaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\t\titemArgs.push(\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t`sku-${paddedHex(i + item).slice(0, 6)}`,\n\t\t\t\t\t\t\t1 + ((i + item) % 5),\n\t\t\t\t\t\t\t100 + (pseudoRandom(i + item + 31) % 5000),\n\t\t\t\t\t\t\titem,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\teventPlaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\teventArgs.push(\n\t\t\t\t\t\t`acct-${customerId % 64}`,\n\t\t\t\t\t\t[\"click\", \"purchase\", \"refund\", \"shipment\"][i % 4],\n\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t`order:${id}`,\n\t\t\t\t\t\tpayload(`event-${id}:`, Math.min(rowBytes, 512)),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${orderPlaceholders.join(\", \")}`,\n\t\t\t\t\t...orderArgs,\n\t\t\t\t);\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_order_items (order_id, sku, quantity, price_cents, line_no) VALUES ${itemPlaceholders.join(\", \")}`,\n\t\t\t\t\t...itemArgs,\n\t\t\t\t);\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_events (account_id, event_type, created_at, entity_key, properties) VALUES ${eventPlaceholders.join(\", \")}`,\n\t\t\t\t\t...eventArgs,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedDocs(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetDocs(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (let offset = txStart; offset < txEnd; offset += DOC_BATCH_ROWS) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + DOC_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst rank = pseudoRandom(i);\n\t\t\t\t\tconst body = payload(`doc-${i}-${rank}:`, rowBytes);\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\t`doc-${paddedHex(i)}`,\n\t\t\t\t\t\trank,\n\t\t\t\t\t\t`tenant-${rank % 128}`,\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\trowBytes,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_docs (external_key, row_rank, tenant_id, body, body_bytes) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedLedger(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetLedger(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (let offset = txStart; offset < txEnd; offset += LEDGER_BATCH_ROWS) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + LEDGER_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst accountId = `acct-${String(i % 256).padStart(4, \"0\")}`;\n\t\t\t\t\tconst entryId = Math.floor(i / 256) + 1;\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\taccountId,\n\t\t\t\t\t\tentryId,\n\t\t\t\t\t\t(i % 2 === 0 ? 1 : -1) * (100 + (i % 10_000)),\n\t\t\t\t\t\t1_700_000_000_000 + i * 1000,\n\t\t\t\t\t\tpayload(`ledger-${accountId}-${entryId}:`, Math.min(rowBytes, 512)),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_ledger (account_id, entry_id, amount_cents, created_at, memo) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nfunction buildChatLogMessage(seq: number, targetBytes: number): string {\n\tconst prefix = `message-${seq}: `;\n\treturn prefix + \"x\".repeat(Math.max(0, targetBytes - prefix.length));\n}\n\nasync function seedChatLog(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tawait resetChatLog(database);\n\tconst createdAtBase = 1_700_000_000_000;\n\tlet remainingBytes = targetBytes;\n\tlet rows = 0;\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\twhile (remainingBytes > 0) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\n\t\t\tfor (\n\t\t\t\tlet batchIndex = 0;\n\t\t\t\tbatchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0;\n\t\t\t\tbatchIndex += 1\n\t\t\t) {\n\t\t\t\tconst contentBytes = Math.min(CHAT_LOG_CHUNK_BYTES, remainingBytes);\n\t\t\t\tconst seq = rows;\n\t\t\t\tconst role = seq % 2 === 0 ? \"user\" : \"assistant\";\n\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\tseq,\n\t\t\t\t\trole,\n\t\t\t\t\tbuildChatLogMessage(seq, contentBytes),\n\t\t\t\t\tcontentBytes,\n\t\t\t\t\tMath.ceil(contentBytes / 4),\n\t\t\t\t\tcreatedAtBase + seq,\n\t\t\t\t);\n\n\t\t\t\tremainingBytes -= contentBytes;\n\t\t\t\trows += 1;\n\t\t\t}\n\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO rw_chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes: CHAT_LOG_CHUNK_BYTES,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function batchInsert(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tsql: string,\n\trows: unknown[][],\n\tbatchSize: number,\n) {\n\tif (rows.length === 0) return;\n\tconst colsPerRow = rows[0]?.length ?? 0;\n\tif (colsPerRow === 0) return;\n\tconst placeholder = `(${\"?,\".repeat(colsPerRow).slice(0, -1)})`;\n\tfor (let i = 0; i < rows.length; i += batchSize) {\n\t\tconst chunk = rows.slice(i, i + batchSize);\n\t\tconst values = new Array(chunk.length).fill(placeholder).join(\",\");\n\t\tconst args: unknown[] = [];\n\t\tfor (const row of chunk) args.push(...row);\n\t\tawait database.execute(`${sql} VALUES ${values}`, ...args);\n\t}\n}\n\nasync function seedSqlRush(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tawait resetSqlRush(database);\n\tconst now = 1_700_000_000_000;\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\tconst msgsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_MSGS_COUNT; i += 1) {\n\t\t\tmsgsRows.push([\n\t\t\t\ti === 0 ? null : i,\n\t\t\t\ti % 3 === 0 ? \"user\" : \"assistant\",\n\t\t\t\tpayload(\"msg:\", 512),\n\t\t\t\t0,\n\t\t\t\tnow - (SQL_RUSH_MSGS_COUNT - i) * 1000,\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO msgs (parent, role, content, cancelled, created_at)\",\n\t\t\tmsgsRows,\n\t\t\t50,\n\t\t);\n\n\t\tconst toolRefsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_TOOL_REFS_COUNT; i += 1) {\n\t\t\ttoolRefsRows.push([\n\t\t\t\ti + 1,\n\t\t\t\t`tool_${i % 20}`,\n\t\t\t\t`call_${i}`,\n\t\t\t\ti % 5 === 0 ? \"pending\" : \"done\",\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO tool_refs (msg_id, tool_name, tool_call_id, status)\",\n\t\t\ttoolRefsRows,\n\t\t\t100,\n\t\t);\n\n\t\tconst eventsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_EVENTS_COUNT; i += 1) {\n\t\t\teventsRows.push([\n\t\t\t\ti + 1,\n\t\t\t\t`event_${i % 8}`,\n\t\t\t\tpayload(\"event:\", 256),\n\t\t\t\tnow - (SQL_RUSH_EVENTS_COUNT - i) * 100,\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO events (seq, event_type, payload, created_at)\",\n\t\t\teventsRows,\n\t\t\t100,\n\t\t);\n\n\t\tconst kvRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_KV_COUNT; i += 1) {\n\t\t\tkvRows.push([`kv_${i}`, payload(\"kv:\", 128), now]);\n\t\t}\n\t\tawait batchInsert(database, \"INSERT INTO kv (key, value, updated_at)\", kvRows, 40);\n\n\t\tconst toolsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_TOOLS_COUNT; i += 1) {\n\t\t\ttoolsRows.push([\"exec-1\", `tool_${i}`, payload(\"tool:\", 1024), now]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO tools (executor_id, name, spec, updated_at)\",\n\t\t\ttoolsRows,\n\t\t\t41,\n\t\t);\n\n\t\tconst metaRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_META_COUNT; i += 1) {\n\t\t\tmetaRows.push([`key_${i}`, payload(\"meta:\", 64)]);\n\t\t}\n\t\tawait batchInsert(database, \"INSERT INTO meta (key, value)\", metaRows, 12);\n\t});\n\n\treturn {\n\t\trows:\n\t\t\tSQL_RUSH_MSGS_COUNT +\n\t\t\tSQL_RUSH_TOOL_REFS_COUNT +\n\t\t\tSQL_RUSH_EVENTS_COUNT +\n\t\t\tSQL_RUSH_KV_COUNT +\n\t\t\tSQL_RUSH_TOOLS_COUNT +\n\t\t\tSQL_RUSH_META_COUNT,\n\t\ttargetBytes,\n\t\trowBytes: 0,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedMigrationSource(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n\tskewed = false,\n) {\n\tawait resetMigration(database);\n\tawait database.execute(`CREATE TABLE rw_migration_source (\n\t\tid INTEGER PRIMARY KEY,\n\t\taccount_id TEXT NOT NULL,\n\t\tstatus TEXT NOT NULL,\n\t\tcreated_at INTEGER NOT NULL,\n\t\ttotal_cents INTEGER NOT NULL,\n\t\tbody TEXT NOT NULL\n\t)`);\n\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (let offset = txStart; offset < txEnd; offset += ORDER_BATCH_ROWS) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst accountId = skewed\n\t\t\t\t\t\t? `acct-${i % 10 === 0 ? i % 512 : i % 8}`\n\t\t\t\t\t\t: `acct-${pseudoRandom(i) % 512}`;\n\t\t\t\t\tconst status = skewed\n\t\t\t\t\t\t? i % 20 === 0\n\t\t\t\t\t\t\t? \"failed\"\n\t\t\t\t\t\t\t: \"open\"\n\t\t\t\t\t\t: [\"open\", \"closed\", \"failed\", \"pending\"][i % 4];\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\ti + 1,\n\t\t\t\t\t\taccountId,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t1_700_000_000_000 + i * 1000,\n\t\t\t\t\t\t100 + (pseudoRandom(i + 41) % 50_000),\n\t\t\t\t\t\tpayload(`migration-${i}:`, rowBytes),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_migration_source (id, account_id, status, created_at, total_cents, body) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function readRowidRange(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tdirection: \"forward\" | \"backward\",\n) {\n\tconst [count] = typedRows(\n\t\tawait database.execute(\"SELECT COUNT(*) AS rows FROM rw_orders\"),\n\t);\n\tconst rows = count?.rows ?? 0;\n\tlet bytes = 0;\n\tlet scannedRows = 0;\n\n\tif (direction === \"backward\") {\n\t\tfor (let upper = rows; upper > 0; upper -= RANGE_CHUNK_ROWS) {\n\t\t\tconst lower = Math.max(1, upper - RANGE_CHUNK_ROWS + 1);\n\t\t\tconst chunk = typedRows(\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`SELECT length(note) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ? ORDER BY id DESC`,\n\t\t\t\t\tlower,\n\t\t\t\t\tupper,\n\t\t\t\t),\n\t\t\t);\n\t\t\tfor (const row of chunk) {\n\t\t\t\tbytes += row.bytes;\n\t\t\t\tscannedRows += 1;\n\t\t\t}\n\t\t}\n\t\treturn { rows: scannedRows, bytes };\n\t}\n\n\tfor (let lower = 1; lower <= rows; lower += RANGE_CHUNK_ROWS) {\n\t\tconst upper = lower + RANGE_CHUNK_ROWS - 1;\n\t\tconst [chunk] = typedRows<{ rows: number; bytes: number }>(\n\t\t\tawait database.execute(\n\t\t\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(length(note)), 0) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ?`,\n\t\t\t\tlower,\n\t\t\t\tupper,\n\t\t\t),\n\t\t);\n\t\tbytes += chunk?.bytes ?? 0;\n\t\tscannedRows += chunk?.rows ?? 0;\n\t}\n\n\treturn { rows: scannedRows, bytes };\n}\n\nexport const sqliteRealworldBench = actor({\n\toptions: {\n\t\tactionTimeout: 1_200_000,\n\t\tsleepGracePeriod: 30_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_customers (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\temail TEXT NOT NULL,\n\t\t\t\tplan TEXT NOT NULL,\n\t\t\t\tregion TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_orders (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tcustomer_id INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\ttotal_cents INTEGER NOT NULL,\n\t\t\t\tshard INTEGER NOT NULL,\n\t\t\t\tnote TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_order_items (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\tsku TEXT NOT NULL,\n\t\t\t\tquantity INTEGER NOT NULL,\n\t\t\t\tprice_cents INTEGER NOT NULL,\n\t\t\t\tline_no INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_events (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\tevent_type TEXT NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tentity_key TEXT NOT NULL,\n\t\t\t\tproperties TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_docs (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\texternal_key TEXT NOT NULL UNIQUE,\n\t\t\t\trow_rank INTEGER NOT NULL,\n\t\t\t\ttenant_id TEXT NOT NULL,\n\t\t\t\tbody TEXT NOT NULL,\n\t\t\t\tbody_bytes INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_ledger (\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\tentry_id INTEGER NOT NULL,\n\t\t\t\tamount_cents INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tmemo TEXT NOT NULL,\n\t\t\t\tPRIMARY KEY (account_id, entry_id)\n\t\t\t) WITHOUT ROWID`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_chat_log (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tthread_id TEXT NOT NULL,\n\t\t\t\tseq INTEGER NOT NULL,\n\t\t\t\trole TEXT NOT NULL,\n\t\t\t\tcontent TEXT NOT NULL,\n\t\t\t\tcontent_bytes INTEGER NOT NULL,\n\t\t\t\ttoken_estimate INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY AUTOINCREMENT, parent INTEGER, role TEXT NOT NULL, content TEXT NOT NULL, cancelled INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS tool_refs (id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id INTEGER NOT NULL, tool_name TEXT NOT NULL, tool_call_id TEXT NOT NULL, status TEXT NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY, event_type TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS tools (id INTEGER PRIMARY KEY AUTOINCREMENT, executor_id TEXT NOT NULL, name TEXT NOT NULL, spec TEXT NOT NULL, updated_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_customer_created ON rw_orders(customer_id, created_at DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_status_created ON rw_orders(status, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_created ON rw_orders(created_at DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_order_items_order ON rw_order_items(order_id)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_events_account_created ON rw_events(account_id, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_docs_external_rank ON rw_docs(external_key, row_rank)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_docs_tenant_rank ON rw_docs(tenant_id, row_rank)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_chat_log_thread_seq ON rw_chat_log(thread_id, seq DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_msgs_parent_role_cancelled_created_at ON msgs (parent, role, cancelled, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_tool_refs_msg_id ON tool_refs (msg_id)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tinspectCacheConfig: async (c) => {\n\t\t\tconst [cacheSize] = typedRows(\n\t\t\t\tawait c.db.execute(\"PRAGMA cache_size\"),\n\t\t\t);\n\t\t\tconst [pageSize] = typedRows(\n\t\t\t\tawait c.db.execute(\"PRAGMA page_size\"),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tsqliteCacheSizePragma: cacheSize?.cache_size ?? null,\n\t\t\t\tsqlitePageSize: pageSize?.page_size ?? null,\n\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t};\n\t\t},\n\n\t\tsetupWorkload: async (c, input: SetupInput) => {\n\t\t\tassertWorkload(input.workload);\n\t\t\tconst rowBytes = positiveInteger(input.rowBytes, DEFAULT_ROW_BYTES, \"rowBytes\");\n\t\t\tif (input.workload === \"migration-ddl-small\") {\n\t\t\t\tawait resetMigration(c.db);\n\t\t\t\treturn {\n\t\t\t\t\trows: 0,\n\t\t\t\t\ttargetBytes: 0,\n\t\t\t\t\trowBytes,\n\t\t\t\t\tsetupMs: 0,\n\t\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst targetBytes = positiveInteger(\n\t\t\t\tinput.targetBytes,\n\t\t\t\t8 * 1024 * 1024,\n\t\t\t\t\"targetBytes\",\n\t\t\t);\n\n\t\t\tswitch (input.workload) {\n\t\t\t\tcase \"small-rowid-point\":\n\t\t\t\tcase \"small-schema-read\":\n\t\t\t\tcase \"small-range-scan\":\n\t\t\t\tcase \"rowid-range-forward\":\n\t\t\t\tcase \"rowid-range-backward\":\n\t\t\t\tcase \"aggregate-status\":\n\t\t\t\tcase \"aggregate-time-bucket\":\n\t\t\t\tcase \"aggregate-tenant-time-range\":\n\t\t\t\tcase \"parallel-read-aggregates\":\n\t\t\t\tcase \"parallel-read-write-transition\":\n\t\t\t\tcase \"feed-order-by-limit\":\n\t\t\t\tcase \"feed-pagination-adjacent\":\n\t\t\t\tcase \"join-order-items\":\n\t\t\t\tcase \"random-point-lookups\":\n\t\t\t\tcase \"write-batch-after-wake\":\n\t\t\t\tcase \"update-hot-partition\":\n\t\t\t\tcase \"delete-churn-range-read\":\n\t\t\t\t\treturn seedCommerce(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"secondary-index-covering-range\":\n\t\t\t\tcase \"secondary-index-scattered-table\":\n\t\t\t\tcase \"hot-index-cold-table\":\n\t\t\t\t\treturn seedDocs(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"ledger-without-rowid-range\":\n\t\t\t\t\treturn seedLedger(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"chat-log-select-limit\":\n\t\t\t\tcase \"chat-log-select-indexed\":\n\t\t\t\tcase \"chat-log-count\":\n\t\t\t\tcase \"chat-log-sum\":\n\t\t\t\tcase \"chat-tool-read-fanout\":\n\t\t\t\t\treturn seedChatLog(c.db, targetBytes);\n\t\t\t\tcase \"chat-tool-script\":\n\t\t\t\t\treturn seedSqlRush(c.db, targetBytes);\n\t\t\t\tcase \"migration-create-indexes-large\":\n\t\t\t\t\treturn seedMigrationSource(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"migration-create-indexes-skewed-large\":\n\t\t\t\t\treturn seedMigrationSource(c.db, targetBytes, rowBytes, true);\n\t\t\t\tcase \"migration-table-rebuild-large\":\n\t\t\t\tcase \"migration-add-column-large\":\n\t\t\t\t\treturn seedMigrationSource(c.db, targetBytes, rowBytes);\n\t\t\t}\n\t\t},\n\n\t\trunWorkload: async (c, input: RunInput) => {\n\t\t\tassertWorkload(input.workload);\n\t\t\tconst t0 = performance.now();\n\t\t\tlet details: Record;\n\n\t\t\tswitch (input.workload) {\n\t\t\t\tcase \"small-rowid-point\": {\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (let i = 0; i < 50; i += 1) {\n\t\t\t\t\t\tconst id = (i % 16) + 1;\n\t\t\t\t\t\tconst [row] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT length(note) AS bytes FROM rw_orders WHERE id = ?\",\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += row?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { ops: 50, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"small-schema-read\": {\n\t\t\t\t\tconst tables = await c.db.execute(\n\t\t\t\t\t\t\"SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index') ORDER BY name\",\n\t\t\t\t\t);\n\t\t\t\t\tconst columns = await c.db.execute(\"PRAGMA table_info(rw_orders)\");\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\"SELECT COUNT(*) AS rows FROM rw_orders\"),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tobjects: tables.length,\n\t\t\t\t\t\tcolumns: columns.length,\n\t\t\t\t\t\trows: count?.rows ?? 0,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"small-range-scan\":\n\t\t\t\tcase \"rowid-range-forward\": {\n\t\t\t\t\tdetails = await readRowidRange(c.db, \"forward\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"rowid-range-backward\": {\n\t\t\t\t\tdetails = await readRowidRange(c.db, \"backward\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"secondary-index-covering-range\": {\n\t\t\t\t\tconst rows = typedRows<{ external_key: string; row_rank: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT external_key, row_rank FROM rw_docs\n\t\t\t\t\t\tWHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff'\n\t\t\t\t\t\tORDER BY external_key`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet checksum = 0;\n\t\t\t\t\tfor (const row of rows) checksum = (checksum + row.row_rank) >>> 0;\n\t\t\t\t\tdetails = { rows: rows.length, checksum };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"secondary-index-scattered-table\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT body_bytes AS bytes FROM rw_docs\n\t\t\t\t\t\tWHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff'\n\t\t\t\t\t\tORDER BY external_key`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of rows) bytes += row.bytes;\n\t\t\t\t\tdetails = { rows: rows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-status\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-time-bucket\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY bucket\n\t\t\t\t\t\tORDER BY bucket`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tbuckets: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-tenant-time-range\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total\n\t\t\t\t\t\tFROM rw_events e\n\t\t\t\t\t\tJOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER)\n\t\t\t\t\t\tWHERE e.account_id = ? AND e.created_at BETWEEN ? AND ?\n\t\t\t\t\t\tGROUP BY e.event_type\n\t\t\t\t\t\tORDER BY e.event_type`,\n\t\t\t\t\t\t\t\"acct-7\",\n\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\t1_700_000_000_000 + 86_400_000,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"parallel-read-aggregates\": {\n\t\t\t\t\tconst [\n\t\t\t\t\t\tstatusRows,\n\t\t\t\t\t\tbucketRows,\n\t\t\t\t\t\ttenantRows,\n\t\t\t\t\t\tjoinRows,\n\t\t\t\t\t] = await Promise.all([\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t`SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY bucket\n\t\t\t\t\t\tORDER BY bucket`,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t`SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total\n\t\t\t\t\t\tFROM rw_events e\n\t\t\t\t\t\tJOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER)\n\t\t\t\t\t\tWHERE e.account_id = ? AND e.created_at BETWEEN ? AND ?\n\t\t\t\t\t\tGROUP BY e.event_type\n\t\t\t\t\t\tORDER BY e.event_type`,\n\t\t\t\t\t\t\t\"acct-7\",\n\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\t1_700_000_000_000 + 86_400_000,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t]);\n\t\t\t\t\tconst aggregates = [\n\t\t\t\t\t\t...typedRows(statusRows),\n\t\t\t\t\t\t...typedRows(bucketRows),\n\t\t\t\t\t\t...typedRows(tenantRows),\n\t\t\t\t\t\t...typedRows(joinRows),\n\t\t\t\t\t];\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\tgroups: aggregates.length,\n\t\t\t\t\t\trows: aggregates.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: aggregates.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"parallel-read-write-transition\": {\n\t\t\t\t\tconst readStatus = c.db.execute(\n\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t);\n\t\t\t\t\tconst readJoin = c.db.execute(\n\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t);\n\t\t\t\t\tconst writeHotShard = c.db.execute(\n\t\t\t\t\t\t\"UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 7\",\n\t\t\t\t\t);\n\t\t\t\t\tconst readAfterWrite = c.db.execute(\n\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 7\",\n\t\t\t\t\t);\n\t\t\t\t\tconst [statusRows, joinRows, , shardRows] = await Promise.all([\n\t\t\t\t\t\treadStatus,\n\t\t\t\t\t\treadJoin,\n\t\t\t\t\t\twriteHotShard,\n\t\t\t\t\t\treadAfterWrite,\n\t\t\t\t\t]);\n\t\t\t\t\tconst aggregates = [\n\t\t\t\t\t\t...typedRows(statusRows),\n\t\t\t\t\t\t...typedRows(joinRows),\n\t\t\t\t\t];\n\t\t\t\t\tconst [shardCount] = typedRows(shardRows);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\treadOps: 3,\n\t\t\t\t\t\twriteOps: 1,\n\t\t\t\t\t\tgroups: aggregates.length,\n\t\t\t\t\t\trows:\n\t\t\t\t\t\t\taggregates.reduce((sum, row) => sum + row.rows, 0) +\n\t\t\t\t\t\t\t(shardCount?.rows ?? 0),\n\t\t\t\t\t\ttotal: aggregates.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"feed-order-by-limit\": {\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t`SELECT id, customer_id, created_at, status, total_cents\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at >= ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT 1000`,\n\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"feed-pagination-adjacent\": {\n\t\t\t\t\tconst firstPage = typedRows<{ created_at: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT created_at\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at >= ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT ?`,\n\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\tFEED_PAGE_ROWS,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst cursor = firstPage.at(-1)?.created_at ?? 1_700_000_000_000;\n\t\t\t\t\tconst secondPage = await c.db.execute(\n\t\t\t\t\t\t`SELECT id, customer_id, created_at, status, total_cents\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at < ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT ?`,\n\t\t\t\t\t\tcursor,\n\t\t\t\t\t\tFEED_PAGE_ROWS,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { firstPageRows: firstPage.length, rows: secondPage.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"join-order-items\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"random-point-lookups\": {\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\"SELECT COUNT(*) AS rows FROM rw_orders\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst rows = Math.max(1, count?.rows ?? 1);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (let i = 0; i < POINT_LOOKUP_OPS; i += 1) {\n\t\t\t\t\t\tconst id = (pseudoRandom(i) % rows) + 1;\n\t\t\t\t\t\tconst [row] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT length(note) AS bytes FROM rw_orders WHERE id = ?\",\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += row?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { ops: POINT_LOOKUP_OPS, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"hot-index-cold-table\": {\n\t\t\t\t\tconst indexRows = typedRows<{ id: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT id\n\t\t\t\t\t\tFROM rw_docs\n\t\t\t\t\t\tWHERE tenant_id = ?\n\t\t\t\t\t\tORDER BY row_rank\n\t\t\t\t\t\tLIMIT 1000`,\n\t\t\t\t\t\t\t\"tenant-7\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of indexRows) {\n\t\t\t\t\t\tconst [doc] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT body_bytes AS bytes FROM rw_docs WHERE id = ?\",\n\t\t\t\t\t\t\t\trow.id,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += doc?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { rows: indexRows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"ledger-without-rowid-range\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT account_id, entry_id, amount_cents, length(memo) AS bytes\n\t\t\t\t\t\tFROM rw_ledger\n\t\t\t\t\t\tWHERE account_id BETWEEN 'acct-0040' AND 'acct-0180'\n\t\t\t\t\t\tORDER BY account_id, entry_id`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of rows) bytes += row.bytes;\n\t\t\t\t\tdetails = { rows: rows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-select-limit\": {\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-select-indexed\": {\n\t\t\t\t\tconst expectedRows = Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.ceil(\n\t\t\t\t\t\t\tpositiveInteger(input.targetBytes, CHAT_LOG_CHUNK_BYTES, \"targetBytes\") /\n\t\t\t\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst lowerBound = Math.max(0, expectedRows - 100);\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t\"SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\tlowerBound,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-count\": {\n\t\t\t\t\tconst [row] = typedRows<{ count: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { ops: 1, rows: row?.count ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-sum\": {\n\t\t\t\t\tconst [row] = typedRows<{ total_bytes: number | null }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { ops: 1, bytes: row?.total_bytes ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-tool-read-fanout\": {\n\t\t\t\t\tconst expectedRows = Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.ceil(\n\t\t\t\t\t\t\tpositiveInteger(input.targetBytes, CHAT_LOG_CHUNK_BYTES, \"targetBytes\") /\n\t\t\t\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst lowerBound = Math.max(0, expectedRows - 100);\n\t\t\t\t\tconst [limitRows, indexedRows, countRows, sumRows] = await Promise.all([\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t\tlowerBound,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t]);\n\t\t\t\t\tconst [countRow] = typedRows<{ count: number }>(countRows);\n\t\t\t\t\tconst [sumRow] = typedRows<{ total_bytes: number | null }>(sumRows);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\tlimitRows: limitRows.length,\n\t\t\t\t\t\tindexedRows: indexedRows.length,\n\t\t\t\t\t\trows: countRow?.count ?? 0,\n\t\t\t\t\t\tbytes: sumRow?.total_bytes ?? 0,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-tool-script\": {\n\t\t\t\t\tconst [\n\t\t\t\t\t\tmsgsRows,\n\t\t\t\t\t\ttoolRefsRows,\n\t\t\t\t\t\teventsRows,\n\t\t\t\t\t\tkvRows,\n\t\t\t\t\t\ttoolsRows,\n\t\t\t\t\t\tmetaRows,\n\t\t\t\t\t\tunresolvedRows,\n\t\t\t\t\t] = await Promise.all([\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, role, length(content) AS bytes FROM msgs WHERE parent IS NOT NULL AND role = ? AND cancelled = 0 ORDER BY created_at DESC LIMIT 50\",\n\t\t\t\t\t\t\t\"assistant\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, tool_name, status FROM tool_refs WHERE status = ? ORDER BY id DESC LIMIT 50\",\n\t\t\t\t\t\t\t\"pending\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT seq, event_type, length(payload) AS bytes FROM events WHERE seq > ? ORDER BY seq ASC LIMIT 100\",\n\t\t\t\t\t\t\t600,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT key, length(value) AS bytes FROM kv ORDER BY updated_at DESC LIMIT 20\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, name, length(spec) AS bytes FROM tools WHERE executor_id = ? ORDER BY updated_at DESC\",\n\t\t\t\t\t\t\t\"exec-1\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\"SELECT key, length(value) AS bytes FROM meta\"),\n\t\t\t\t\t\tc.db.execute(`SELECT m.id, m.role, count(tr.id) AS pending_refs\n\t\t\t\t\t\t\tFROM msgs m\n\t\t\t\t\t\t\tLEFT JOIN tool_refs tr ON tr.msg_id = m.id AND tr.status = 'pending'\n\t\t\t\t\t\t\tWHERE m.role = 'assistant' AND m.cancelled = 0\n\t\t\t\t\t\t\tGROUP BY m.id\n\t\t\t\t\t\t\tORDER BY m.created_at DESC\n\t\t\t\t\t\t\tLIMIT 100`),\n\t\t\t\t\t]);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 7,\n\t\t\t\t\t\tmsgsRows: msgsRows.length,\n\t\t\t\t\t\ttoolRefsRows: toolRefsRows.length,\n\t\t\t\t\t\teventsRows: eventsRows.length,\n\t\t\t\t\t\tkvRows: kvRows.length,\n\t\t\t\t\t\ttoolsRows: toolsRows.length,\n\t\t\t\t\t\tmetaRows: metaRows.length,\n\t\t\t\t\t\tunresolvedRows: unresolvedRows.length,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"write-batch-after-wake\": {\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\"SELECT COUNT(*) AS rows FROM rw_orders\"),\n\t\t\t\t\t);\n\t\t\t\t\tconst startId = (count?.rows ?? 0) + 1;\n\t\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\t\tfor (let offset = 0; offset < 1000; offset += ORDER_BATCH_ROWS) {\n\t\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\t\tfor (let i = offset; i < offset + ORDER_BATCH_ROWS; i += 1) {\n\t\t\t\t\t\t\tconst id = startId + i;\n\t\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t(i % 128) + 1,\n\t\t\t\t\t\t\t\t1_800_000_000_000 + i,\n\t\t\t\t\t\t\t\t\"pending\",\n\t\t\t\t\t\t\t\t1000 + i,\n\t\t\t\t\t\t\t\ti % 128,\n\t\t\t\t\t\t\t\tpayload(`wake-insert-${id}:`, DEFAULT_ROW_BYTES),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t\t...args,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\t\tdetails = { rows: 1000 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"update-hot-partition\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 15\",\n\t\t\t\t\t);\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 15\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: count?.rows ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"delete-churn-range-read\": {\n\t\t\t\t\tawait c.db.execute(\"DELETE FROM rw_orders WHERE shard BETWEEN 0 AND 15\");\n\t\t\t\t\tconst result = await readRowidRange(c.db, \"forward\");\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tdeletedShardCount: 16,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-create-indexes-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_account ON rw_migration_source(account_id)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_created ON rw_migration_source(created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_status_total ON rw_migration_source(status, total_cents)\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { indexes: 3 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-create-indexes-skewed-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_skew_account ON rw_migration_source(account_id, created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_skew_status ON rw_migration_source(status, total_cents)\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { indexes: 2, skewed: true };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-table-rebuild-large\": {\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_source_rebuilt (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\ttotal_cents INTEGER NOT NULL,\n\t\t\t\t\t\tbody TEXT NOT NULL,\n\t\t\t\t\t\tarchived_at INTEGER\n\t\t\t\t\t)`);\n\t\t\t\t\tawait c.db.execute(`INSERT INTO rw_migration_source_rebuilt (\n\t\t\t\t\t\tid, account_id, status, created_at, total_cents, body, archived_at\n\t\t\t\t\t)\n\t\t\t\t\tSELECT id, account_id, status, created_at, total_cents, body, NULL\n\t\t\t\t\tFROM rw_migration_source`);\n\t\t\t\t\tawait c.db.execute(\"DROP TABLE rw_migration_source\");\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"ALTER TABLE rw_migration_source_rebuilt RENAME TO rw_migration_source\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rebuilt: true };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-add-column-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"ALTER TABLE rw_migration_source ADD COLUMN archived_at INTEGER\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { alters: 1, rewritesRows: false };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-ddl-small\": {\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_empty (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\ttenant_id TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t\t)`);\n\t\t\t\t\tawait c.db.execute(\"ALTER TABLE rw_migration_empty ADD COLUMN status TEXT\");\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_empty_tenant_created ON rw_migration_empty(tenant_id, created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_audit (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\tmigration_name TEXT NOT NULL,\n\t\t\t\t\t\tapplied_at INTEGER NOT NULL\n\t\t\t\t\t)`);\n\t\t\t\t\tdetails = { tables: 2, indexes: 1, alters: 1 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst ms = performance.now() - t0;\n\t\t\treturn {\n\t\t\t\tms,\n\t\t\t\tworkload: input.workload,\n\t\t\t\t...details,\n\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\ntype WorkloadMode =\n\t| \"balanced\"\n\t| \"hot\"\n\t| \"transactions\"\n\t| \"payloads\"\n\t| \"edge\"\n\t| \"fragmentation\"\n\t| \"schema\"\n\t| \"index\"\n\t| \"relational\"\n\t| \"constraints\"\n\t| \"savepoints\"\n\t| \"pragma\"\n\t| \"prepared\"\n\t| \"growth\"\n\t| \"readwrite\"\n\t| \"truncate\"\n\t| \"boundary-keys\"\n\t| \"shadow\"\n\t| \"actual-nul\"\n\t| \"nasty-script\"\n\t| \"nasty\"\n\t| \"kitchen-sink\";\n\ninterface RunPhaseInput {\n\tseed: string;\n\tphase: number;\n\titerations: number;\n\tmode?: WorkloadMode;\n\tmaxPayloadBytes?: number;\n\tgrowthTargetBytes?: number;\n\tkeySpace?: number;\n}\n\ninterface ValidationSummary {\n\ttotalEvents: number;\n\tactiveRows: number;\n\texpectedRows: number;\n\tmissingRows: number;\n\textraRows: number;\n\tmismatchedRows: number;\n\tduplicateKeys: number;\n\tactualVersionSum: number;\n\texpectedVersionSum: number;\n\tactualPayloadChecksumSum: number;\n\texpectedPayloadChecksumSum: number;\n\taccountCount: number;\n\taccountBalanceSum: number;\n\texpectedAccountBalanceSum: number;\n\taccountBalanceMismatch: number;\n\tintegrityCheck: string;\n\tquickCheck: string;\n\tedgeRows: number;\n\tedgeExpectedRows: number;\n\tedgeMismatches: number;\n\tindexRows: number;\n\tindexMismatches: number;\n\trelationalOrders: number;\n\trelationalMismatches: number;\n\tconstraintAttempts: number;\n\tconstraintLeaks: number;\n\tsavepointRows: number;\n\tsavepointMismatches: number;\n\tidempotentOps: number;\n\tidempotentMismatches: number;\n\tschemaObjects: number;\n\tschemaMissingObjects: number;\n\tprobeRows: number;\n\tprobeMismatches: number;\n\tpreparedRows: number;\n\tpreparedMismatches: number;\n\tshadowRows: number;\n\tshadowMismatches: number;\n}\n\ninterface ItemMismatchDebugRow {\n\titemKey: string;\n\tactualValue: string | null;\n\texpectedValue: string | null;\n\tactualVersion: number | null;\n\texpectedVersion: number | null;\n\tactualUpdateCount: number | null;\n\texpectedUpdateCount: number | null;\n\tactualPayloadChecksum: number | null;\n\texpectedPayloadChecksum: number | null;\n\tactualPayloadBytes: number | null;\n\texpectedPayloadBytes: number | null;\n}\n\ninterface ItemEventDebugRow {\n\tseq: number;\n\tphase: number;\n\tlocalIndex: number;\n\tkind: string;\n\tpresent: number;\n\tvalue: string | null;\n\tversion: number;\n\tupdateCount: number;\n\tpayloadChecksum: number;\n\tpayloadBytes: number;\n\tapplied: number;\n}\n\ninterface PhaseResult {\n\tseed: string;\n\tphase: number;\n\tmode: WorkloadMode;\n\titerations: number;\n\tops: Record;\n\tvalidation: ValidationSummary;\n}\n\ninterface ItemRow {\n\titem_key: string;\n\tvalue: string;\n\tversion: number;\n\tupdate_count: number;\n\tpayload?: string;\n\tpayload_checksum: number;\n\tpayload_bytes: number;\n}\n\nconst ACCOUNT_COUNT = 8;\nconst ACCOUNT_INITIAL_BALANCE = 100_000;\nconst DEFAULT_KEY_SPACE = 64;\nconst DEFAULT_MAX_PAYLOAD_BYTES = 8 * 1024;\nconst DEFAULT_GROWTH_TARGET_BYTES = 1024 * 1024;\nconst LARGE_WRITE_CHUNK_BYTES = 96 * 1024;\nconst PAGE_BOUNDARY_SIZES = [\n\t1,\n\t4095,\n\t4096,\n\t4097,\n\t8191,\n\t8192,\n\t8193,\n\t32768,\n\t65535,\n\t65536,\n\t98304,\n\t131072,\n];\n\nfunction hashSeed(input: string): number {\n\tlet hash = 2166136261;\n\tfor (let i = 0; i < input.length; i += 1) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction makeRng(seed: string): () => number {\n\tlet state = hashSeed(seed) || 0x9e3779b9;\n\treturn () => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = state;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\nfunction intBetween(rng: () => number, min: number, max: number): number {\n\treturn min + Math.floor(rng() * (max - min + 1));\n}\n\nfunction checksum(input: string): number {\n\tlet hash = 2166136261;\n\tfor (let i = 0; i < input.length; i += 1) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction payloadFor(\n\tseed: string,\n\tphase: number,\n\tindex: number,\n\tbytes: number,\n): string {\n\tconst prefix = `${seed}:${phase}:${index}:`;\n\tif (bytes <= prefix.length) return prefix.slice(0, bytes);\n\treturn prefix + \"x\".repeat(bytes - prefix.length);\n}\n\nasync function queryOne(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tsql: string,\n\t...args: unknown[]\n): Promise {\n\tconst rows = await database.execute(sql, ...args);\n\treturn rows[0] as T | undefined;\n}\n\nasync function transaction(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tfn: () => Promise,\n): Promise {\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tconst result = await fn();\n\t\tawait database.execute(\"COMMIT\");\n\t\treturn result;\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n}\n\nasync function recordProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n\tscenario: string,\n\tname: string,\n\texpected: string | number,\n\tactual: string | number,\n\tmismatch: boolean,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_probe_results (\n\t\t\tphase, scenario, name, expected, actual, mismatch, created_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n\t\tphase,\n\t\tscenario,\n\t\tname,\n\t\tString(expected),\n\t\tString(actual),\n\t\tmismatch ? 1 : 0,\n\t\tDate.now(),\n\t);\n}\n\nfunction firstColumn(row: unknown): unknown {\n\tif (!row || typeof row !== \"object\") return undefined;\n\tconst values = Object.values(row as Record);\n\treturn values[0];\n}\n\nasync function ensureAccounts(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n): Promise {\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < ACCOUNT_COUNT; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_accounts (id, balance) VALUES (?, ?)\",\n\t\t\t\t`acct-${i}`,\n\t\t\t\tACCOUNT_INITIAL_BALANCE,\n\t\t\t);\n\t\t}\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n}\n\nasync function recordItemEvent(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n\tlocalIndex: number,\n\tkind: string,\n\titemKey: string,\n\tpresent: boolean,\n\tvalue: string | null,\n\tversion: number,\n\tupdateCount: number,\n\tpayload: string,\n\tapplied: boolean,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_item_events (\n\t\t\tphase, local_index, kind, item_key, present, value, version,\n\t\t\tupdate_count, payload_checksum, payload_bytes, applied, created_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\tphase,\n\t\tlocalIndex,\n\t\tkind,\n\t\titemKey,\n\t\tpresent ? 1 : 0,\n\t\tvalue,\n\t\tversion,\n\t\tupdateCount,\n\t\tchecksum(payload),\n\t\tpayload.length,\n\t\tapplied ? 1 : 0,\n\t\tDate.now(),\n\t);\n}\n\nasync function upsertLiveItem(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\trow: ItemRow,\n\tpayload: string,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_items (\n\t\t\titem_key, value, version, update_count, payload, payload_checksum,\n\t\t\tpayload_bytes, updated_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(item_key) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\tversion = excluded.version,\n\t\t\tupdate_count = excluded.update_count,\n\t\t\tpayload = excluded.payload,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\tupdated_at = excluded.updated_at`,\n\t\trow.item_key,\n\t\trow.value,\n\t\trow.version,\n\t\trow.update_count,\n\t\tpayload,\n\t\trow.payload_checksum,\n\t\trow.payload_bytes,\n\t\tDate.now(),\n\t);\n}\n\nasync function applyItemOperation(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\tkind: \"insert\" | \"update\" | \"delete\" | \"upsert\";\n\t\titemKey: string;\n\t\tpayloadBytes: number;\n\t},\n): Promise {\n\tlet current: ItemRow | undefined;\n\ttry {\n\t\tcurrent = await queryOne(\n\t\t\tdatabase,\n\t\t\t\"SELECT item_key, value, version, update_count, payload, payload_checksum, payload_bytes FROM fuzz_items WHERE item_key = ?\",\n\t\t\topts.itemKey,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation select failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\tconst payload = payloadFor(\n\t\topts.seed,\n\t\topts.phase,\n\t\topts.localIndex,\n\t\topts.payloadBytes,\n\t);\n\tconst nextVersion = (current?.version ?? 0) + 1;\n\tconst nextUpdateCount = (current?.update_count ?? 0) + 1;\n\tconst nextValue = `${opts.kind}:${opts.phase}:${opts.localIndex}:${nextVersion}`;\n\n\tif (opts.kind === \"delete\") {\n\t\ttry {\n\t\t\tawait recordItemEvent(\n\t\t\t\tdatabase,\n\t\t\t\topts.phase,\n\t\t\t\topts.localIndex,\n\t\t\t\topts.kind,\n\t\t\t\topts.itemKey,\n\t\t\t\tfalse,\n\t\t\t\tnull,\n\t\t\t\tnextVersion,\n\t\t\t\tnextUpdateCount,\n\t\t\t\t\"\",\n\t\t\t\tcurrent !== undefined,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`item operation event insert failed for delete key ${JSON.stringify(opts.itemKey)}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\"DELETE FROM fuzz_items WHERE item_key = ?\", opts.itemKey);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`item operation delete failed for key ${JSON.stringify(opts.itemKey)}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\treturn;\n\t}\n\n\tif (opts.kind === \"insert\" && current) {\n\t\ttry {\n\t\t\tawait recordItemEvent(\n\t\t\t\tdatabase,\n\t\t\t\topts.phase,\n\t\t\t\topts.localIndex,\n\t\t\t\topts.kind,\n\t\t\t\topts.itemKey,\n\t\t\t\ttrue,\n\t\t\t\tcurrent.value,\n\t\t\t\tcurrent.version,\n\t\t\t\tcurrent.update_count,\n\t\t\t\tcurrent.payload ?? \"\",\n\t\t\t\tfalse,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`item operation event insert failed for noop insert key ${JSON.stringify(opts.itemKey)}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\treturn;\n\t}\n\n\tconst row: ItemRow = {\n\t\titem_key: opts.itemKey,\n\t\tvalue: nextValue,\n\t\tversion: nextVersion,\n\t\tupdate_count: nextUpdateCount,\n\t\tpayload_checksum: checksum(payload),\n\t\tpayload_bytes: payload.length,\n\t};\n\n\ttry {\n\t\tawait recordItemEvent(\n\t\t\tdatabase,\n\t\t\topts.phase,\n\t\t\topts.localIndex,\n\t\t\topts.kind,\n\t\t\topts.itemKey,\n\t\t\ttrue,\n\t\t\trow.value,\n\t\t\trow.version,\n\t\t\trow.update_count,\n\t\t\tpayload,\n\t\t\ttrue,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation event insert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\ttry {\n\t\tawait upsertLiveItem(database, row, payload);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation live-row upsert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n\nasync function applyHotUpdates(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\titemKey: string;\n\t\tupdates: number;\n\t\tpayloadBytes: number;\n\t},\n): Promise {\n\tfor (let i = 0; i < opts.updates; i += 1) {\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: opts.localIndex * 1000 + i,\n\t\t\t\tkind: \"update\",\n\t\t\t\titemKey: opts.itemKey,\n\t\t\t\tpayloadBytes: opts.payloadBytes,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`hot update failed for ${opts.itemKey} at sub-update ${i + 1}/${opts.updates} with payloadBytes ${opts.payloadBytes}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function applyTransfer(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\tfromAccount: string;\n\t\ttoAccount: string;\n\t\tamount: number;\n\t},\n): Promise {\n\tawait transaction(database, async () => {\n\t\tconst before = await queryOne<{ total: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_accounts SET balance = balance - ? WHERE id = ?\",\n\t\t\topts.amount,\n\t\t\topts.fromAccount,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_accounts SET balance = balance + ? WHERE id = ?\",\n\t\t\topts.amount,\n\t\t\topts.toAccount,\n\t\t);\n\t\tconst after = await queryOne<{ total: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_transfer_events (\n\t\t\t\tphase, local_index, from_account, to_account, amount,\n\t\t\t\tbalance_sum_before, balance_sum_after, created_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\topts.phase,\n\t\t\topts.localIndex,\n\t\t\topts.fromAccount,\n\t\t\topts.toAccount,\n\t\t\topts.amount,\n\t\t\tbefore?.total ?? 0,\n\t\t\tafter?.total ?? 0,\n\t\t\tDate.now(),\n\t\t);\n\t});\n}\n\nasync function applyEdgePayloads(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst writeEdgePayload = async (\n\t\tid: string,\n\t\tkind: string,\n\t\tpayload: string,\n\t\tsizeLabel: string,\n\t): Promise => {\n\t\tconst payloadChecksum = checksum(payload);\n\t\tconst payloadBytes = payload.length;\n\t\ttry {\n\t\t\tawait database.execute(\"BEGIN\");\n\t\t} catch (error) {\n\t\t\tthrow new Error(`edge payload begin failed for ${sizeLabel}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\ttry {\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t\t) VALUES (?, ?, ?, ?, ?, ?)\n\t\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\t\tkind = excluded.kind,\n\t\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\t\tid,\n\t\t\t\t\tkind,\n\t\t\t\t\tpayload,\n\t\t\t\t\tpayloadChecksum,\n\t\t\t\t\tpayloadBytes,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`edge payload row upsert failed for ${sizeLabel}`, {\n\t\t\t\t\tcause: error,\n\t\t\t\t});\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\t\tpresent = excluded.present,\n\t\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\t\t\tid,\n\t\t\t\t\tpayloadChecksum,\n\t\t\t\t\tpayloadBytes,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`edge payload expectation upsert failed for ${sizeLabel}`, {\n\t\t\t\t\tcause: error,\n\t\t\t\t});\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait database.execute(\"COMMIT\");\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`edge payload commit failed for ${sizeLabel}`, {\n\t\t\t\t\tcause: error,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst sizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= opts.maxPayloadBytes);\n\tif (!sizes.includes(opts.maxPayloadBytes)) sizes.push(opts.maxPayloadBytes);\n\n\tlet ops = 0;\n\tfor (const size of sizes) {\n\t\tconst id = `edge-${opts.phase}-${size}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, size, size);\n\t\ttry {\n\t\t\tawait writeEdgePayload(id, \"boundary\", payload, `size ${size}`);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`edge payload write failed for size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\tconst unicodePayload = `escaped-nul:\\\\0 unicode:☃️ phase:${opts.phase} seed:${opts.seed}`;\n\tconst unicodeId = `edge-${opts.phase}-unicode-nul`;\n\ttry {\n\t\tawait writeEdgePayload(\n\t\t\tunicodeId,\n\t\t\t\"unicode-nul\",\n\t\t\tunicodePayload,\n\t\t\t\"unicode escaped-nul payload\",\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\"edge payload write failed for unicode escaped-nul payload\", {\n\t\t\tcause: error,\n\t\t});\n\t}\n\n\treturn ops + 1;\n}\n\nasync function applyActualNulPayload(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t},\n): Promise {\n\tconst payload = `actual-nul:\\0 phase:${opts.phase} seed:${opts.seed}`;\n\tconst id = `actual-nul-${opts.phase}`;\n\tawait transaction(database, async () => {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t) VALUES (?, 'actual-nul', ?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\tid,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t\tDate.now(),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tid,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t);\n\t});\n\treturn 1;\n}\n\nasync function applyFragmentationChurn(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst rows = Math.max(12, Math.floor(opts.iterations / 2));\n\tlet ops = 0;\n\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst size = intBetween(opts.rng, 32, Math.max(32, opts.maxPayloadBytes));\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 10_000 + i, size);\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t) VALUES (?, 'fragment', ?, ?, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\tid,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation payload upsert failed for ${id} at size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpresent = 1,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\t\tid,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation expectation upsert failed for ${id} at size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\tfor (let i = 0; i < rows; i += 3) {\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\ttry {\n\t\t\tawait database.execute(\"DELETE FROM fuzz_edge_payloads WHERE id = ?\", id);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation delete failed for ${id}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t) VALUES (?, 0, 0, 0)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpresent = 0,\n\t\t\t\t\tpayload_checksum = 0,\n\t\t\t\t\tpayload_bytes = 0`,\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation tombstone expectation failed for ${id}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\tfor (let i = 1; i < rows; i += 4) {\n\t\tconst size = intBetween(opts.rng, 1, Math.max(1, opts.maxPayloadBytes));\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 20_000 + i, size);\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_edge_payloads\n\t\t\t\tSET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ?\n\t\t\t\tWHERE id = ?`,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation payload rewrite failed for ${id} at size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_edge_expectations\n\t\t\t\tSET payload_checksum = ?, payload_bytes = ?\n\t\t\t\tWHERE id = ? AND present = 1`,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation expectation rewrite failed for ${id} at size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\tif (opts.phase % 2 === 1) {\n\t\ttry {\n\t\t\tawait database.execute(\"VACUUM\");\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation vacuum failed for phase ${opts.phase}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\treturn ops;\n}\n\nasync function applySchemaChurn(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tconst table = `fuzz_schema_phase_${phase}`;\n\tconst index = `idx_fuzz_schema_phase_${phase}_name`;\n\tconst view = `view_fuzz_schema_phase_${phase}`;\n\tconst dropIndex = `idx_fuzz_schema_drop_probe_${phase}`;\n\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS ${table} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tname TEXT NOT NULL UNIQUE,\n\t\t\tvalue INTEGER NOT NULL DEFAULT 0,\n\t\t\textra TEXT\n\t\t)`,\n\t);\n\tawait database.execute(`CREATE INDEX IF NOT EXISTS ${index} ON ${table}(name, value)`);\n\ttry {\n\t\tawait database.execute(`ALTER TABLE ${table} ADD COLUMN altered_${phase} TEXT DEFAULT 'altered'`);\n\t} catch {\n\t\tconst column = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t`SELECT COUNT(*) AS count FROM pragma_table_info('${table}') WHERE name = ?`,\n\t\t\t`altered_${phase}`,\n\t\t);\n\t\tif ((column?.count ?? 0) !== 1) throw new Error(`failed to add altered_${phase}`);\n\t}\n\tawait database.execute(`CREATE VIEW IF NOT EXISTS ${view} AS SELECT id, name, value FROM ${table}`);\n\tawait database.execute(\n\t\t`INSERT INTO ${table} (name, value, extra)\n\t\tVALUES (?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\textra = excluded.extra`,\n\t\t`schema-${phase}`,\n\t\tphase,\n\t\t`extra-${phase}`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS fuzz_without_rowid (\n\t\t\tid TEXT PRIMARY KEY,\n\t\t\tvalue INTEGER NOT NULL\n\t\t) WITHOUT ROWID`,\n\t);\n\tawait database.execute(`\n\t\tCREATE TRIGGER IF NOT EXISTS trg_fuzz_edge_payload_update\n\t\tAFTER UPDATE ON fuzz_edge_payloads\n\t\tBEGIN\n\t\t\tINSERT INTO fuzz_trigger_audit (\n\t\t\t\tpayload_id, old_checksum, new_checksum, created_at\n\t\t\t) VALUES (\n\t\t\t\tnew.id, old.payload_checksum, new.payload_checksum, strftime('%s', 'now') * 1000\n\t\t\t);\n\t\tEND\n\t`);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_without_rowid (id, value)\n\t\tVALUES (?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET value = excluded.value`,\n\t\t`phase-${phase}`,\n\t\tphase,\n\t);\n\n\tfor (const [name, type] of [\n\t\t[table, \"table\"],\n\t\t[index, \"index\"],\n\t\t[view, \"view\"],\n\t\t[\"trg_fuzz_edge_payload_update\", \"trigger\"],\n\t\t[\"fuzz_without_rowid\", \"table\"],\n\t] as const) {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_schema_registry (name, type)\n\t\t\tVALUES (?, ?)\n\t\t\tON CONFLICT(name) DO UPDATE SET type = excluded.type`,\n\t\t\tname,\n\t\t\ttype,\n\t\t);\n\t}\n\n\tawait database.execute(\"CREATE TEMP TABLE IF NOT EXISTS fuzz_temp_probe (id INTEGER PRIMARY KEY, value TEXT)\");\n\tawait database.execute(\"INSERT INTO fuzz_temp_probe (value) VALUES (?)\", `temp-${phase}`);\n\tawait database.execute(\"DROP TABLE fuzz_temp_probe\");\n\tawait database.execute(`CREATE INDEX IF NOT EXISTS ${dropIndex} ON fuzz_schema_registry(type)`);\n\tawait database.execute(`DROP INDEX IF EXISTS ${dropIndex}`);\n\tconst dropped = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = ?\",\n\t\tdropIndex,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"schema\",\n\t\t\"drop-index\",\n\t\t0,\n\t\tdropped?.count ?? -1,\n\t\t(dropped?.count ?? -1) !== 0,\n\t);\n\n\treturn 13;\n}\n\nasync function applyIndexProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t},\n): Promise {\n\tconst rows = Math.max(20, opts.iterations);\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < rows; i += 1) {\n\t\t\tconst tenant = `tenant-${intBetween(opts.rng, 0, 5)}`;\n\t\t\tconst bucket = intBetween(opts.rng, 0, 12);\n\t\t\tconst score = intBetween(opts.rng, -500, 500);\n\t\t\tconst label = `${opts.seed}:${opts.phase}:${i}`;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_indexed (tenant, bucket, score, label, payload)\n\t\t\t\tVALUES (?, ?, ?, ?, ?)`,\n\t\t\t\ttenant,\n\t\t\t\tbucket,\n\t\t\t\tscore,\n\t\t\t\tlabel,\n\t\t\t\tpayloadFor(opts.seed, opts.phase, 30_000 + i, intBetween(opts.rng, 8, 256)),\n\t\t\t);\n\t\t}\n\t});\n\treturn rows;\n}\n\nasync function applyPreparedChurn(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\titerations: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst rows = Math.max(32, opts.iterations);\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst id = `prep-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(\n\t\t\topts.seed,\n\t\t\topts.phase,\n\t\t\t70_000 + i,\n\t\t\tMath.min(opts.maxPayloadBytes, 64 + (i % 257)),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum)\n\t\t\tVALUES (?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tvalue = excluded.value,\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum\n\t\t\t/* unique-prepared-${opts.phase}-${i} */`,\n\t\t\tid,\n\t\t\ti,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum)\n\t\t\tVALUES (?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tvalue = excluded.value,\n\t\t\t\tpayload_checksum = excluded.payload_checksum`,\n\t\t\tid,\n\t\t\ti,\n\t\t\tchecksum(payload),\n\t\t);\n\t}\n\n\tconst repeatedId = `prep-repeat-${opts.phase}`;\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum)\n\t\tVALUES (?, 0, '', 0)\n\t\tON CONFLICT(id) DO UPDATE SET value = 0, payload = '', payload_checksum = 0`,\n\t\trepeatedId,\n\t);\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 80_000 + i, Math.min(512, opts.maxPayloadBytes));\n\t\tawait database.execute(\n\t\t\t`UPDATE fuzz_prepared_churn\n\t\t\tSET value = value + ?, payload = ?, payload_checksum = ?\n\t\t\tWHERE id = ?`,\n\t\t\t1,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\trepeatedId,\n\t\t);\n\t}\n\tconst finalPayload = payloadFor(opts.seed, opts.phase, 80_000 + rows - 1, Math.min(512, opts.maxPayloadBytes));\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum)\n\t\tVALUES (?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\tpayload_checksum = excluded.payload_checksum`,\n\t\trepeatedId,\n\t\trows,\n\t\tchecksum(finalPayload),\n\t);\n\n\treturn rows * 2 + 1;\n}\n\nasync function applyReadWriteProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t},\n): Promise {\n\tif ((await queryOne<{ count: number }>(database, \"SELECT COUNT(*) AS count FROM fuzz_indexed\"))?.count === 0) {\n\t\tawait applyIndexProbe(database, opts);\n\t}\n\n\tconst read = database.execute(\n\t\t`SELECT\n\t\t\tCOUNT(*) AS joined_rows,\n\t\t\tCOALESCE(SUM(a.score + b.score), 0) AS score_sum\n\t\tFROM fuzz_indexed a\n\t\tJOIN fuzz_indexed b ON b.bucket = a.bucket\n\t\tWHERE a.tenant <= 'tenant-3'`,\n\t);\n\tconst write = applyIndexProbe(database, {\n\t\tseed: opts.seed,\n\t\tphase: opts.phase,\n\t\trng: opts.rng,\n\t\titerations: Math.max(10, Math.floor(opts.iterations / 2)),\n\t});\n\tconst [readRows, writeOps] = await Promise.all([read, write]);\n\tconst row = readRows[0] as { joined_rows?: number; score_sum?: number } | undefined;\n\tconst joinedRows = Number(row?.joined_rows ?? -1);\n\tconst scoreSum = Number(row?.score_sum ?? Number.NaN);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"readwrite\",\n\t\t\"long-read-while-write\",\n\t\t\"nonnegative-finite\",\n\t\t`${joinedRows}:${scoreSum}`,\n\t\tjoinedRows < 0 || !Number.isFinite(scoreSum),\n\t);\n\treturn writeOps + 1;\n}\n\nasync function applyBoundaryKeys(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst keys = [\n\t\t\"\",\n\t\t\" \",\n\t\t`long-${\"k\".repeat(2048)}`,\n\t\t\"slash/key\",\n\t\t\"comma,key\",\n\t\t\"percent%key\",\n\t\t\"CaseKey\",\n\t\t\"casekey\",\n\t];\n\tlet ops = 0;\n\tfor (const [index, key] of keys.entries()) {\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 90_000 + index,\n\t\t\t\tkind: \"upsert\",\n\t\t\t\titemKey: key,\n\t\t\t\tpayloadBytes: Math.min(opts.maxPayloadBytes, 128 + index),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`boundary key write failed for literal key ${JSON.stringify(key)} at index ${index}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\tfor (let i = 0; i < 128; i += 1) {\n\t\tconst itemKey = `seq-${opts.phase}-${i.toString().padStart(4, \"0\")}`;\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 91_000 + i,\n\t\t\t\tkind: i % 4 === 0 ? \"delete\" : \"upsert\",\n\t\t\t\titemKey,\n\t\t\t\tpayloadBytes: Math.min(opts.maxPayloadBytes, 32 + (i % 97)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`boundary key write failed for sequential key ${JSON.stringify(itemKey)} at index ${i}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\tawait recordProbe(database, opts.phase, \"boundary-keys\", \"keys-written\", 136, ops, ops !== 136);\n\treturn ops;\n}\n\nasync function applyGrowthProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t\tgrowthTargetBytes: number;\n\t},\n): Promise {\n\tconst chunkBytes = Math.max(1, Math.min(LARGE_WRITE_CHUNK_BYTES, opts.maxPayloadBytes));\n\tconst rows = Math.max(1, Math.ceil(opts.growthTargetBytes / chunkBytes));\n\tlet written = 0;\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst size = Math.min(chunkBytes, opts.growthTargetBytes - written);\n\t\tconst id = `growth-${opts.phase}-${opts.growthTargetBytes}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 100_000 + i, size);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t) VALUES (?, 'growth', ?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\tid,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t\tDate.now(),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tid,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t);\n\t\twritten += size;\n\t}\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"growth\",\n\t\t\"target-bytes-written\",\n\t\topts.growthTargetBytes,\n\t\twritten,\n\t\twritten !== opts.growthTargetBytes,\n\t);\n\treturn rows;\n}\n\nasync function applyTruncateRecreateProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst id = `truncate-${opts.phase}`;\n\tconst largeSize = Math.max(1, Math.min(opts.maxPayloadBytes, 131072));\n\tconst largePayload = payloadFor(opts.seed, opts.phase, 110_000, largeSize);\n\tconst tinyPayload = payloadFor(opts.seed, opts.phase, 110_001, 1);\n\tconst recreatedPayload = payloadFor(opts.seed, opts.phase, 110_002, Math.min(4096, largeSize));\n\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t) VALUES (?, 'truncate', ?, ?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tpayload = excluded.payload,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\tupdated_at = excluded.updated_at`,\n\t\tid,\n\t\tlargePayload,\n\t\tchecksum(largePayload),\n\t\tlargePayload.length,\n\t\tDate.now(),\n\t);\n\tawait database.execute(\n\t\t\"UPDATE fuzz_edge_payloads SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? WHERE id = ?\",\n\t\ttinyPayload,\n\t\tchecksum(tinyPayload),\n\t\ttinyPayload.length,\n\t\tDate.now(),\n\t\tid,\n\t);\n\tawait database.execute(\"DELETE FROM fuzz_edge_payloads WHERE id = ?\", id);\n\tawait database.execute(\"VACUUM\");\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t) VALUES (?, 'truncate', ?, ?, ?, ?)`,\n\t\tid,\n\t\trecreatedPayload,\n\t\tchecksum(recreatedPayload),\n\t\trecreatedPayload.length,\n\t\tDate.now(),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\tid, present, payload_checksum, payload_bytes\n\t\t) VALUES (?, 1, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tpresent = 1,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\tid,\n\t\tchecksum(recreatedPayload),\n\t\trecreatedPayload.length,\n\t);\n\treturn 5;\n}\n\nasync function updateShadowChecksums(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tconst item = await queryOne<{ rows: number; value: number }>(\n\t\tdatabase,\n\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + version + update_count), 0) AS value\n\t\tFROM fuzz_items`,\n\t);\n\tconst edge = await queryOne<{ rows: number; value: number }>(\n\t\tdatabase,\n\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value\n\t\tFROM fuzz_edge_payloads`,\n\t);\n\tawait transaction(database, async () => {\n\t\tfor (const [name, rows, value] of [\n\t\t\t[\"items\", item?.rows ?? 0, item?.value ?? 0],\n\t\t\t[\"edge\", edge?.rows ?? 0, edge?.value ?? 0],\n\t\t] as const) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_shadow_checksums (name, value, row_count)\n\t\t\t\tVALUES (?, ?, ?)\n\t\t\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\t\t\tvalue = excluded.value,\n\t\t\t\t\trow_count = excluded.row_count`,\n\t\t\t\tname,\n\t\t\t\tvalue,\n\t\t\t\trows,\n\t\t\t);\n\t\t}\n\t});\n\tawait recordProbe(database, phase, \"shadow\", \"shadow-updated\", 2, 2, false);\n\treturn 2;\n}\n\nasync function applyConstraintChaos(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tawait database.execute(\"PRAGMA foreign_keys = ON\");\n\tconst validPrefix = `valid-${phase}`;\n\tconst existingValidRows = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints WHERE id LIKE ?\",\n\t\t`${validPrefix}-%`,\n\t);\n\tconst runSeq = existingValidRows?.count ?? 0;\n\tconst validId = `${validPrefix}-${runSeq}`;\n\tconst uniqValue = `uniq-${phase}-${runSeq}`;\n\tconst before = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq)\n\t\tVALUES (?, ?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tmust_not_null = excluded.must_not_null,\n\t\t\tqty = excluded.qty,\n\t\t\tuniq = excluded.uniq`,\n\t\tvalidId,\n\t\t\"ok\",\n\t\tphase,\n\t\tuniqValue,\n\t);\n\n\tconst attempts: Array<{\n\t\tname: string;\n\t\tsql: string;\n\t\targs: unknown[];\n\t}> = [\n\t\t{\n\t\t\tname: `not-null-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [`bad-null-${phase}-${runSeq}`, null, 1, `bad-null-${phase}-${runSeq}`],\n\t\t},\n\t\t{\n\t\t\tname: `check-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [`bad-check-${phase}-${runSeq}`, \"ok\", -1, `bad-check-${phase}-${runSeq}`],\n\t\t},\n\t\t{\n\t\t\tname: `unique-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [`bad-unique-${phase}-${runSeq}`, \"ok\", 1, uniqValue],\n\t\t},\n\t];\n\n\tfor (const attempt of attempts) {\n\t\tconst attemptBefore = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t\t);\n\t\tlet failed = false;\n\t\ttry {\n\t\t\tawait database.execute(attempt.sql, ...attempt.args);\n\t\t} catch {\n\t\t\tfailed = true;\n\t\t}\n\t\tconst attemptAfter = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t\t) VALUES (?, 1, ?, ?, ?)`,\n\t\t\tattempt.name,\n\t\t\tfailed ? 1 : 0,\n\t\t\tattemptBefore?.count ?? 0,\n\t\t\tattemptAfter?.count ?? 0,\n\t\t);\n\t}\n\n\tconst after = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t);\n\tif ((after?.count ?? 0) !== (before?.count ?? 0) + 1) {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t\t) VALUES (?, 0, 0, ?, ?)`,\n\t\t\t`valid-count-${phase}-${runSeq}`,\n\t\t\tbefore?.count ?? 0,\n\t\t\tafter?.count ?? 0,\n\t\t);\n\t}\n\n\tconst parentId = `fk-parent-${phase}-${runSeq}`;\n\tconst childId = `fk-child-${phase}-${runSeq}`;\n\tawait database.execute(\n\t\t\"INSERT INTO fuzz_fk_parent (id) VALUES (?) ON CONFLICT(id) DO NOTHING\",\n\t\tparentId,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_fk_child (id, parent_id)\n\t\tVALUES (?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET parent_id = excluded.parent_id`,\n\t\tchildId,\n\t\tparentId,\n\t);\n\tconst childBeforeDelete = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?\",\n\t\tparentId,\n\t);\n\tawait database.execute(\"DELETE FROM fuzz_fk_parent WHERE id = ?\", parentId);\n\tconst childAfterDelete = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?\",\n\t\tparentId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"constraints\",\n\t\t\"fk-cascade-delete\",\n\t\t0,\n\t\tchildAfterDelete?.count ?? -1,\n\t\t(childBeforeDelete?.count ?? 0) !== 1 || (childAfterDelete?.count ?? -1) !== 0,\n\t);\n\n\tconst fkBefore = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child\",\n\t);\n\tlet fkFailed = false;\n\ttry {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_fk_child (id, parent_id) VALUES (?, ?)\",\n\t\t\t`fk-orphan-${phase}-${runSeq}`,\n\t\t\t`missing-parent-${phase}-${runSeq}`,\n\t\t);\n\t} catch {\n\t\tfkFailed = true;\n\t}\n\tconst fkAfter = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child\",\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"constraints\",\n\t\t\"fk-failure-isolation\",\n\t\t`${fkBefore?.count ?? 0}:failed`,\n\t\t`${fkAfter?.count ?? 0}:${fkFailed ? \"failed\" : \"inserted\"}`,\n\t\t!fkFailed || (fkAfter?.count ?? 0) !== (fkBefore?.count ?? 0),\n\t);\n\n\treturn attempts.length + 3;\n}\n\nasync function applyPragmaProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tlet ops = 0;\n\tfor (const [name, setupSql, checkSql, expected] of [\n\t\t[\"journal_mode\", \"PRAGMA journal_mode = DELETE\", \"PRAGMA journal_mode\", \"nonempty\"],\n\t\t[\"synchronous\", \"PRAGMA synchronous = NORMAL\", \"PRAGMA synchronous\", \"nonempty\"],\n\t\t[\"cache_size\", \"PRAGMA cache_size = -2000\", \"PRAGMA cache_size\", \"-2000\"],\n\t\t[\"foreign_keys\", \"PRAGMA foreign_keys = ON\", \"PRAGMA foreign_keys\", \"1\"],\n\t\t[\"auto_vacuum\", \"PRAGMA auto_vacuum\", \"PRAGMA auto_vacuum\", \"nonempty\"],\n\t] as const) {\n\t\ttry {\n\t\t\tawait database.execute(setupSql);\n\t\t\tconst rows = await database.execute(checkSql);\n\t\t\tconst actual = String(firstColumn(rows[0]) ?? \"\");\n\t\t\tawait recordProbe(\n\t\t\t\tdatabase,\n\t\t\t\tphase,\n\t\t\t\t\"pragma\",\n\t\t\t\tname,\n\t\t\t\texpected,\n\t\t\t\tactual,\n\t\t\t\texpected === \"nonempty\" ? actual.length === 0 : actual !== expected,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tawait recordProbe(\n\t\t\t\tdatabase,\n\t\t\t\tphase,\n\t\t\t\t\"pragma\",\n\t\t\t\tname,\n\t\t\t\texpected,\n\t\t\t\terr instanceof Error ? err.message : \"unknown error\",\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\treturn ops;\n}\n\nasync function applySavepointScenario(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tconst keepId = `save-keep-${phase}`;\n\tconst rolledBackId = `save-rolled-back-${phase}`;\n\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value\",\n\t\t\tkeepId,\n\t\t\tphase,\n\t\t);\n\t\tawait database.execute(\"SAVEPOINT sp_rollback_probe\");\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value\",\n\t\t\trolledBackId,\n\t\t\t999_000 + phase,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_savepoints SET value = value + 1000 WHERE id = ?\",\n\t\t\tkeepId,\n\t\t);\n\t\tawait database.execute(\"ROLLBACK TO sp_rollback_probe\");\n\t\tawait database.execute(\"RELEASE sp_rollback_probe\");\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_savepoint_expectations (id, present, value)\n\t\tVALUES (?, 1, ?)\n\t\tON CONFLICT(id) DO UPDATE SET present = 1, value = excluded.value`,\n\t\tkeepId,\n\t\tphase,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_savepoint_expectations (id, present, value)\n\t\tVALUES (?, 0, 0)\n\t\tON CONFLICT(id) DO UPDATE SET present = 0, value = 0`,\n\t\trolledBackId,\n\t);\n\n\treturn 5;\n}\n\nasync function applyIdempotentReplay(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n): Promise {\n\tconst targetId = `idem-target-${phase % 3}`;\n\tawait database.execute(\n\t\t\"INSERT OR IGNORE INTO fuzz_idempotent_targets (id, value) VALUES (?, 0)\",\n\t\ttargetId,\n\t);\n\n\tfor (let i = 0; i < 8; i += 1) {\n\t\tconst opId = `idem-${phase}-${i}`;\n\t\tconst amount = phase + i + 1;\n\t\tfor (let attempt = 0; attempt < 3; attempt += 1) {\n\t\t\tawait transaction(database, async () => {\n\t\t\t\tconst existing = await queryOne<{ op_id: string }>(\n\t\t\t\t\tdatabase,\n\t\t\t\t\t\"SELECT op_id FROM fuzz_idempotent_ops WHERE op_id = ?\",\n\t\t\t\t\topId,\n\t\t\t\t);\n\t\t\t\tif (!existing) {\n\t\t\t\t\tawait database.execute(\n\t\t\t\t\t\t\"INSERT INTO fuzz_idempotent_ops (op_id, target_id, amount) VALUES (?, ?, ?)\",\n\t\t\t\t\t\topId,\n\t\t\t\t\t\ttargetId,\n\t\t\t\t\t\tamount,\n\t\t\t\t\t);\n\t\t\t\t\tawait database.execute(\n\t\t\t\t\t\t\"UPDATE fuzz_idempotent_targets SET value = value + ? WHERE id = ?\",\n\t\t\t\t\t\tamount,\n\t\t\t\t\t\ttargetId,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\treturn 24;\n}\n\nasync function ensureRelationalSeed(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n): Promise {\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 8; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_rel_users (id, name) VALUES (?, ?)\",\n\t\t\t\t`user-${i}`,\n\t\t\t\t`User ${i}`,\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < 12; i += 1) {\n\t\t\tconst productId = `product-${i}`;\n\t\t\tconst initialQty = 10_000;\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_rel_products (id, price) VALUES (?, ?)\",\n\t\t\t\tproductId,\n\t\t\t\t(i + 1) * 7,\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT OR IGNORE INTO fuzz_inventory (\n\t\t\t\t\tproduct_id, initial_qty, sold_qty, stock_qty\n\t\t\t\t) VALUES (?, ?, 0, ?)`,\n\t\t\t\tproductId,\n\t\t\t\tinitialQty,\n\t\t\t\tinitialQty,\n\t\t\t);\n\t\t}\n\t});\n}\n\nasync function applyRelationalOrder(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\trng: () => number;\n\t},\n): Promise {\n\tawait ensureRelationalSeed(database);\n\tconst orderPrefix = `order-${opts.phase}-${opts.localIndex}`;\n\tconst existingOrders = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_orders WHERE id LIKE ?\",\n\t\t`${orderPrefix}-%`,\n\t);\n\tconst orderId = `${orderPrefix}-${existingOrders?.count ?? 0}`;\n\tconst userId = `user-${intBetween(opts.rng, 0, 7)}`;\n\tconst itemCount = intBetween(opts.rng, 1, 4);\n\tlet total = 0;\n\n\tawait transaction(database, async () => {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_orders (id, user_id, total, status) VALUES (?, ?, 0, 'open')\",\n\t\t\torderId,\n\t\t\tuserId,\n\t\t);\n\t\tfor (let i = 0; i < itemCount; i += 1) {\n\t\t\tconst productId = `product-${intBetween(opts.rng, 0, 11)}`;\n\t\t\tconst product = await queryOne<{ price: number }>(\n\t\t\t\tdatabase,\n\t\t\t\t\"SELECT price FROM fuzz_rel_products WHERE id = ?\",\n\t\t\t\tproductId,\n\t\t\t);\n\t\t\tconst quantity = intBetween(opts.rng, 1, 5);\n\t\t\tconst price = product?.price ?? 0;\n\t\t\ttotal += price * quantity;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_order_items (\n\t\t\t\t\torder_id, product_id, quantity, price\n\t\t\t\t) VALUES (?, ?, ?, ?)`,\n\t\t\t\torderId,\n\t\t\t\tproductId,\n\t\t\t\tquantity,\n\t\t\t\tprice,\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_inventory\n\t\t\t\tSET sold_qty = sold_qty + ?, stock_qty = stock_qty - ?\n\t\t\t\tWHERE product_id = ?`,\n\t\t\t\tquantity,\n\t\t\t\tquantity,\n\t\t\t\tproductId,\n\t\t\t);\n\t\t}\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_orders SET total = ?, status = 'paid' WHERE id = ?\",\n\t\t\ttotal,\n\t\t\torderId,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_payments (order_id, amount, status) VALUES (?, ?, 'captured')\",\n\t\t\torderId,\n\t\t\ttotal,\n\t\t);\n\t});\n\n\treturn itemCount + 4;\n}\n\nasync function applyRollbackProbe(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tphase: number,\n\trowCount = 20,\n): Promise {\n\tconst before = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?\",\n\t\t`rollback-${phase}-%`,\n\t);\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < rowCount; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_items (\n\t\t\t\t\titem_key, value, version, update_count, payload, payload_checksum,\n\t\t\t\t\tpayload_bytes, updated_at\n\t\t\t\t) VALUES (?, ?, 1, 1, ?, ?, ?, ?)`,\n\t\t\t\t`rollback-${phase}-${i}`,\n\t\t\t\t\"should-not-survive\",\n\t\t\t\t\"rollback-payload\",\n\t\t\t\tchecksum(\"rollback-payload\"),\n\t\t\t\t\"rollback-payload\".length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t}\n\t\tthrow new Error(\"intentional rollback probe\");\n\t} catch {\n\t\tawait database.execute(\"ROLLBACK\");\n\t}\n\tconst after = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?\",\n\t\t`rollback-${phase}-%`,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t) VALUES (?, 1, 1, ?, ?)`,\n\t\t`rollback-probe-${phase}`,\n\t\tbefore?.count ?? 0,\n\t\tafter?.count ?? 0,\n\t);\n\treturn rowCount;\n}\n\nasync function applyNastyScript(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t\tintense?: boolean;\n\t},\n): Promise {\n\tconst growKey = `nasty-grow-${opts.phase}`;\n\tlet ops = 0;\n\tconst growMax = Math.min(opts.maxPayloadBytes, 131072);\n\tconst growSizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= growMax);\n\tif (!growSizes.includes(1)) growSizes.unshift(1);\n\tif (!growSizes.includes(growMax)) growSizes.push(growMax);\n\tfor (const size of growSizes) {\n\t\tawait applyItemOperation(database, {\n\t\t\tseed: opts.seed,\n\t\t\tphase: opts.phase,\n\t\t\tlocalIndex: 50_000 + size,\n\t\t\tkind: \"upsert\",\n\t\t\titemKey: growKey,\n\t\t\tpayloadBytes: Math.min(size, opts.maxPayloadBytes),\n\t\t});\n\t\tops += 1;\n\t}\n\n\tconst hotUpdates = opts.intense ? 10_000 : 250;\n\tawait applyHotUpdates(database, {\n\t\tseed: opts.seed,\n\t\tphase: opts.phase,\n\t\tlocalIndex: 60_000,\n\t\titemKey: `nasty-hot-${opts.phase}`,\n\t\tupdates: hotUpdates,\n\t\tpayloadBytes: Math.min(1024, opts.maxPayloadBytes),\n\t});\n\tops += hotUpdates;\n\n\tif (opts.intense) {\n\t\tawait database.execute(\"CREATE INDEX IF NOT EXISTS idx_nasty_heavy_write ON fuzz_items(value, version)\");\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 120_000 + i,\n\t\t\t\tkind: \"upsert\",\n\t\t\t\titemKey: `nasty-bulk-${opts.phase}-${i}`,\n\t\t\t\tpayloadBytes: Math.min(256, opts.maxPayloadBytes),\n\t\t\t});\n\t\t\tops += 1;\n\t\t}\n\t\tfor (let i = 0; i < 10_000; i += 2) {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 140_000 + i,\n\t\t\t\tkind: \"delete\",\n\t\t\t\titemKey: `nasty-bulk-${opts.phase}-${i}`,\n\t\t\t\tpayloadBytes: 1,\n\t\t\t});\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\"DROP INDEX IF EXISTS idx_nasty_heavy_write\");\n\t}\n\n\tconst rollbackRows = opts.intense ? 1000 : 20;\n\tawait applyRollbackProbe(database, opts.phase, rollbackRows);\n\tops += rollbackRows;\n\n\treturn ops;\n}\n\nasync function applyDeterministicNastyScript(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tlet ops = 0;\n\tconst growId = `nasty-script-grow-${opts.phase}`;\n\tconst maxGrowBytes = Math.min(opts.maxPayloadBytes, 131072);\n\tlet finalGrowPayload = \"\";\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 256; i += 1) {\n\t\t\tconst size = Math.max(1, Math.floor(1 + ((maxGrowBytes - 1) * i) / 255));\n\t\t\tconst payload = payloadFor(opts.seed, opts.phase, 160_000 + i, size);\n\t\t\tfinalGrowPayload = payload;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t) VALUES (?, 'nasty-grow', ?, ?, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\tgrowId,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tgrowId,\n\t\t\tchecksum(finalGrowPayload),\n\t\t\tfinalGrowPayload.length,\n\t\t);\n\t});\n\n\tconst counterId = `nasty-counter-${opts.phase}`;\n\tawait database.execute(\n\t\t\"INSERT INTO fuzz_nasty_counter (id, value) VALUES (?, 0) ON CONFLICT(id) DO UPDATE SET value = 0\",\n\t\tcounterId,\n\t);\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"UPDATE fuzz_nasty_counter SET value = value + 1 WHERE id = ?\",\n\t\t\t\tcounterId,\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t});\n\tconst counter = await queryOne<{ value: number }>(\n\t\tdatabase,\n\t\t\"SELECT value FROM fuzz_nasty_counter WHERE id = ?\",\n\t\tcounterId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"same-row-10k-updates\",\n\t\t10_000,\n\t\tcounter?.value ?? -1,\n\t\t(counter?.value ?? -1) !== 10_000,\n\t);\n\n\tconst groupId = `nasty-bulk-${opts.phase}`;\n\tawait database.execute(\"CREATE INDEX IF NOT EXISTS idx_fuzz_nasty_rows_group_n ON fuzz_nasty_rows(group_id, n)\");\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_nasty_rows (group_id, n, payload)\n\t\t\t\tVALUES (?, ?, ?)\n\t\t\t\tON CONFLICT(group_id, n) DO UPDATE SET payload = excluded.payload`,\n\t\t\t\tgroupId,\n\t\t\t\ti,\n\t\t\t\tpayloadFor(opts.seed, opts.phase, 170_000 + i, 64),\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\"DELETE FROM fuzz_nasty_rows WHERE group_id = ? AND n % 2 = 0\", groupId);\n\t\tops += 1;\n\t});\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_fuzz_nasty_rows_group_n\");\n\tconst remaining = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\tgroupId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"insert-10k-delete-every-other\",\n\t\t5000,\n\t\tremaining?.count ?? -1,\n\t\t(remaining?.count ?? -1) !== 5000,\n\t);\n\tconst indexLeft = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = 'idx_fuzz_nasty_rows_group_n'\",\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"create-drop-index-around-heavy-writes\",\n\t\t0,\n\t\tindexLeft?.count ?? -1,\n\t\t(indexLeft?.count ?? -1) !== 0,\n\t);\n\n\tconst rollbackGroupId = `nasty-rollback-${opts.phase}`;\n\tconst beforeRollback = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\trollbackGroupId,\n\t);\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < 1000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT INTO fuzz_nasty_rows (group_id, n, payload) VALUES (?, ?, ?)\",\n\t\t\t\trollbackGroupId,\n\t\t\t\ti,\n\t\t\t\t\"rollback\",\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\"ROLLBACK\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n\tconst afterRollback = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\trollbackGroupId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"rollback-1k-inserts\",\n\t\tbeforeRollback?.count ?? 0,\n\t\tafterRollback?.count ?? -1,\n\t\t(afterRollback?.count ?? -1) !== (beforeRollback?.count ?? 0),\n\t);\n\n\treturn ops;\n}\n\nfunction shouldRunDeepScenario(mode: WorkloadMode, scenario: WorkloadMode): boolean {\n\treturn mode === scenario || mode === \"kitchen-sink\" || mode === \"nasty\";\n}\n\nasync function applyDeepScenarios(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmode: WorkloadMode;\n\t\titerations: number;\n\t\trng: () => number;\n\t\tmaxPayloadBytes: number;\n\t\tgrowthTargetBytes: number;\n\t\tops: Record;\n\t},\n): Promise {\n\tconst runScenario = async (name: string, fn: () => Promise): Promise => {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\tconst detail =\n\t\t\t\terror instanceof Error ? error.message : typeof error === \"string\" ? error : String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`deep scenario ${name} failed in mode ${opts.mode} during phase ${opts.phase}: ${detail}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t};\n\n\tif (shouldRunDeepScenario(opts.mode, \"edge\") || opts.mode === \"payloads\") {\n\t\topts.ops.edgePayload = (opts.ops.edgePayload ?? 0) +\n\t\t\tawait runScenario(\"edge\", () => applyEdgePayloads(database, opts));\n\t}\n\tif (opts.mode === \"actual-nul\") {\n\t\topts.ops.actualNul = (opts.ops.actualNul ?? 0) +\n\t\t\tawait runScenario(\"actual-nul\", () => applyActualNulPayload(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"fragmentation\")) {\n\t\topts.ops.fragmentation = (opts.ops.fragmentation ?? 0) +\n\t\t\tawait runScenario(\"fragmentation\", () => applyFragmentationChurn(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"schema\")) {\n\t\topts.ops.schema = (opts.ops.schema ?? 0) +\n\t\t\tawait runScenario(\"schema\", () => applySchemaChurn(database, opts.phase));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"index\")) {\n\t\topts.ops.index = (opts.ops.index ?? 0) +\n\t\t\tawait runScenario(\"index\", () => applyIndexProbe(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"constraints\")) {\n\t\topts.ops.constraints = (opts.ops.constraints ?? 0) +\n\t\t\tawait runScenario(\"constraints\", () => applyConstraintChaos(database, opts.phase));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"savepoints\")) {\n\t\topts.ops.savepoints = (opts.ops.savepoints ?? 0) +\n\t\t\tawait runScenario(\"savepoints\", () => applySavepointScenario(database, opts.phase));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"pragma\")) {\n\t\topts.ops.pragma = (opts.ops.pragma ?? 0) +\n\t\t\tawait runScenario(\"pragma\", () => applyPragmaProbe(database, opts.phase));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"prepared\")) {\n\t\topts.ops.prepared = (opts.ops.prepared ?? 0) +\n\t\t\tawait runScenario(\"prepared\", () => applyPreparedChurn(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"growth\")) {\n\t\topts.ops.growth = (opts.ops.growth ?? 0) +\n\t\t\tawait runScenario(\"growth\", () => applyGrowthProbe(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"readwrite\")) {\n\t\topts.ops.readwrite = (opts.ops.readwrite ?? 0) +\n\t\t\tawait runScenario(\"readwrite\", () => applyReadWriteProbe(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"truncate\")) {\n\t\topts.ops.truncate = (opts.ops.truncate ?? 0) +\n\t\t\tawait runScenario(\"truncate\", () => applyTruncateRecreateProbe(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"boundary-keys\")) {\n\t\topts.ops.boundaryKeys = (opts.ops.boundaryKeys ?? 0) +\n\t\t\tawait runScenario(\"boundary-keys\", () => applyBoundaryKeys(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"relational\")) {\n\t\tconst orders = Math.max(1, Math.floor(opts.iterations / 20));\n\t\tfor (let i = 0; i < orders; i += 1) {\n\t\t\topts.ops.relational = (opts.ops.relational ?? 0) +\n\t\t\t\tawait runScenario(\"relational\", () =>\n\t\t\t\t\tapplyRelationalOrder(database, {\n\t\t\t\t\t\tphase: opts.phase,\n\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\trng: opts.rng,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t}\n\t}\n\tif (opts.mode === \"kitchen-sink\" || opts.mode === \"nasty\") {\n\t\topts.ops.idempotent = (opts.ops.idempotent ?? 0) +\n\t\t\tawait runScenario(\"idempotent\", () => applyIdempotentReplay(database, opts.phase));\n\t\topts.ops.nasty = (opts.ops.nasty ?? 0) +\n\t\t\tawait runScenario(\"nasty\", () =>\n\t\t\t\tapplyNastyScript(database, { ...opts, intense: opts.mode === \"nasty\" }),\n\t\t\t);\n\t}\n\tif (opts.mode === \"nasty-script\") {\n\t\topts.ops.nasty = (opts.ops.nasty ?? 0) +\n\t\t\tawait runScenario(\"nasty-script\", () => applyDeterministicNastyScript(database, opts));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"shadow\")) {\n\t\topts.ops.shadow = (opts.ops.shadow ?? 0) +\n\t\t\tawait runScenario(\"shadow\", () => updateShadowChecksums(database, opts.phase));\n\t}\n}\n\nfunction chooseKind(\n\tmode: WorkloadMode,\n\trng: () => number,\n): \"insert\" | \"update\" | \"delete\" | \"upsert\" | \"hot\" | \"transfer\" {\n\tconst roll = rng();\n\tif (mode === \"transactions\") {\n\t\tif (roll < 0.55) return \"transfer\";\n\t\tif (roll < 0.75) return \"upsert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (mode === \"hot\") {\n\t\tif (roll < 0.6) return \"hot\";\n\t\tif (roll < 0.75) return \"upsert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (mode === \"payloads\") {\n\t\tif (roll < 0.4) return \"upsert\";\n\t\tif (roll < 0.7) return \"insert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (roll < 0.2) return \"insert\";\n\tif (roll < 0.45) return \"update\";\n\tif (roll < 0.65) return \"delete\";\n\tif (roll < 0.85) return \"upsert\";\n\tif (roll < 0.95) return \"hot\";\n\treturn \"transfer\";\n}\n\nasync function validate(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n): Promise {\n\tconst integrity = await queryOne<{ integrity_check: string }>(\n\t\tdatabase,\n\t\t\"PRAGMA integrity_check\",\n\t);\n\tconst quick = await queryOne<{ quick_check: string }>(\n\t\tdatabase,\n\t\t\"PRAGMA quick_check\",\n\t);\n\tconst totals = await queryOne<{\n\t\ttotal_events: number;\n\t\tactive_rows: number;\n\t\texpected_rows: number;\n\t\tactual_version_sum: number;\n\t\texpected_version_sum: number;\n\t\tactual_payload_checksum_sum: number;\n\t\texpected_payload_checksum_sum: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_item_events) AS total_events,\n\t\t\t(SELECT COUNT(*) FROM fuzz_items) AS active_rows,\n\t\t\t(SELECT COUNT(*) FROM latest WHERE present = 1) AS expected_rows,\n\t\t\t(SELECT COALESCE(SUM(version), 0) FROM fuzz_items) AS actual_version_sum,\n\t\t\t(SELECT COALESCE(SUM(version), 0) FROM latest WHERE present = 1) AS expected_version_sum,\n\t\t\t(SELECT COALESCE(SUM(payload_checksum), 0) FROM fuzz_items) AS actual_payload_checksum_sum,\n\t\t\t(SELECT COALESCE(SUM(payload_checksum), 0) FROM latest WHERE present = 1) AS expected_payload_checksum_sum`,\n\t);\n\tconst mismatches = await queryOne<{\n\t\tmissing_rows: number;\n\t\textra_rows: number;\n\t\tmismatched_rows: number;\n\t\tduplicate_keys: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM latest l\n\t\t\t\tLEFT JOIN fuzz_items i ON i.item_key = l.item_key\n\t\t\t\tWHERE l.present = 1 AND i.item_key IS NULL\n\t\t\t) AS missing_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_items i\n\t\t\t\tLEFT JOIN latest l ON l.item_key = i.item_key\n\t\t\t\tWHERE l.item_key IS NULL OR l.present = 0\n\t\t\t) AS extra_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM latest l\n\t\t\t\tJOIN fuzz_items i ON i.item_key = l.item_key\n\t\t\t\tWHERE l.present = 1\n\t\t\t\t\tAND (\n\t\t\t\t\t\ti.value != l.value OR\n\t\t\t\t\t\ti.version != l.version OR\n\t\t\t\t\t\ti.update_count != l.update_count OR\n\t\t\t\t\t\ti.payload_checksum != l.payload_checksum OR\n\t\t\t\t\t\ti.payload_bytes != l.payload_bytes\n\t\t\t\t\t)\n\t\t\t) AS mismatched_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT item_key\n\t\t\t\t\tFROM fuzz_items\n\t\t\t\t\tGROUP BY item_key\n\t\t\t\t\tHAVING COUNT(*) > 1\n\t\t\t\t)\n\t\t\t) AS duplicate_keys`,\n\t);\n\tconst accounts = await queryOne<{\n\t\taccount_count: number;\n\t\taccount_balance_sum: number;\n\t\taccount_balance_mismatch: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS account_count,\n\t\t\tCOALESCE(SUM(balance), 0) AS account_balance_sum,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_transfer_events\n\t\t\t\tWHERE balance_sum_before != ? OR balance_sum_after != ?\n\t\t\t) AS account_balance_mismatch\n\t\tFROM fuzz_accounts`,\n\t\tACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t\tACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t);\n\tconst edge = await queryOne<{\n\t\tedge_rows: number;\n\t\tedge_expected_rows: number;\n\t\tedge_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_edge_payloads) AS edge_rows,\n\t\t\t(SELECT COUNT(*) FROM fuzz_edge_expectations WHERE present = 1) AS edge_expected_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_edge_expectations e\n\t\t\t\tLEFT JOIN fuzz_edge_payloads p ON p.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\t(e.present = 1 AND p.id IS NULL) OR\n\t\t\t\t\t(e.present = 0 AND p.id IS NOT NULL) OR\n\t\t\t\t\t(e.present = 1 AND (\n\t\t\t\t\t\tp.payload_checksum != e.payload_checksum OR\n\t\t\t\t\t\tp.payload_bytes != e.payload_bytes\n\t\t\t\t\t))\n\t\t\t) AS edge_mismatches`,\n\t);\n\tconst indexProbe = await queryOne<{\n\t\tindex_rows: number;\n\t\tindex_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_indexed) AS index_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT id FROM fuzz_indexed\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t\tEXCEPT\n\t\t\t\t\tSELECT id FROM fuzz_indexed NOT INDEXED\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT id FROM fuzz_indexed NOT INDEXED\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t\tEXCEPT\n\t\t\t\t\tSELECT id FROM fuzz_indexed\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t)\n\t\t\t) AS index_mismatches`,\n\t);\n\tconst relational = await queryOne<{\n\t\trelational_orders: number;\n\t\trelational_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_orders) AS relational_orders,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_orders o\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT order_id, COALESCE(SUM(quantity * price), 0) AS item_total\n\t\t\t\t\tFROM fuzz_order_items\n\t\t\t\t\tGROUP BY order_id\n\t\t\t\t) i ON i.order_id = o.id\n\t\t\t\tWHERE o.total != COALESCE(i.item_total, 0)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_orders o\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT order_id, COALESCE(SUM(amount), 0) AS payment_total\n\t\t\t\t\tFROM fuzz_payments\n\t\t\t\t\tWHERE status = 'captured'\n\t\t\t\t\tGROUP BY order_id\n\t\t\t\t) p ON p.order_id = o.id\n\t\t\t\tWHERE o.status = 'paid' AND o.total != COALESCE(p.payment_total, 0)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_inventory\n\t\t\t\tWHERE initial_qty != sold_qty + stock_qty OR stock_qty < 0\n\t\t\t) AS relational_mismatches`,\n\t);\n\tconst constraints = await queryOne<{\n\t\tconstraint_attempts: number;\n\t\tconstraint_leaks: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS constraint_attempts,\n\t\t\tCOALESCE(SUM(\n\t\t\t\tCASE\n\t\t\t\t\tWHEN expected_failed = 1 AND (actually_failed != 1 OR before_count != after_count) THEN 1\n\t\t\t\t\tWHEN expected_failed = 0 AND after_count != before_count + 1 THEN 1\n\t\t\t\t\tELSE 0\n\t\t\t\tEND\n\t\t\t), 0) AS constraint_leaks\n\t\tFROM fuzz_constraint_attempts`,\n\t);\n\tconst savepoints = await queryOne<{\n\t\tsavepoint_rows: number;\n\t\tsavepoint_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_savepoints) AS savepoint_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_savepoint_expectations e\n\t\t\t\tLEFT JOIN fuzz_savepoints s ON s.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\t(e.present = 1 AND s.id IS NULL) OR\n\t\t\t\t\t(e.present = 0 AND s.id IS NOT NULL) OR\n\t\t\t\t\t(e.present = 1 AND s.value != e.value)\n\t\t\t) AS savepoint_mismatches`,\n\t);\n\tconst idempotency = await queryOne<{\n\t\tidempotent_ops: number;\n\t\tidempotent_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_idempotent_ops) AS idempotent_ops,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_idempotent_targets t\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT target_id, COALESCE(SUM(amount), 0) AS expected\n\t\t\t\t\tFROM fuzz_idempotent_ops\n\t\t\t\t\tGROUP BY target_id\n\t\t\t\t) o ON o.target_id = t.id\n\t\t\t\tWHERE t.value != COALESCE(o.expected, 0)\n\t\t\t) AS idempotent_mismatches`,\n\t);\n\tconst schema = await queryOne<{\n\t\tschema_objects: number;\n\t\tschema_missing_objects: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS schema_objects,\n\t\t\tCOALESCE(SUM(CASE WHEN m.name IS NULL THEN 1 ELSE 0 END), 0) AS schema_missing_objects\n\t\tFROM fuzz_schema_registry r\n\t\tLEFT JOIN sqlite_master m ON m.name = r.name AND m.type = r.type`,\n\t);\n\tconst probes = await queryOne<{\n\t\tprobe_rows: number;\n\t\tprobe_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS probe_rows,\n\t\t\tCOALESCE(SUM(mismatch), 0) AS probe_mismatches\n\t\tFROM fuzz_probe_results`,\n\t);\n\tconst prepared = await queryOne<{\n\t\tprepared_rows: number;\n\t\tprepared_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_prepared_churn) AS prepared_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_prepared_expectations e\n\t\t\t\tLEFT JOIN fuzz_prepared_churn p ON p.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\tp.id IS NULL OR\n\t\t\t\t\tp.value != e.value OR\n\t\t\t\t\tp.payload_checksum != e.payload_checksum\n\t\t\t) AS prepared_mismatches`,\n\t);\n\tconst shadow = await queryOne<{\n\t\tshadow_rows: number;\n\t\tshadow_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH recomputed AS (\n\t\t\tSELECT 'items' AS name,\n\t\t\t\tCOUNT(*) AS row_count,\n\t\t\t\tCOALESCE(SUM(payload_checksum + version + update_count), 0) AS value\n\t\t\tFROM fuzz_items\n\t\t\tUNION ALL\n\t\t\tSELECT 'edge' AS name,\n\t\t\t\tCOUNT(*) AS row_count,\n\t\t\t\tCOALESCE(SUM(payload_checksum + payload_bytes), 0) AS value\n\t\t\tFROM fuzz_edge_payloads\n\t\t)\n\t\tSELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_shadow_checksums) AS shadow_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_shadow_checksums s\n\t\t\t\tJOIN recomputed r ON r.name = s.name\n\t\t\t\tWHERE s.value != r.value OR s.row_count != r.row_count\n\t\t\t) AS shadow_mismatches`,\n\t);\n\n\tconst summary: ValidationSummary = {\n\t\ttotalEvents: totals?.total_events ?? 0,\n\t\tactiveRows: totals?.active_rows ?? 0,\n\t\texpectedRows: totals?.expected_rows ?? 0,\n\t\tmissingRows: mismatches?.missing_rows ?? 0,\n\t\textraRows: mismatches?.extra_rows ?? 0,\n\t\tmismatchedRows: mismatches?.mismatched_rows ?? 0,\n\t\tduplicateKeys: mismatches?.duplicate_keys ?? 0,\n\t\tactualVersionSum: totals?.actual_version_sum ?? 0,\n\t\texpectedVersionSum: totals?.expected_version_sum ?? 0,\n\t\tactualPayloadChecksumSum: totals?.actual_payload_checksum_sum ?? 0,\n\t\texpectedPayloadChecksumSum: totals?.expected_payload_checksum_sum ?? 0,\n\t\taccountCount: accounts?.account_count ?? 0,\n\t\taccountBalanceSum: accounts?.account_balance_sum ?? 0,\n\t\texpectedAccountBalanceSum: ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t\taccountBalanceMismatch: accounts?.account_balance_mismatch ?? 0,\n\t\tintegrityCheck: integrity?.integrity_check ?? \"missing\",\n\t\tquickCheck: quick?.quick_check ?? \"missing\",\n\t\tedgeRows: edge?.edge_rows ?? 0,\n\t\tedgeExpectedRows: edge?.edge_expected_rows ?? 0,\n\t\tedgeMismatches: edge?.edge_mismatches ?? 0,\n\t\tindexRows: indexProbe?.index_rows ?? 0,\n\t\tindexMismatches: indexProbe?.index_mismatches ?? 0,\n\t\trelationalOrders: relational?.relational_orders ?? 0,\n\t\trelationalMismatches: relational?.relational_mismatches ?? 0,\n\t\tconstraintAttempts: constraints?.constraint_attempts ?? 0,\n\t\tconstraintLeaks: constraints?.constraint_leaks ?? 0,\n\t\tsavepointRows: savepoints?.savepoint_rows ?? 0,\n\t\tsavepointMismatches: savepoints?.savepoint_mismatches ?? 0,\n\t\tidempotentOps: idempotency?.idempotent_ops ?? 0,\n\t\tidempotentMismatches: idempotency?.idempotent_mismatches ?? 0,\n\t\tschemaObjects: schema?.schema_objects ?? 0,\n\t\tschemaMissingObjects: schema?.schema_missing_objects ?? 0,\n\t\tprobeRows: probes?.probe_rows ?? 0,\n\t\tprobeMismatches: probes?.probe_mismatches ?? 0,\n\t\tpreparedRows: prepared?.prepared_rows ?? 0,\n\t\tpreparedMismatches: prepared?.prepared_mismatches ?? 0,\n\t\tshadowRows: shadow?.shadow_rows ?? 0,\n\t\tshadowMismatches: shadow?.shadow_mismatches ?? 0,\n\t};\n\n\treturn summary;\n}\n\nasync function debugItemMismatches(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tlimit = 5,\n): Promise<{\n\titemMismatches: ItemMismatchDebugRow[];\n\trecentEventsByKey: Record;\n}> {\n\tconst rows = (await database.execute(\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\tCOALESCE(l.item_key, i.item_key) AS item_key,\n\t\t\ti.value AS actual_value,\n\t\t\tl.value AS expected_value,\n\t\t\ti.version AS actual_version,\n\t\t\tl.version AS expected_version,\n\t\t\ti.update_count AS actual_update_count,\n\t\t\tl.update_count AS expected_update_count,\n\t\t\ti.payload_checksum AS actual_payload_checksum,\n\t\t\tl.payload_checksum AS expected_payload_checksum,\n\t\t\ti.payload_bytes AS actual_payload_bytes,\n\t\t\tl.payload_bytes AS expected_payload_bytes\n\t\tFROM latest l\n\t\tFULL OUTER JOIN fuzz_items i ON i.item_key = l.item_key\n\t\tWHERE\n\t\t\t(l.present = 1 AND i.item_key IS NULL) OR\n\t\t\t((l.item_key IS NULL OR l.present = 0) AND i.item_key IS NOT NULL) OR\n\t\t\t(\n\t\t\t\tl.present = 1 AND i.item_key IS NOT NULL AND (\n\t\t\t\t\ti.value != l.value OR\n\t\t\t\t\ti.version != l.version OR\n\t\t\t\t\ti.update_count != l.update_count OR\n\t\t\t\t\ti.payload_checksum != l.payload_checksum OR\n\t\t\t\t\ti.payload_bytes != l.payload_bytes\n\t\t\t\t)\n\t\t\t)\n\t\tORDER BY COALESCE(l.item_key, i.item_key)\n\t\tLIMIT ?`,\n\t\tlimit,\n\t)) as Array<{\n\t\titem_key: string;\n\t\tactual_value: string | null;\n\t\texpected_value: string | null;\n\t\tactual_version: number | null;\n\t\texpected_version: number | null;\n\t\tactual_update_count: number | null;\n\t\texpected_update_count: number | null;\n\t\tactual_payload_checksum: number | null;\n\t\texpected_payload_checksum: number | null;\n\t\tactual_payload_bytes: number | null;\n\t\texpected_payload_bytes: number | null;\n\t}>;\n\n\tconst itemMismatches = rows.map((row) => ({\n\t\titemKey: row.item_key,\n\t\tactualValue: row.actual_value,\n\t\texpectedValue: row.expected_value,\n\t\tactualVersion: row.actual_version,\n\t\texpectedVersion: row.expected_version,\n\t\tactualUpdateCount: row.actual_update_count,\n\t\texpectedUpdateCount: row.expected_update_count,\n\t\tactualPayloadChecksum: row.actual_payload_checksum,\n\t\texpectedPayloadChecksum: row.expected_payload_checksum,\n\t\tactualPayloadBytes: row.actual_payload_bytes,\n\t\texpectedPayloadBytes: row.expected_payload_bytes,\n\t}));\n\n\tconst recentEventsByKey: Record = {};\n\tfor (const row of itemMismatches) {\n\t\tconst events = (await database.execute(\n\t\t\t`SELECT\n\t\t\t\tseq,\n\t\t\t\tphase,\n\t\t\t\tlocal_index,\n\t\t\t\tkind,\n\t\t\t\tpresent,\n\t\t\t\tvalue,\n\t\t\t\tversion,\n\t\t\t\tupdate_count,\n\t\t\t\tpayload_checksum,\n\t\t\t\tpayload_bytes,\n\t\t\t\tapplied\n\t\t\tFROM fuzz_item_events\n\t\t\tWHERE item_key = ?\n\t\t\tORDER BY seq DESC\n\t\t\tLIMIT 10`,\n\t\t\trow.itemKey,\n\t\t)) as Array<{\n\t\t\tseq: number;\n\t\t\tphase: number;\n\t\t\tlocal_index: number;\n\t\t\tkind: string;\n\t\t\tpresent: number;\n\t\t\tvalue: string | null;\n\t\t\tversion: number;\n\t\t\tupdate_count: number;\n\t\t\tpayload_checksum: number;\n\t\t\tpayload_bytes: number;\n\t\t\tapplied: number;\n\t\t}>;\n\t\trecentEventsByKey[row.itemKey] = events.map((event) => ({\n\t\t\tseq: event.seq,\n\t\t\tphase: event.phase,\n\t\t\tlocalIndex: event.local_index,\n\t\t\tkind: event.kind,\n\t\t\tpresent: event.present,\n\t\t\tvalue: event.value,\n\t\t\tversion: event.version,\n\t\t\tupdateCount: event.update_count,\n\t\t\tpayloadChecksum: event.payload_checksum,\n\t\t\tpayloadBytes: event.payload_bytes,\n\t\t\tapplied: event.applied,\n\t\t}));\n\t}\n\n\treturn { itemMismatches, recentEventsByKey };\n}\n\nexport const rawSqliteFuzzer = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_items (\n\t\t\t\t\titem_key TEXT PRIMARY KEY,\n\t\t\t\t\tvalue TEXT NOT NULL,\n\t\t\t\t\tversion INTEGER NOT NULL,\n\t\t\t\t\tupdate_count INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_item_events (\n\t\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tlocal_index INTEGER NOT NULL,\n\t\t\t\t\tkind TEXT NOT NULL,\n\t\t\t\t\titem_key TEXT NOT NULL,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tvalue TEXT,\n\t\t\t\t\tversion INTEGER NOT NULL,\n\t\t\t\t\tupdate_count INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tapplied INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_item_events_key_seq ON fuzz_item_events(item_key, seq)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_accounts (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tbalance INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_transfer_events (\n\t\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tlocal_index INTEGER NOT NULL,\n\t\t\t\t\tfrom_account TEXT NOT NULL,\n\t\t\t\t\tto_account TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL,\n\t\t\t\t\tbalance_sum_before INTEGER NOT NULL,\n\t\t\t\t\tbalance_sum_after INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_edge_payloads (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tkind TEXT NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_edge_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_trigger_audit (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tpayload_id TEXT NOT NULL,\n\t\t\t\t\told_checksum INTEGER NOT NULL,\n\t\t\t\t\tnew_checksum INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_edge_kind_size ON fuzz_edge_payloads(kind, payload_bytes)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_indexed (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\ttenant TEXT NOT NULL,\n\t\t\t\t\tbucket INTEGER NOT NULL,\n\t\t\t\t\tscore INTEGER NOT NULL,\n\t\t\t\t\tlabel TEXT NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_tenant_bucket_score ON fuzz_indexed(tenant, bucket, score)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_score_label ON fuzz_indexed(score, label)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_rel_users (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tname TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_rel_products (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tprice INTEGER NOT NULL CHECK (price >= 0)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_orders (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tuser_id TEXT NOT NULL,\n\t\t\t\t\ttotal INTEGER NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES fuzz_rel_users(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_order_items (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id TEXT NOT NULL,\n\t\t\t\t\tproduct_id TEXT NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL CHECK (quantity > 0),\n\t\t\t\t\tprice INTEGER NOT NULL CHECK (price >= 0),\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES fuzz_orders(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_payments (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES fuzz_orders(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_inventory (\n\t\t\t\t\tproduct_id TEXT PRIMARY KEY,\n\t\t\t\t\tinitial_qty INTEGER NOT NULL,\n\t\t\t\t\tsold_qty INTEGER NOT NULL,\n\t\t\t\t\tstock_qty INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_constraints (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tmust_not_null TEXT NOT NULL,\n\t\t\t\t\tqty INTEGER NOT NULL CHECK (qty >= 0),\n\t\t\t\t\tuniq TEXT NOT NULL UNIQUE\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_constraint_attempts (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\texpected_failed INTEGER NOT NULL,\n\t\t\t\t\tactually_failed INTEGER NOT NULL,\n\t\t\t\t\tbefore_count INTEGER NOT NULL,\n\t\t\t\t\tafter_count INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_fk_parent (\n\t\t\t\t\tid TEXT PRIMARY KEY\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_fk_child (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tparent_id TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (parent_id) REFERENCES fuzz_fk_parent(id) ON DELETE CASCADE\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_savepoints (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_savepoint_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_idempotent_ops (\n\t\t\t\t\top_id TEXT PRIMARY KEY,\n\t\t\t\t\ttarget_id TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_idempotent_targets (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_schema_registry (\n\t\t\t\t\tname TEXT PRIMARY KEY,\n\t\t\t\t\ttype TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_probe_results (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tscenario TEXT NOT NULL,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\texpected TEXT NOT NULL,\n\t\t\t\t\tactual TEXT NOT NULL,\n\t\t\t\t\tmismatch INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_prepared_churn (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_prepared_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_shadow_checksums (\n\t\t\t\t\tname TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\trow_count INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_nasty_counter (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_nasty_rows (\n\t\t\t\t\tgroup_id TEXT NOT NULL,\n\t\t\t\t\tn INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tPRIMARY KEY (group_id, n)\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_nasty_rows\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_nasty_counter\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_shadow_checksums\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_prepared_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_prepared_churn\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_probe_results\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_schema_registry\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_idempotent_targets\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_idempotent_ops\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_savepoint_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_savepoints\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_fk_child\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_fk_parent\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_constraint_attempts\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_constraints\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_payments\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_order_items\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_orders\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_inventory\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_rel_products\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_rel_users\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_indexed\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_trigger_audit\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_edge_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_edge_payloads\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_transfer_events\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_accounts\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_item_events\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_items\");\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await validate(c.db);\n\t\t},\n\n\t\trunPhase: async (c, input: RunPhaseInput): Promise => {\n\t\t\tconst mode = input.mode ?? \"balanced\";\n\t\t\tconst iterations = Math.max(1, Math.floor(input.iterations));\n\t\t\tconst keySpace = Math.max(1, Math.floor(input.keySpace ?? DEFAULT_KEY_SPACE));\n\t\t\tconst maxPayloadBytes = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(input.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES),\n\t\t\t);\n\t\t\tconst growthTargetBytes = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(input.growthTargetBytes ?? DEFAULT_GROWTH_TARGET_BYTES),\n\t\t\t);\n\t\t\tconst rng = makeRng(`${input.seed}:${input.phase}:${mode}`);\n\t\t\tconst ops: Record = {};\n\t\t\tlet stage = \"ensureAccounts\";\n\n\t\t\ttry {\n\t\t\t\tawait ensureAccounts(c.db);\n\n\t\t\t\tfor (let i = 0; i < iterations; i += 1) {\n\t\t\t\t\tconst kind = chooseKind(mode, rng);\n\t\t\t\t\tops[kind] = (ops[kind] ?? 0) + 1;\n\t\t\t\t\tstage = `base:${kind}:iteration:${i}`;\n\n\t\t\t\t\tif (kind === \"transfer\") {\n\t\t\t\t\t\tconst fromIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1);\n\t\t\t\t\t\tlet toIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1);\n\t\t\t\t\t\tif (toIndex === fromIndex) toIndex = (toIndex + 1) % ACCOUNT_COUNT;\n\t\t\t\t\t\tconst fromAccount = `acct-${fromIndex}`;\n\t\t\t\t\t\tconst toAccount = `acct-${toIndex}`;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyTransfer(c.db, {\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\tfromAccount,\n\t\t\t\t\t\t\t\ttoAccount,\n\t\t\t\t\t\t\t\tamount: intBetween(rng, 1, 500),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation transfer failed at iteration ${i} from ${fromAccount} to ${toAccount}`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (kind === \"hot\") {\n\t\t\t\t\t\tconst itemKey = `hot-${intBetween(rng, 0, 3)}`;\n\t\t\t\t\t\tconst updates = intBetween(rng, 2, mode === \"hot\" ? 12 : 5);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyHotUpdates(c.db, {\n\t\t\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\titemKey,\n\t\t\t\t\t\t\t\tupdates,\n\t\t\t\t\t\t\t\tpayloadBytes: intBetween(rng, 1, maxPayloadBytes),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation hot failed at iteration ${i} for ${itemKey} with ${updates} updates`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst itemKey =\n\t\t\t\t\t\t\tmode === \"hot\" && rng() < 0.6\n\t\t\t\t\t\t\t\t? `hot-${intBetween(rng, 0, 3)}`\n\t\t\t\t\t\t\t\t: `item-${intBetween(rng, 0, keySpace - 1)}`;\n\t\t\t\t\t\tconst payloadBytes =\n\t\t\t\t\t\t\tmode === \"payloads\"\n\t\t\t\t\t\t\t\t? intBetween(rng, Math.min(256, maxPayloadBytes), maxPayloadBytes)\n\t\t\t\t\t\t\t\t: intBetween(rng, 1, maxPayloadBytes);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyItemOperation(c.db, {\n\t\t\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\titemKey,\n\t\t\t\t\t\t\t\tpayloadBytes,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation ${kind} failed at iteration ${i} for ${JSON.stringify(itemKey)} with payloadBytes ${payloadBytes}`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstage = \"deep-scenarios\";\n\t\t\t\tawait applyDeepScenarios(c.db, {\n\t\t\t\t\tseed: input.seed,\n\t\t\t\t\tphase: input.phase,\n\t\t\t\t\tmode,\n\t\t\t\t\titerations,\n\t\t\t\t\trng,\n\t\t\t\t\tmaxPayloadBytes,\n\t\t\t\t\tgrowthTargetBytes,\n\t\t\t\t\tops,\n\t\t\t\t});\n\n\t\t\t\tstage = \"validate\";\n\t\t\t\treturn {\n\t\t\t\t\tseed: input.seed,\n\t\t\t\t\tphase: input.phase,\n\t\t\t\t\tmode,\n\t\t\t\t\titerations,\n\t\t\t\t\tops,\n\t\t\t\t\tvalidation: await validate(c.db),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconst detail =\n\t\t\t\t\terror instanceof Error ? error.message : typeof error === \"string\" ? error : String(error);\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`runPhase failed during ${stage} for mode ${mode} phase ${input.phase} seed ${input.seed}: ${detail}`,\n\t\t\t\t\t{ cause: error },\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tvalidate: async (c) => {\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await validate(c.db);\n\t\t},\n\n\t\tdebugItemMismatches: async (c, limit?: number) => {\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await debugItemMismatches(c.db, limit ?? 5);\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { SqliteNativeMetrics } from \"rivetkit/db\";\nimport { db } from \"rivetkit/db\";\n\ninterface RunCycleInput {\n\tseed: string;\n\tcycle: number;\n\tinsertRows?: number;\n\trowBytes?: number;\n\tdeleteRows?: number;\n\tretainRows?: number;\n\tscanRows?: number;\n}\n\ninterface CountRow {\n\tcount: number;\n}\n\ninterface StorageRow {\n\tpage_count: number;\n\tfreelist_count: number;\n\tpage_size: number;\n\tvfs: SqliteNativeMetrics | null;\n}\n\nconst DEFAULT_INSERT_ROWS = 128;\nconst DEFAULT_ROW_BYTES = 16 * 1024;\n// const DEFAULT_DELETE_ROWS = 64;\n// const DEFAULT_RETAIN_ROWS = 1024;\nconst DEFAULT_SCAN_ROWS = 512;\nconst INSERT_BATCH_ROWS = 32;\n\nfunction finiteInt(value: number | undefined, fallback: number): number {\n\tif (value === undefined) return fallback;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`expected a non-negative finite number, got ${value}`);\n\t}\n\treturn Math.floor(value);\n}\n\nfunction copyNativeMetrics(\n\tmetrics: SqliteNativeMetrics | null | undefined,\n): SqliteNativeMetrics | null {\n\tif (!metrics) return null;\n\tconst raw = metrics as unknown as Record;\n\tconst numberField = (camel: string, snake: string) =>\n\t\tNumber(raw[camel] ?? raw[snake] ?? 0);\n\treturn {\n\t\trequestBuildNs: numberField(\"requestBuildNs\", \"request_build_ns\"),\n\t\tserializeNs: numberField(\"serializeNs\", \"serialize_ns\"),\n\t\ttransportNs: numberField(\"transportNs\", \"transport_ns\"),\n\t\tstateUpdateNs: numberField(\"stateUpdateNs\", \"state_update_ns\"),\n\t\ttotalNs: numberField(\"totalNs\", \"total_ns\"),\n\t\tcommitCount: numberField(\"commitCount\", \"commit_count\"),\n\t\tpageCacheEntries: numberField(\"pageCacheEntries\", \"page_cache_entries\"),\n\t\tpageCacheWeightedSize: numberField(\n\t\t\t\"pageCacheWeightedSize\",\n\t\t\t\"page_cache_weighted_size\",\n\t\t),\n\t\tpageCacheCapacityPages: numberField(\n\t\t\t\"pageCacheCapacityPages\",\n\t\t\t\"page_cache_capacity_pages\",\n\t\t),\n\t\twriteBufferDirtyPages: numberField(\n\t\t\t\"writeBufferDirtyPages\",\n\t\t\t\"write_buffer_dirty_pages\",\n\t\t),\n\t\tdbSizePages: numberField(\"dbSizePages\", \"db_size_pages\"),\n\t};\n}\n\nasync function queryOne(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tsql: string,\n\t...args: unknown[]\n): Promise {\n\tconst rows = await database.execute(sql, ...args);\n\tif (!rows[0]) throw new Error(`query returned no rows: ${sql}`);\n\treturn rows[0] as T;\n}\n\nasync function storageStats(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n\tnativeMetrics?: () =>\n\t\t| SqliteNativeMetrics\n\t\t| Promise\n\t\t| null;\n}): Promise {\n\tconst [pageCount, freelistCount, pageSize] = await Promise.all([\n\t\tqueryOne<{ page_count: number }>(database, \"PRAGMA page_count\"),\n\t\tqueryOne<{ freelist_count: number }>(database, \"PRAGMA freelist_count\"),\n\t\tqueryOne<{ page_size: number }>(database, \"PRAGMA page_size\"),\n\t]);\n\n\tconst nativeMetrics = await database.nativeMetrics?.();\n\tconst copiedMetrics = copyNativeMetrics(nativeMetrics);\n\n\treturn {\n\t\tpage_count: pageCount.page_count,\n\t\tfreelist_count: freelistCount.freelist_count,\n\t\tpage_size: pageSize.page_size,\n\t\tvfs: copiedMetrics,\n\t};\n}\n\nexport const sqliteMemoryPressure = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tstate: {\n\t\tsleepCount: 0,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS pressure_rows (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tseed TEXT NOT NULL,\n\t\t\t\t\tcycle INTEGER NOT NULL,\n\t\t\t\t\tbucket INTEGER NOT NULL,\n\t\t\t\t\tpayload BLOB NOT NULL,\n\t\t\t\t\ttouched_count INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_pressure_rows_seed_cycle ON pressure_rows(seed, cycle)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_pressure_rows_bucket ON pressure_rows(bucket)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS pressure_cycles (\n\t\t\t\t\tcycle INTEGER PRIMARY KEY,\n\t\t\t\t\tseed TEXT NOT NULL,\n\t\t\t\t\tinserted_rows INTEGER NOT NULL,\n\t\t\t\t\tdeleted_rows INTEGER NOT NULL,\n\t\t\t\t\tactive_rows INTEGER NOT NULL,\n\t\t\t\t\tactive_bytes INTEGER NOT NULL,\n\t\t\t\t\tduration_ms REAL NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t\tconsole.log(\n\t\t\tJSON.stringify({\n\t\t\t\tkind: \"sqlite_memory_pressure_on_sleep\",\n\t\t\t\tactorId: c.actorId,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t}),\n\t\t);\n\t},\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"DELETE FROM pressure_cycles\");\n\t\t\tawait c.db.execute(\"DELETE FROM pressure_rows\");\n\t\t\tawait c.db.execute(\"VACUUM\");\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tstorage: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\treleaseStorage: async (c) => {\n\t\t\tconst before = await storageStats(c.db);\n\t\t\t// Keep the remote DB large for the sleep reclamation soak.\n\t\t\t// await c.db.execute(\"DELETE FROM pressure_cycles\");\n\t\t\t// await c.db.execute(\"DELETE FROM pressure_rows\");\n\t\t\t// await c.db.execute(\"VACUUM\");\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tbefore,\n\t\t\t\tafter: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\tstats: async (c) => {\n\t\t\tconst rowStats = await queryOne<{\n\t\t\t\tactive_rows: number;\n\t\t\t\tactive_bytes: number | null;\n\t\t\t\ttouched_sum: number | null;\n\t\t\t}>(\n\t\t\t\tc.db,\n\t\t\t\t\"SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes, COALESCE(SUM(touched_count), 0) AS touched_sum FROM pressure_rows\",\n\t\t\t);\n\t\t\tconst cycles = await queryOne(\n\t\t\t\tc.db,\n\t\t\t\t\"SELECT COUNT(*) AS count FROM pressure_cycles\",\n\t\t\t);\n\t\t\tconst integrity = await queryOne<{ integrity_check: string }>(\n\t\t\t\tc.db,\n\t\t\t\t\"PRAGMA integrity_check\",\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\ttouchedCount: rowStats.touched_sum ?? 0,\n\t\t\t\tcycles: cycles.count,\n\t\t\t\tintegrityCheck: integrity.integrity_check,\n\t\t\t\tstorage: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\trunCycle: async (c, input: RunCycleInput) => {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst insertRows = finiteInt(input.insertRows, DEFAULT_INSERT_ROWS);\n\t\t\tconst rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES);\n\t\t\t// const deleteRows = finiteInt(input.deleteRows, DEFAULT_DELETE_ROWS);\n\t\t\t// const retainRows = Math.max(\n\t\t\t// \t1,\n\t\t\t// \tfiniteInt(input.retainRows, DEFAULT_RETAIN_ROWS),\n\t\t\t// );\n\t\t\tconst scanRows = Math.max(1, finiteInt(input.scanRows, DEFAULT_SCAN_ROWS));\n\t\t\tconst now = Date.now();\n\t\t\tlet insertedRows = 0;\n\t\t\tconst logStage = (\n\t\t\t\tstage: string,\n\t\t\t\tphase: \"start\" | \"end\" | \"error\",\n\t\t\t\tfields: Record = {},\n\t\t\t) => {\n\t\t\t\tconsole.log(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\tkind: \"sqlite_memory_pressure_run_cycle_stage\",\n\t\t\t\t\t\tactorId: c.actorId,\n\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\tcycle: input.cycle,\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tphase,\n\t\t\t\t\t\telapsedMs: performance.now() - startedAt,\n\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t...fields,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst executeTimed = async (\n\t\t\t\tstage: string,\n\t\t\t\tsql: string,\n\t\t\t\t...args: unknown[]\n\t\t\t) => {\n\t\t\t\tconst stageStartedAt = performance.now();\n\t\t\t\tlogStage(stage, \"start\", { argCount: args.length });\n\t\t\t\ttry {\n\t\t\t\t\tconst rows = await c.db.execute(sql, ...args);\n\t\t\t\t\tlogStage(stage, \"end\", {\n\t\t\t\t\t\tdurationMs: performance.now() - stageStartedAt,\n\t\t\t\t\t\trowCount: rows.length,\n\t\t\t\t\t});\n\t\t\t\t\treturn rows;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogStage(stage, \"error\", {\n\t\t\t\t\t\tdurationMs: performance.now() - stageStartedAt,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t};\n\t\t\tlogStage(\"run_cycle\", \"start\", {\n\t\t\t\tinsertRows,\n\t\t\t\trowBytes,\n\t\t\t\tscanRows,\n\t\t\t});\n\n\t\t\tawait executeTimed(\"begin\", \"BEGIN\");\n\t\t\ttry {\n\t\t\t\twhile (insertedRows < insertRows) {\n\t\t\t\t\tconst batchRows = Math.min(\n\t\t\t\t\t\tINSERT_BATCH_ROWS,\n\t\t\t\t\t\tinsertRows - insertedRows,\n\t\t\t\t\t);\n\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\tconst args: unknown[] = [];\n\n\t\t\t\t\tfor (let i = 0; i < batchRows; i += 1) {\n\t\t\t\t\t\tconst rowIndex = insertedRows + i;\n\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?, randomblob(?), 0, ?)\");\n\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\tinput.seed,\n\t\t\t\t\t\t\tinput.cycle,\n\t\t\t\t\t\t\t(input.cycle + rowIndex) % 32,\n\t\t\t\t\t\t\trowBytes,\n\t\t\t\t\t\t\tnow + rowIndex,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait executeTimed(\n\t\t\t\t\t\t\"insert_batch\",\n\t\t\t\t\t\t`INSERT INTO pressure_rows (seed, cycle, bucket, payload, touched_count, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t...args,\n\t\t\t\t\t);\n\t\t\t\t\tinsertedRows += batchRows;\n\t\t\t\t\tlogStage(\"insert_batch_progress\", \"end\", {\n\t\t\t\t\t\tinsertedRows,\n\t\t\t\t\t\tbatchRows,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait executeTimed(\"commit\", \"COMMIT\");\n\t\t\t} catch (err) {\n\t\t\t\tawait executeTimed(\"rollback\", \"ROLLBACK\").catch(() => undefined);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tconst scan = await executeTimed(\n\t\t\t\t\"scan_recent\",\n\t\t\t\t\"SELECT id, length(payload) AS payload_bytes FROM pressure_rows ORDER BY id DESC LIMIT ?\",\n\t\t\t\tscanRows,\n\t\t\t);\n\t\t\tconst bucketAgg = await executeTimed(\n\t\t\t\t\"bucket_agg\",\n\t\t\t\t\"SELECT bucket, COUNT(*) AS rows, SUM(length(payload)) AS bytes FROM pressure_rows WHERE bucket BETWEEN ? AND ? GROUP BY bucket ORDER BY bucket\",\n\t\t\t\tinput.cycle % 16,\n\t\t\t\t(input.cycle % 16) + 15,\n\t\t\t);\n\t\t\tawait executeTimed(\n\t\t\t\t\"touch_recent\",\n\t\t\t\t\"UPDATE pressure_rows SET touched_count = touched_count + 1 WHERE id IN (SELECT id FROM pressure_rows ORDER BY id DESC LIMIT ?)\",\n\t\t\t\tMath.min(scanRows, insertRows),\n\t\t\t);\n\n\t\t\tlet deletedRows = 0;\n\t\t\t// const beforeDelete = await queryOne(\n\t\t\t// \tc.db,\n\t\t\t// \t\"SELECT COUNT(*) AS count FROM pressure_rows\",\n\t\t\t// );\n\t\t\t// const overRetainRows = Math.max(0, beforeDelete.count - retainRows);\n\t\t\t// const deleteLimit = Math.max(deleteRows, overRetainRows);\n\t\t\t// if (deleteLimit > 0) {\n\t\t\t// \tawait c.db.execute(\n\t\t\t// \t\t\"DELETE FROM pressure_rows WHERE id IN (SELECT id FROM pressure_rows ORDER BY id ASC LIMIT ?)\",\n\t\t\t// \t\tdeleteLimit,\n\t\t\t// \t);\n\t\t\t// \tconst afterDelete = await queryOne(\n\t\t\t// \t\tc.db,\n\t\t\t// \t\t\"SELECT changes() AS count\",\n\t\t\t// \t);\n\t\t\t// \tdeletedRows = afterDelete.count;\n\t\t\t// }\n\n\t\t\tconst rowStatsRows = await executeTimed(\n\t\t\t\t\"row_stats\",\n\t\t\t\t\"SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes FROM pressure_rows\",\n\t\t\t);\n\t\t\tconst rowStats = rowStatsRows[0] as\n\t\t\t\t| {\n\t\t\t\t\t\tactive_rows: number;\n\t\t\t\t\t\tactive_bytes: number | null;\n\t\t\t\t }\n\t\t\t\t| undefined;\n\t\t\tif (!rowStats) throw new Error(\"query returned no rows: row_stats\");\n\t\t\tconst integrityRows = await executeTimed(\n\t\t\t\t\"integrity_check\",\n\t\t\t\t\"PRAGMA integrity_check\",\n\t\t\t);\n\t\t\tconst integrity = integrityRows[0] as\n\t\t\t\t| { integrity_check: string }\n\t\t\t\t| undefined;\n\t\t\tif (!integrity) {\n\t\t\t\tthrow new Error(\"query returned no rows: integrity_check\");\n\t\t\t}\n\t\t\tconst durationMs = performance.now() - startedAt;\n\n\t\t\tawait executeTimed(\n\t\t\t\t\"record_cycle\",\n\t\t\t\t\"INSERT OR REPLACE INTO pressure_cycles (cycle, seed, inserted_rows, deleted_rows, active_rows, active_bytes, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n\t\t\t\tinput.cycle,\n\t\t\t\tinput.seed,\n\t\t\t\tinsertedRows,\n\t\t\t\tdeletedRows,\n\t\t\t\trowStats.active_rows,\n\t\t\t\trowStats.active_bytes ?? 0,\n\t\t\t\tdurationMs,\n\t\t\t\tnow,\n\t\t\t);\n\n\t\t\tconst storageStartedAt = performance.now();\n\t\t\tlogStage(\"storage_stats\", \"start\");\n\t\t\tconst storage = await storageStats(c.db);\n\t\t\tlogStage(\"storage_stats\", \"end\", {\n\t\t\t\tdurationMs: performance.now() - storageStartedAt,\n\t\t\t\tpageCount: storage.page_count,\n\t\t\t\tdbSizePages: storage.vfs?.dbSizePages ?? null,\n\t\t\t\tpageCacheEntries: storage.vfs?.pageCacheEntries ?? null,\n\t\t\t});\n\t\t\tlogStage(\"run_cycle\", \"end\", {\n\t\t\t\tdurationMs,\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\tpageCount: storage.page_count,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tseed: input.seed,\n\t\t\t\tcycle: input.cycle,\n\t\t\t\tinsertedRows,\n\t\t\t\tdeletedRows,\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\tscannedRows: scan.length,\n\t\t\t\tbucketsRead: bucketAgg.length,\n\t\t\t\tintegrityCheck: integrity.integrity_check,\n\t\t\t\tstorage,\n\t\t\t\tdurationMs,\n\t\t\t};\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_SLEEP_GRACE_PERIOD_MS = 120_000;\nconst DEFAULT_ON_SLEEP_DELAY_MS = 0;\n\ntype EntryRow = {\n\trequest_id: string;\n\tidx: number;\n\tcreated_at: number;\n};\n\ntype CountRow = {\n\tcount: number;\n};\n\ntype SleepStateRow = {\n\tsleep_started_at: number;\n};\n\ntype DebugEventRow = {\n\tevent_id: string;\n\tname: string;\n\tactor_id: string;\n\tconnection_id: string | null;\n\trequest_id: string | null;\n\tdetails_json: string;\n\tcreated_at: number;\n};\n\ntype ExpectedRequest = {\n\trequestId: string;\n\tseconds: number;\n};\n\ntype DebugEventInput = {\n\tname: string;\n\tconnectionId?: string;\n\trequestId?: string;\n\tdetails?: Record;\n\tcreatedAt?: number;\n};\n\ntype DebugContext = {\n\tactorId: string;\n\tdb: {\n\t\texecute: (query: string, ...params: unknown[]) => Promise;\n\t};\n\tlog: {\n\t\twarn: (payload: unknown) => void;\n\t};\n};\n\nconst debugSocketsByActorId = new Map>();\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction positiveInteger(value: unknown, name: string) {\n\tif (!Number.isInteger(value) || (value as number) < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\n\treturn value as number;\n}\n\nfunction stringValue(value: unknown, name: string) {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`${name} must be a non-empty string`);\n\t}\n\n\treturn value;\n}\n\nfunction typedRows(rows: unknown[]): T[] {\n\treturn rows as T[];\n}\n\nfunction numberFromEnv(name: string, fallback: number): number {\n\tconst value = process.env[name];\n\tif (value === undefined || value === \"\") return fallback;\n\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed) || parsed < 0) {\n\t\tthrow new Error(`${name} must be a finite non-negative number`);\n\t}\n\n\treturn parsed;\n}\n\nfunction send(websocket: UniversalWebSocket, payload: unknown) {\n\tif (websocket.readyState !== 1) return;\n\twebsocket.send(JSON.stringify(payload));\n}\n\nfunction debugPayload(row: DebugEventRow, replayed: boolean) {\n\treturn {\n\t\ttype: \"debugEvent\",\n\t\teventId: row.event_id,\n\t\tname: row.name,\n\t\tactorId: row.actor_id,\n\t\tconnectionId: row.connection_id,\n\t\trequestId: row.request_id,\n\t\tdetails: JSON.parse(row.details_json) as Record,\n\t\tcreatedAt: row.created_at,\n\t\treplayed,\n\t};\n}\n\nfunction publishDebugEvent(row: DebugEventRow) {\n\tconst sockets = debugSocketsByActorId.get(row.actor_id);\n\tif (!sockets) return;\n\n\tfor (const socket of sockets) {\n\t\tsend(socket, debugPayload(row, false));\n\t}\n}\n\nfunction addDebugSocket(actorId: string, websocket: UniversalWebSocket) {\n\tconst sockets = debugSocketsByActorId.get(actorId) ?? new Set();\n\tsockets.add(websocket);\n\tdebugSocketsByActorId.set(actorId, sockets);\n\n\treturn () => {\n\t\tsockets.delete(websocket);\n\t\tif (sockets.size === 0) {\n\t\t\tdebugSocketsByActorId.delete(actorId);\n\t\t}\n\t};\n}\n\nasync function recordDebugEvent(c: DebugContext, input: DebugEventInput) {\n\tconst row: DebugEventRow = {\n\t\tevent_id: crypto.randomUUID(),\n\t\tname: input.name,\n\t\tactor_id: c.actorId,\n\t\tconnection_id: input.connectionId ?? null,\n\t\trequest_id: input.requestId ?? null,\n\t\tdetails_json: JSON.stringify(input.details ?? {}),\n\t\tcreated_at: input.createdAt ?? Date.now(),\n\t};\n\n\ttry {\n\t\tawait c.db.execute(\n\t\t\t\"INSERT INTO mock_agentic_debug_events (event_id, name, actor_id, connection_id, request_id, details_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t\t\trow.event_id,\n\t\t\trow.name,\n\t\t\trow.actor_id,\n\t\t\trow.connection_id,\n\t\t\trow.request_id,\n\t\t\trow.details_json,\n\t\t\trow.created_at,\n\t\t);\n\t\tpublishDebugEvent(row);\n\t} catch (error) {\n\t\tc.log.warn({\n\t\t\tmsg: \"mock agentic debug event failed\",\n\t\t\tname: input.name,\n\t\t\terr: error instanceof Error ? error.message : String(error),\n\t\t});\n\t}\n}\n\nasync function replayDebugEvents(\n\tdatabase: DebugContext[\"db\"],\n\twebsocket: UniversalWebSocket,\n) {\n\tconst rows = typedRows(\n\t\tawait database.execute(`\n\t\t\tSELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at\n\t\t\tFROM (\n\t\t\t\tSELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at\n\t\t\t\tFROM mock_agentic_debug_events\n\t\t\t\tORDER BY created_at DESC\n\t\t\t\tLIMIT 200\n\t\t\t)\n\t\t\tORDER BY created_at ASC\n\t\t`),\n\t);\n\n\tfor (const row of rows) {\n\t\tsend(websocket, debugPayload(row, true));\n\t}\n}\n\nfunction verifyEntryRows(rows: EntryRow[], expectedSeconds: number) {\n\tconst seen = new Set();\n\tconst indexes = rows.map((row) => row.idx).sort((a, b) => a - b);\n\tfor (const idx of indexes) seen.add(idx);\n\n\tconst missing: number[] = [];\n\tfor (let idx = 1; idx <= expectedSeconds; idx += 1) {\n\t\tif (!seen.has(idx)) missing.push(idx);\n\t}\n\n\tconst contiguous =\n\t\trows.length === expectedSeconds &&\n\t\tmissing.length === 0 &&\n\t\tindexes.every((idx, offset) => idx === offset + 1);\n\n\treturn {\n\t\texpectedSeconds,\n\t\tcount: rows.length,\n\t\tcontiguous,\n\t\tmissing,\n\t\tindexes,\n\t\tok: contiguous,\n\t};\n}\n\nfunction verifyAllRows(rows: EntryRow[], expectedRequests: ExpectedRequest[]) {\n\tconst expectedByRequest = new Map(\n\t\texpectedRequests.map((request) => [request.requestId, request.seconds]),\n\t);\n\tconst rowsByRequest = new Map();\n\n\tfor (const row of rows) {\n\t\tconst requestRows = rowsByRequest.get(row.request_id) ?? [];\n\t\trequestRows.push(row);\n\t\trowsByRequest.set(row.request_id, requestRows);\n\t}\n\n\tconst requests = expectedRequests.map((request) => {\n\t\tconst result = verifyEntryRows(\n\t\t\trowsByRequest.get(request.requestId) ?? [],\n\t\t\trequest.seconds,\n\t\t);\n\t\treturn {\n\t\t\trequestId: request.requestId,\n\t\t\t...result,\n\t\t};\n\t});\n\n\tconst unexpectedRequestIds = [...rowsByRequest.keys()]\n\t\t.filter((requestId) => !expectedByRequest.has(requestId))\n\t\t.sort();\n\tconst expectedTotalRows = expectedRequests.reduce(\n\t\t(total, request) => total + request.seconds,\n\t\t0,\n\t);\n\tconst ok =\n\t\tunexpectedRequestIds.length === 0 &&\n\t\trows.length === expectedTotalRows &&\n\t\trequests.every((request) => request.ok);\n\n\treturn {\n\t\ttype: \"verifiedAll\",\n\t\texpectedRequests: expectedRequests.length,\n\t\texpectedTotalRows,\n\t\ttotalRows: rows.length,\n\t\trows,\n\t\tunexpectedRequestIds,\n\t\trequests,\n\t\tok,\n\t};\n}\n\nexport const mockAgenticLoop = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: DEFAULT_SLEEP_GRACE_PERIOD_MS,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_entries (\n\t\t\t\t\trequest_id TEXT NOT NULL,\n\t\t\t\t\tidx INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (request_id, idx)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_mock_agentic_entries_created_at ON mock_agentic_entries(created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_sleep_state (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tsleep_started_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_debug_events (\n\t\t\t\t\tevent_id TEXT PRIMARY KEY,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tactor_id TEXT NOT NULL,\n\t\t\t\t\tconnection_id TEXT,\n\t\t\t\t\trequest_id TEXT,\n\t\t\t\t\tdetails_json TEXT NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_mock_agentic_debug_events_created_at ON mock_agentic_debug_events(created_at)\",\n\t\t\t);\n\t\t},\n\t}),\n\tasync onWake(c) {\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onWake\",\n\t\t\tdetails: {\n\t\t\t\tkey: c.key,\n\t\t\t\tname: c.name,\n\t\t\t},\n\t\t});\n\t},\n\tasync onSleep(c) {\n\t\tconst delayMs = numberFromEnv(\n\t\t\t\"MOCK_AGENTIC_ON_SLEEP_DELAY_MS\",\n\t\t\tDEFAULT_ON_SLEEP_DELAY_MS,\n\t\t);\n\t\tconst sleepStartedAt = Date.now();\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onSleepStart\",\n\t\t\tcreatedAt: sleepStartedAt,\n\t\t\tdetails: {\n\t\t\t\tdelayMs,\n\t\t\t},\n\t\t});\n\t\tawait c.db.execute(\n\t\t\t\"INSERT OR REPLACE INTO mock_agentic_sleep_state (id, sleep_started_at) VALUES (1, ?)\",\n\t\t\tsleepStartedAt,\n\t\t);\n\t\tc.log.info({\n\t\t\tmsg: \"mock agentic loop onSleep delay\",\n\t\t\tdelayMs,\n\t\t\tsleepStartedAt,\n\t\t});\n\t\tawait sleep(delayMs);\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onSleepEnd\",\n\t\t\tdetails: {\n\t\t\t\tdelayMs,\n\t\t\t\tsleepStartedAt,\n\t\t\t\telapsedMs: Date.now() - sleepStartedAt,\n\t\t\t},\n\t\t});\n\t},\n\tasync onRequest(c, request) {\n\t\tconst url = new URL(request.url);\n\t\tif (url.pathname === \"/bypass\" || url.pathname === \"/request/bypass\") {\n\t\t\tconst [sleepState] = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn new Response(JSON.stringify({\n\t\t\t\ttype: \"bypass\",\n\t\t\t\ttransport: \"http\",\n\t\t\t\tsleepStarted: sleepState !== undefined,\n\t\t\t\tsleepStartedAt: sleepState?.sleep_started_at ?? null,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t}), {\n\t\t\t\theaders: {\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"not found\", { status: 404 });\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tconst connectionId = crypto.randomUUID();\n\t\tlet activeInference: Promise | undefined;\n\t\tconst removeDebugSocket = addDebugSocket(c.actorId, websocket);\n\n\t\tsend(websocket, {\n\t\t\ttype: \"hello\",\n\t\t\tconnectionId,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tawait replayDebugEvents(c.db, websocket);\n\t\t\t} catch (error) {\n\t\t\t\tc.log.warn({\n\t\t\t\t\tmsg: \"mock agentic debug replay failed\",\n\t\t\t\t\terr: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait recordDebugEvent(c, {\n\t\t\t\tname: \"webSocketOpen\",\n\t\t\t\tconnectionId,\n\t\t\t});\n\t\t})();\n\n\t\tconst verify = async (requestId: string, expectedSeconds: number) => {\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC\",\n\t\t\t\t\trequestId,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\ttype: \"verified\",\n\t\t\t\trequestId,\n\t\t\t\t...verifyEntryRows(rows, expectedSeconds),\n\t\t\t};\n\t\t};\n\n\t\tconst sleepStatus = async () => {\n\t\t\tconst [sleepState] = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tsleepStarted: sleepState !== undefined,\n\t\t\t\tsleepStartedAt: sleepState?.sleep_started_at ?? null,\n\t\t\t};\n\t\t};\n\n\t\tconst runInference = async (requestId: string, seconds: number) => {\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"started\",\n\t\t\t\trequestId,\n\t\t\t\tseconds,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t});\n\n\t\t\tawait c.db.execute(\n\t\t\t\t\"DELETE FROM mock_agentic_entries WHERE request_id = ?\",\n\t\t\t\trequestId,\n\t\t\t);\n\n\t\t\tfor (let idx = 1; idx <= seconds; idx += 1) {\n\t\t\t\tawait sleep(1_000);\n\t\t\t\tconst createdAt = Date.now();\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mock_agentic_entries (request_id, idx, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\trequestId,\n\t\t\t\t\tidx,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t);\n\t\t\t\tsend(websocket, {\n\t\t\t\t\ttype: \"progress\",\n\t\t\t\t\trequestId,\n\t\t\t\t\tidx,\n\t\t\t\t\tseconds,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst verification = await verify(requestId, seconds);\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"done\",\n\t\t\t\trequestId,\n\t\t\t\tseconds,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tverification,\n\t\t\t});\n\t\t};\n\n\t\twebsocket.addEventListener(\"message\", async (event: RivetMessageEvent) => {\n\t\t\ttry {\n\t\t\t\tif (typeof event.data !== \"string\") {\n\t\t\t\t\tthrow new Error(\"message data must be a JSON string\");\n\t\t\t\t}\n\n\t\t\t\tconst message = JSON.parse(event.data) as Record;\n\t\t\t\tconst type = stringValue(message.type, \"type\");\n\n\t\t\t\tif (type === \"history\") {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY created_at ASC, request_id ASC, idx ASC\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM mock_agentic_entries\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"history\",\n\t\t\t\t\t\ttotalRows: count?.count ?? rows.length,\n\t\t\t\t\t\tentries: rows,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (type === \"ping\") {\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\tprobeId: stringValue(message.probeId, \"probeId\"),\n\t\t\t\t\t\t...(await sleepStatus()),\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (type === \"verify\") {\n\t\t\t\t\tconst requestId = stringValue(message.requestId, \"requestId\");\n\t\t\t\t\tconst expectedSeconds = positiveInteger(\n\t\t\t\t\t\tmessage.expectedSeconds,\n\t\t\t\t\t\t\"expectedSeconds\",\n\t\t\t\t\t);\n\t\t\t\t\tsend(websocket, await verify(requestId, expectedSeconds));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (type === \"infer\") {\n\t\t\t\t\tconst requestId = stringValue(message.requestId, \"requestId\");\n\t\t\t\t\tconst seconds = positiveInteger(message.seconds, \"seconds\");\n\t\t\t\t\tawait recordDebugEvent(c, {\n\t\t\t\t\t\tname: \"inferenceRequested\",\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\trequestId,\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\tseconds,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tconst previousInference = activeInference;\n\t\t\t\t\tconst inference = (async () => {\n\t\t\t\t\t\tawait previousInference?.catch(() => undefined);\n\t\t\t\t\t\tawait runInference(requestId, seconds);\n\t\t\t\t\t})();\n\t\t\t\t\tactiveInference = inference;\n\t\t\t\t\tawait c.keepAwake(inference);\n\t\t\t\t\tif (activeInference === inference) {\n\t\t\t\t\t\tactiveInference = undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthrow new Error(`unknown message type: ${type}`);\n\t\t\t} catch (error) {\n\t\t\t\tsend(websocket, {\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t\t: \"unknown websocket error\",\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", async () => {\n\t\t\tremoveDebugSocket();\n\t\t\tawait recordDebugEvent(c, {\n\t\t\t\tname: \"webSocketClose\",\n\t\t\t\tconnectionId,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tverify: async (c, requestId: string, expectedSeconds: number) => {\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC\",\n\t\t\t\t\trequestId,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trequestId,\n\t\t\t\texpectedSeconds,\n\t\t\t\tcount: rows.length,\n\t\t\t\tindexes: rows.map((row) => row.idx),\n\t\t\t};\n\t\t},\n\t\tverifyAll: async (c, expectedRequests: ExpectedRequest[]) => {\n\t\t\tif (!Array.isArray(expectedRequests)) {\n\t\t\t\tthrow new Error(\"expectedRequests must be an array\");\n\t\t\t}\n\n\t\t\tfor (const request of expectedRequests) {\n\t\t\t\tstringValue(request.requestId, \"requestId\");\n\t\t\t\tpositiveInteger(request.seconds, \"seconds\");\n\t\t\t}\n\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY request_id ASC, idx ASC\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn verifyAllRows(rows, expectedRequests);\n\t\t},\n\t},\n});\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\n\n// Minimal non-hibernatable WebSocket actor for fuzz-testing the\n// force-sleep → gateway close path. Keeps state intentionally tiny so the\n// only thing being exercised is the close lifecycle, not user code.\nexport const sleepCloseFuzz = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\tconst interval = setInterval(() => {\n\t\t\tif (websocket.readyState !== 1) return;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"tick\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t}, 500);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.messageCount += 1;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"echo\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\treceived: event.data,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(interval);\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_TOKENS_PER_SECOND = 20;\nconst DEFAULT_DURATION_MS = 5_000;\n\nfunction send(websocket: UniversalWebSocket, payload: unknown): void {\n\tif (websocket.readyState !== 1) return;\n\twebsocket.send(JSON.stringify(payload));\n}\n\nfunction parsePositiveNumber(\n\tvalue: unknown,\n\tname: string,\n\tfallback: number,\n): number {\n\tif (value === undefined || value === null) return fallback;\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed) || parsed <= 0) {\n\t\tthrow new Error(`${name} must be a positive number`);\n\t}\n\treturn parsed;\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise {\n\tif (signal.aborted) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nexport const loadTestAgent = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 30_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS messages (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tconnection_id TEXT NOT NULL,\n\t\t\t\t\trequest_id TEXT NOT NULL,\n\t\t\t\t\ttoken_index INTEGER NOT NULL,\n\t\t\t\t\ttoken TEXT NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE INDEX IF NOT EXISTS messages_request_idx\n\t\t\t\tON messages (request_id, token_index)\n\t\t\t`);\n\t\t},\n\t}),\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tinferenceCount: 0,\n\t\ttokenCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tsend(websocket, {\n\t\t\ttype: \"connected\",\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\twebsocket.addEventListener(\"message\", async (event: RivetMessageEvent) => {\n\t\t\ttry {\n\t\t\t\tconst message =\n\t\t\t\t\ttypeof event.data === \"string\"\n\t\t\t\t\t\t? JSON.parse(event.data)\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t// Fast-path ping: echo back without touching SQLite so the client can measure raw\n\t\t\t\t// RTT without the per-message storage write. Used by the counter-latency client's\n\t\t\t\t// first two probes after WS open.\n\t\t\t\tif (message && message.type === \"ping\") {\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\tid: message.id,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!message || message.type !== \"inference\") {\n\t\t\t\t\tthrow new Error(\"expected inference message\");\n\t\t\t\t}\n\n\t\t\t\tconst requestId =\n\t\t\t\t\ttypeof message.requestId === \"string\" && message.requestId\n\t\t\t\t\t\t? message.requestId\n\t\t\t\t\t\t: crypto.randomUUID();\n\t\t\t\tconst tokensPerSecond = parsePositiveNumber(\n\t\t\t\t\tmessage.tokensPerSecond,\n\t\t\t\t\t\"tokensPerSecond\",\n\t\t\t\t\tDEFAULT_TOKENS_PER_SECOND,\n\t\t\t\t);\n\t\t\t\tconst durationMs = parsePositiveNumber(\n\t\t\t\t\tmessage.durationMs,\n\t\t\t\t\t\"durationMs\",\n\t\t\t\t\tDEFAULT_DURATION_MS,\n\t\t\t\t);\n\t\t\t\tconst intervalMs = 1_000 / tokensPerSecond;\n\t\t\t\tconst targetTokens = Math.max(\n\t\t\t\t\t1,\n\t\t\t\t\tMath.floor((durationMs / 1_000) * tokensPerSecond),\n\t\t\t\t);\n\n\t\t\t\tconst inference = (async () => {\n\t\t\t\t\tc.state.inferenceCount += 1;\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"inference-start\",\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\trequestId,\n\t\t\t\t\t\ttokensPerSecond,\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\ttargetTokens,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\n\t\t\t\t\tconst startedAt = performance.now();\n\t\t\t\t\tfor (let i = 0; i < targetTokens; i++) {\n\t\t\t\t\t\tif (c.abortSignal.aborted || websocket.readyState !== 1) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst tokenIndex = i + 1;\n\t\t\t\t\t\tconst token = `token-${tokenIndex}`;\n\t\t\t\t\t\tconst createdAt = Date.now();\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"INSERT INTO messages (connection_id, request_id, token_index, token, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\ttokenIndex,\n\t\t\t\t\t\t\ttoken,\n\t\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tc.state.tokenCount += 1;\n\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"token\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\ttokenIndex,\n\t\t\t\t\t\t\ttoken,\n\t\t\t\t\t\t\ttimestamp: createdAt,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst nextAt = startedAt + tokenIndex * intervalMs;\n\t\t\t\t\t\tconst delayMs = Math.max(0, nextAt - performance.now());\n\t\t\t\t\t\tif (delayMs > 0) {\n\t\t\t\t\t\t\tawait sleep(delayMs, c.abortSignal);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"inference-complete\",\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\trequestId,\n\t\t\t\t\t\ttokenCount: targetTokens,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t})();\n\n\t\t\t\tawait c.keepAwake(inference);\n\t\t\t} catch (error) {\n\t\t\t\tsend(websocket, {\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t\t: \"unknown websocket error\",\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tinferenceCount: c.state.inferenceCount,\n\t\t\t\ttokenCount: c.state.tokenCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\ntype AgentConcurrent2Request =\n\t| { type: \"agent2_resume\"; version: number }\n\t| { type: \"agent2_connect\"; clientId: string; staggerHandleMs?: number }\n\t| { type: \"force_sleep\" }\n\t| { type: \"ping\"; id?: number };\n\ninterface AgentConcurrent2Step {\n\tname: string;\n\tdurationMs: number;\n\trowCount: number;\n}\n\ninterface AgentConcurrent2QueryStats {\n\ttotal: number;\n\treads: number;\n\tmutations: number;\n\ttx: number;\n\tother: number;\n\trows: number;\n\terrors: number;\n\tslow: number;\n\tmaxMs: number;\n\tmaxStep: string;\n\tbyOperation: Record;\n\tbyTable: Record;\n}\n\ninterface AgentConcurrent2StatsSnapshot {\n\twakeIndex: number;\n\tactorIteration: number;\n\twakeIteration: number;\n\tcycle: AgentConcurrent2QueryStats;\n\twake: AgentConcurrent2QueryStats;\n\tactor: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2WorkloadResult {\n\tname: string;\n\ttotalMs: number;\n\tsteps: AgentConcurrent2Step[];\n}\n\ninterface AgentConcurrent2ResultMessage {\n\ttype: \"agent2_result\";\n\ttrigger: AgentConcurrent2Request[\"type\"];\n\ttotalMs: number;\n\tresults: AgentConcurrent2WorkloadResult[];\n\tstats: AgentConcurrent2StatsSnapshot;\n}\n\ninterface AgentConcurrent2ErrorMessage {\n\ttype: \"agent2_error\";\n\ttrigger: AgentConcurrent2Request[\"type\"] | \"unknown\";\n\terror: string;\n\tstats?: AgentConcurrent2StatsSnapshot;\n}\n\ninterface AgentConcurrent2Vars {\n\tsql: AgentConcurrent2Db | null;\n\twakeStats: AgentConcurrent2QueryStats | null;\n\twakeStartedAt: number | null;\n\twakeIteration: number;\n}\n\ninterface RawRivetDB {\n\texecute: (\n\t\tquery: string,\n\t\t...args: unknown[]\n\t) => Promise[]>;\n}\n\ntype SQLPrimitive = string | number | boolean | null;\n\ninterface AgentConcurrent2State {\n\trunCount: number;\n\twakeCount: number;\n\tqueryStats: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2QueryStatsSet {\n\tcycle: AgentConcurrent2QueryStats;\n\twake: AgentConcurrent2QueryStats;\n\tactor: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2Runtime {\n\tsql: AgentConcurrent2Db;\n\twakeStats: AgentConcurrent2QueryStats;\n\tvars: AgentConcurrent2Vars;\n}\n\ntype AgentConcurrent2Db = (>(\n\tquery: string,\n\t...values: SQLPrimitive[]\n) => Promise) & {\n\twithTransaction(\n\t\tstats: AgentConcurrent2QueryStatsSet,\n\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t): Promise;\n};\n\nclass AsyncMutex {\n\tprivate locked = false;\n\tprivate waiters: Array<() => void> = [];\n\n\tasync acquire(): Promise {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true;\n\t\t\treturn;\n\t\t}\n\t\tawait new Promise((resolve) => this.waiters.push(resolve));\n\t\tthis.locked = true;\n\t}\n\n\trelease(): void {\n\t\tconst next = this.waiters.shift();\n\t\tif (next) {\n\t\t\tnext();\n\t\t\treturn;\n\t\t}\n\t\tthis.locked = false;\n\t}\n}\n\nfunction createSerializedDb(\n\texecute: >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t) => Promise,\n): AgentConcurrent2Db {\n\tconst mutex = new AsyncMutex();\n\tlet activeTransaction: AgentConcurrent2Db | null = null;\n\n\tconst createTransactionDb = (): AgentConcurrent2Db => {\n\t\tconst tx = Object.assign(\n\t\t\t>(\n\t\t\t\tquery: string,\n\t\t\t\t...values: SQLPrimitive[]\n\t\t\t) => execute(query, ...values),\n\t\t\t{\n\t\t\t\twithTransaction: async (\n\t\t\t\t\t_stats: AgentConcurrent2QueryStatsSet,\n\t\t\t\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t\t\t\t): Promise => fn(tx),\n\t\t\t},\n\t\t);\n\t\treturn tx;\n\t};\n\n\tconst queryWithMutex = async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tawait mutex.acquire();\n\t\ttry {\n\t\t\treturn await execute(query, ...values);\n\t\t} finally {\n\t\t\tmutex.release();\n\t\t}\n\t};\n\n\treturn Object.assign(queryWithMutex, {\n\t\twithTransaction: async (\n\t\t\tstats: AgentConcurrent2QueryStatsSet,\n\t\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t\t): Promise => {\n\t\t\tif (activeTransaction) {\n\t\t\t\treturn fn(activeTransaction);\n\t\t\t}\n\t\t\tawait mutex.acquire();\n\t\t\tconst tx = createTransactionDb();\n\t\t\ttry {\n\t\t\t\tawait executeTrackedQuery(execute, stats, \"transaction-begin\", \"BEGIN\");\n\t\t\t\tactiveTransaction = tx;\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fn(tx);\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait executeTrackedQuery(execute, stats, \"transaction-commit\", \"COMMIT\");\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait executeTrackedQuery(\n\t\t\t\t\t\texecute,\n\t\t\t\t\t\tstats,\n\t\t\t\t\t\t\"transaction-rollback\",\n\t\t\t\t\t\t\"ROLLBACK\",\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tactiveTransaction = null;\n\t\t\t\tmutex.release();\n\t\t\t}\n\t\t},\n\t});\n}\n\nconst MESSAGE_COUNT = 84;\nconst MESSAGE_TOOL_REF_COUNT = 122;\nconst TOOL_CALL_COUNT = 61;\nconst EXECUTOR_TOOL_COUNT = 42;\nconst THREAD_EVENT_COUNT = 233;\n\nconst MESSAGE_CONTENT_BYTES = 2_600;\nconst THREAD_EVENT_PAYLOAD_BYTES = 1_000;\nconst TOOL_CALL_RESULT_BYTES = 2_700;\nconst EXECUTOR_TOOL_SCHEMA_BYTES = 550;\nconst SLOW_QUERY_MS = 1_000;\n\nfunction send(\n\twebsocket: UniversalWebSocket,\n\tmessage: AgentConcurrent2ResultMessage | AgentConcurrent2ErrorMessage | object,\n): void {\n\tif (websocket.readyState === 1) {\n\t\twebsocket.send(JSON.stringify(message));\n\t}\n}\n\nexport const loadTestAgent2 = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 1_000,\n\t},\n\tstate: {\n\t\trunCount: 0,\n\t\twakeCount: 0,\n\t\tqueryStats: createAgentConcurrent2QueryStats(),\n\t} as AgentConcurrent2State,\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait createAgentConcurrent2Schema(database);\n\t\t\tawait seedAgentConcurrent2Data(database);\n\t\t},\n\t}),\n\tvars: {\n\t\tsql: null,\n\t\twakeStats: null,\n\t\twakeStartedAt: null,\n\t\twakeIteration: 0,\n\t} as AgentConcurrent2Vars,\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tsend(websocket, {\n\t\t\ttype: \"connected\",\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tconst promise = handleAgentConcurrent2Message(c, websocket, event.data);\n\t\t\tvoid c.keepAwake(promise);\n\t\t});\n\t},\n\tactions: {\n\t\trun: async (c, clientId?: string) => {\n\t\t\tconst runtime = ensureAgentConcurrent2Runtime(c);\n\t\t\tc.state.runCount++;\n\t\t\truntime.vars.wakeIteration++;\n\t\t\tconst cycleStats = createAgentConcurrent2QueryStats();\n\t\t\tconst stats = createAgentConcurrent2StatsSet(\n\t\t\t\tcycleStats,\n\t\t\t\truntime.wakeStats,\n\t\t\t\tc.state.queryStats,\n\t\t\t);\n\t\t\tconst result = await runAgentConcurrent2Workload(\n\t\t\t\truntime.sql,\n\t\t\t\tclientId ?? `agent2-action-${c.state.runCount}`,\n\t\t\t\t0,\n\t\t\t\tstats,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\t...result,\n\t\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t\t};\n\t\t},\n\t\tgetRunCount: (c) => c.state.runCount,\n\t\tsleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn true;\n\t\t},\n\t},\n});\n\nasync function handleAgentConcurrent2Message(\n\tc: {\n\t\tdb: RawRivetDB;\n\t\tvars: AgentConcurrent2Vars;\n\t\tstate: AgentConcurrent2State;\n\t\tsleep: () => void;\n\t},\n\twebsocket: UniversalWebSocket,\n\tdata: unknown,\n): Promise {\n\tlet trigger: AgentConcurrent2Request[\"type\"] | \"unknown\" = \"unknown\";\n\tlet cycleStats: AgentConcurrent2QueryStats | null = null;\n\ttry {\n\t\tconst request = parseAgentConcurrent2Request(data);\n\t\ttrigger = request.type;\n\n\t\tif (request.type === \"ping\") {\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"pong\",\n\t\t\t\tid: request.id,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (request.type === \"force_sleep\") {\n\t\t\tsend(websocket, { type: \"sleeping\", timestamp: Date.now() });\n\t\t\tc.sleep();\n\t\t\treturn;\n\t\t}\n\n\t\tconst runtime = ensureAgentConcurrent2Runtime(c);\n\t\tc.state.runCount++;\n\t\truntime.vars.wakeIteration++;\n\t\tcycleStats = createAgentConcurrent2QueryStats();\n\t\tconst stats = createAgentConcurrent2StatsSet(\n\t\t\tcycleStats,\n\t\t\truntime.wakeStats,\n\t\t\tc.state.queryStats,\n\t\t);\n\n\t\tif (request.type === \"agent2_resume\") {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst result = await runCatchupSnapshot(\n\t\t\t\truntime.sql,\n\t\t\t\trequest.version,\n\t\t\t\tstats,\n\t\t\t);\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"agent2_result\",\n\t\t\t\ttrigger: request.type,\n\t\t\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\t\t\tresults: [result],\n\t\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await runAgentConcurrent2Workload(\n\t\t\truntime.sql,\n\t\t\trequest.clientId,\n\t\t\trequest.staggerHandleMs ?? 0,\n\t\t\tstats,\n\t\t);\n\t\tsend(websocket, {\n\t\t\ttype: \"agent2_result\",\n\t\t\ttrigger: request.type,\n\t\t\t...result,\n\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t});\n\t} catch (error) {\n\t\tsend(websocket, {\n\t\t\ttype: \"agent2_error\",\n\t\t\ttrigger,\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t...(cycleStats ? { stats: snapshotAgentConcurrent2Stats(c, cycleStats) } : {}),\n\t\t});\n\t}\n}\n\nfunction parseAgentConcurrent2Request(data: unknown): AgentConcurrent2Request {\n\tif (typeof data !== \"string\") {\n\t\tthrow new Error(\"agent concurrent 2 request must be a string\");\n\t}\n\tconst parsed = JSON.parse(data) as unknown;\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\tthrow new Error(\"agent concurrent 2 request must be an object\");\n\t}\n\tconst request = parsed as Record;\n\n\tif (request.type === \"ping\") {\n\t\treturn {\n\t\t\ttype: \"ping\",\n\t\t\t...(typeof request.id === \"number\" ? { id: request.id } : {}),\n\t\t};\n\t}\n\tif (request.type === \"force_sleep\") {\n\t\treturn { type: \"force_sleep\" };\n\t}\n\tif (request.type === \"agent2_resume\") {\n\t\treturn { type: \"agent2_resume\", version: numberField(request, \"version\") };\n\t}\n\tif (request.type === \"agent2_connect\") {\n\t\treturn {\n\t\t\ttype: \"agent2_connect\",\n\t\t\tclientId: stringField(request, \"clientId\"),\n\t\t\t...(typeof request.staggerHandleMs === \"number\"\n\t\t\t\t? { staggerHandleMs: request.staggerHandleMs }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tthrow new Error(`unknown agent concurrent 2 request type: ${String(request.type)}`);\n}\n\nfunction stringField(record: Record, field: string): string {\n\tconst value = record[field];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`agent concurrent 2 request ${field} must be a string`);\n\t}\n\treturn value;\n}\n\nfunction numberField(record: Record, field: string): number {\n\tconst value = record[field];\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(`agent concurrent 2 request ${field} must be a finite number`);\n\t}\n\treturn value;\n}\n\nfunction createAgentConcurrent2Db(db: RawRivetDB): AgentConcurrent2Db {\n\treturn createSerializedDb(async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tconst converted = values.map((value) =>\n\t\t\ttypeof value === \"boolean\" ? (value ? 1 : 0) : value,\n\t\t);\n\t\treturn (await db.execute(query, ...converted)) as T[];\n\t});\n}\n\nfunction ensureAgentConcurrent2Runtime(c: {\n\tdb: RawRivetDB;\n\tvars: AgentConcurrent2Vars;\n\tstate: AgentConcurrent2State;\n}): AgentConcurrent2Runtime {\n\tc.vars.sql ??= createAgentConcurrent2Db(c.db);\n\tc.state.queryStats ??= createAgentConcurrent2QueryStats();\n\tc.state.wakeCount ??= 0;\n\tif (!c.vars.wakeStats) {\n\t\tc.vars.wakeStats = createAgentConcurrent2QueryStats();\n\t\tc.vars.wakeStartedAt = Date.now();\n\t\tc.vars.wakeIteration = 0;\n\t\tc.state.wakeCount++;\n\t}\n\treturn {\n\t\tsql: c.vars.sql,\n\t\twakeStats: c.vars.wakeStats,\n\t\tvars: c.vars,\n\t};\n}\n\nfunction createAgentConcurrent2QueryStats(): AgentConcurrent2QueryStats {\n\treturn {\n\t\ttotal: 0,\n\t\treads: 0,\n\t\tmutations: 0,\n\t\ttx: 0,\n\t\tother: 0,\n\t\trows: 0,\n\t\terrors: 0,\n\t\tslow: 0,\n\t\tmaxMs: 0,\n\t\tmaxStep: \"\",\n\t\tbyOperation: {},\n\t\tbyTable: {},\n\t};\n}\n\nfunction createAgentConcurrent2StatsSet(\n\tcycle: AgentConcurrent2QueryStats,\n\twake: AgentConcurrent2QueryStats,\n\tactor: AgentConcurrent2QueryStats,\n): AgentConcurrent2QueryStatsSet {\n\treturn { cycle, wake, actor };\n}\n\nfunction snapshotAgentConcurrent2Stats(\n\tc: { vars: AgentConcurrent2Vars; state: AgentConcurrent2State },\n\tcycle: AgentConcurrent2QueryStats,\n): AgentConcurrent2StatsSnapshot {\n\treturn {\n\t\twakeIndex: c.state.wakeCount,\n\t\tactorIteration: c.state.runCount,\n\t\twakeIteration: c.vars.wakeIteration,\n\t\tcycle: cloneAgentConcurrent2QueryStats(cycle),\n\t\twake: cloneAgentConcurrent2QueryStats(\n\t\t\tc.vars.wakeStats ?? createAgentConcurrent2QueryStats(),\n\t\t),\n\t\tactor: cloneAgentConcurrent2QueryStats(c.state.queryStats),\n\t};\n}\n\nfunction cloneAgentConcurrent2QueryStats(\n\tstats: AgentConcurrent2QueryStats,\n): AgentConcurrent2QueryStats {\n\treturn {\n\t\ttotal: stats.total,\n\t\treads: stats.reads,\n\t\tmutations: stats.mutations,\n\t\ttx: stats.tx,\n\t\tother: stats.other,\n\t\trows: stats.rows,\n\t\terrors: stats.errors,\n\t\tslow: stats.slow,\n\t\tmaxMs: stats.maxMs,\n\t\tmaxStep: stats.maxStep,\n\t\tbyOperation: { ...stats.byOperation },\n\t\tbyTable: { ...stats.byTable },\n\t};\n}\n\nasync function runAgentConcurrent2Workload(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstaggerHandleMs: number,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise> {\n\tconst startedAt = performance.now();\n\tconst buildToolPlanContext = runBuildToolPlanContext(sql, stats);\n\tconst catchupSnapshot = runCatchupSnapshot(sql, 0, stats);\n\tconst recoverToolCalls = runRecoverToolCalls(sql, stats);\n\tconst mutationMix = runMutationMix(sql, clientId, stats);\n\tconst handleExecutorConnect = delay(staggerHandleMs).then(() =>\n\t\trunHandleClientConnect(sql, clientId, stats),\n\t);\n\n\tconst results = await Promise.all([\n\t\thandleExecutorConnect,\n\t\tbuildToolPlanContext,\n\t\tcatchupSnapshot,\n\t\trecoverToolCalls,\n\t\tmutationMix,\n\t]);\n\treturn {\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tresults,\n\t};\n}\n\nasync function runHandleClientConnect(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst nextSeq = await sql.withTransaction(stats, async (tx) => {\n\t\tconst latestExecutor = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"load-latest-executor-id\",\n\t\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t\t);\n\t\tconst latestExecutorId = String(\n\t\t\tlatestExecutor[0]?.executor_id ?? \"seed-executor\",\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-cached-executor-tools\",\n\t\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\t\tlatestExecutorId,\n\t\t);\n\t\tconst executorType = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-executor-type\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_type'`,\n\t\t);\n\t\tif (!executorType[0]?.value) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tstats,\n\t\t\t\tsteps,\n\t\t\t\t\"set-executor-type\",\n\t\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`,\n\t\t\t\t\"local-client\",\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst sandboxIntent = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-workspace-intent\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'workspace_intent'`,\n\t\t);\n\t\tif (hasPendingLaunch(sandboxIntent[0]?.value)) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tstats,\n\t\t\t\tsteps,\n\t\t\t\t\"clear-pending-launch\",\n\t\t\t\t`UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'workspace_intent'`,\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-next-thread-event-seq\",\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t);\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-client-connected-event\",\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t\"client_connected\",\n\t\t\tJSON.stringify({ type: \"client_connected\", clientId }),\n\t\t\tnew Date().toISOString(),\n\t\t);\n\t\treturn seq;\n\t});\n\tsteps.push({\n\t\tname: \"transaction-total\",\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: nextSeq,\n\t});\n\treturn {\n\t\tname: \"handle-client-connect\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runBuildToolPlanContext(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst latestExecutor = await timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"load-latest-executor-id\",\n\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t);\n\tconst latestExecutorId = String(latestExecutor[0]?.executor_id ?? \"seed-executor\");\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"select-executor-tools\",\n\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\tlatestExecutorId,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"count-uncancelled-top-level\",\n\t\t`SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL`,\n\t);\n\tconst unresolvedRows = await timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"find-unresolved-assistant-message\",\n\t\t`SELECT m.*\n\t\t\tFROM message_tool_refs AS tool_use\n\t\t\tJOIN messages AS m\n\t\t\t\tON m.message_id = tool_use.assistant_message_id\n\t\t\tWHERE tool_use.block_type = 'tool_use'\n\t\t\t\tAND tool_use.cancelled = 0\n\t\t\t\tAND m.cancelled = 0\n\t\t\t\tAND m.role = 'assistant'\n\t\t\t\tAND m.parent_tool_use_id IS NULL\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\t\tWHERE tool_result.assistant_message_id = tool_use.assistant_message_id\n\t\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\t\tAND tool_result.tool_use_id = tool_use.tool_use_id\n\t\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL\n\t\t\t\t)\n\t\t\tGROUP BY m.message_id\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tconst unresolvedMessageId = unresolvedRows[0]?.message_id;\n\tif (typeof unresolvedMessageId === \"string\") {\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"get-persisted-tool-result-ids\",\n\t\t\t`SELECT tool_result.tool_use_id\n\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\tWHERE tool_result.assistant_message_id = ?\n\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"get-tool-calls-by-message-id\",\n\t\t\t`SELECT * FROM tool_calls WHERE message_id = ?`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t}\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"is-last-message-cancelled-assistant\",\n\t\t`SELECT role, cancelled FROM messages\n\t\t\tWHERE parent_tool_use_id IS NULL\n\t\t\tORDER BY created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-last-uncancelled\",\n\t\t`SELECT m.* FROM messages m\n\t\t\tWHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\treturn {\n\t\tname: \"build-tool-plan-context\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runCatchupSnapshot(\n\tsql: AgentConcurrent2Db,\n\tversion: number,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tawait Promise.all([\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"thread-events-list-since-version\",\n\t\t\t`SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`,\n\t\t\tversion,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"environment-snapshot\",\n\t\t\t`SELECT snapshot FROM environment_snapshot WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"thread-settings-snapshot\",\n\t\t\t`SELECT settings FROM thread_settings_snapshot WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"retry-state\",\n\t\t\t`SELECT * FROM retry_state WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"queued-messages\",\n\t\t\t`SELECT * FROM queued_messages ORDER BY created_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"executor-artifacts\",\n\t\t\t`SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"tool-approvals\",\n\t\t\t`SELECT * FROM tool_approvals ORDER BY timestamp ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"compaction-summaries\",\n\t\t\t`SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"executor-status\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_status'`,\n\t\t),\n\t]);\n\tsteps.sort((a, b) => b.durationMs - a.durationMs);\n\treturn {\n\t\tname: \"catchup-snapshot\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runRecoverToolCalls(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"hydrate-tool-progress\",\n\t\t`SELECT id, progress\n\t\t\tFROM tool_calls\n\t\t\tWHERE progress IS NOT NULL\n\t\t\t\tAND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-pending-tool-calls\",\n\t\t`SELECT * FROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')\n\t\t\tORDER BY issued_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-next-tool-expiry\",\n\t\t`SELECT MIN(expires_at) AS expires_at\n\t\t\tFROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\treturn {\n\t\tname: \"recover-tool-calls\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runMutationMix(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst writeCount = await sql.withTransaction(stats, async (tx) => {\n\t\tconst now = new Date().toISOString();\n\t\tconst suffix = safeId(clientId);\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-max-thread-event-seq\",\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t);\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1);\n\t\tconst lastMessageRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-last-message-created-at\",\n\t\t\t`SELECT MAX(created_at) AS created_at FROM messages`,\n\t\t);\n\t\tconst latestToolRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-existing-tool-call\",\n\t\t\t`SELECT id FROM tool_calls ORDER BY issued_at DESC LIMIT 1`,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-sandbox-row\",\n\t\t\t`SELECT sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup\n\t\t\t\tFROM e2b_sandbox\n\t\t\t\tWHERE id = 1`,\n\t\t);\n\n\t\tconst messageIdValue = `agent2-message-${suffix}-${seq}`;\n\t\tconst toolUseIdValue = `agent2-tool-${suffix}-${seq}`;\n\t\tconst toolCallIdValue = `agent2-call-${suffix}-${seq}`;\n\t\tconst latestToolCallId = String(latestToolRows[0]?.id ?? toolUseID(1));\n\t\tconst lastCreatedAt = String(lastMessageRows[0]?.created_at ?? now);\n\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"upsert-agent-state\",\n\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES (?, ?, ?)`,\n\t\t\t\"last_agent_state\",\n\t\t\tJSON.stringify({ status: \"working\", clientId, lastCreatedAt }),\n\t\t\tnow,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-work-event\",\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t\"message_added\",\n\t\t\tJSON.stringify({ type: \"message_added\", messageId: messageIdValue }),\n\t\t\tnow,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message\",\n\t\t\t`INSERT INTO messages (role, content, meta, user_state, message_id, created_at, cancelled, parent_tool_use_id, tool_result_for_message_id)\n\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\"assistant\",\n\t\t\t\"agent concurrent 2 mutation payload\",\n\t\t\tJSON.stringify({ clientId, seq }),\n\t\t\tnull,\n\t\t\tmessageIdValue,\n\t\t\tnow,\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"delete-message-tool-refs\",\n\t\t\t`DELETE FROM message_tool_refs WHERE source_message_id = ?`,\n\t\t\tmessageIdValue,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message-added-event\",\n\t\t\t`INSERT OR IGNORE INTO message_added_events (message_id, seq) VALUES (?, ?)`,\n\t\t\tmessageIdValue,\n\t\t\tseq,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message-tool-ref\",\n\t\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)\n\t\t\t\tVALUES (?, ?, ?, ?, ?)`,\n\t\t\tmessageIdValue,\n\t\t\tmessageIdValue,\n\t\t\ttoolUseIdValue,\n\t\t\t\"tool_use\",\n\t\t\t0,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-tool-call\",\n\t\t\t`INSERT OR IGNORE INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)\n\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\ttoolCallIdValue,\n\t\t\t`provider-${toolCallIdValue}`,\n\t\t\t\"tool_1\",\n\t\t\tJSON.stringify({ path: `/tmp/${toolCallIdValue}` }),\n\t\t\t\"seed-executor\",\n\t\t\tmessageIdValue,\n\t\t\tnow,\n\t\t\tnull,\n\t\t\t\"running\",\n\t\t\tnull,\n\t\t\tJSON.stringify({ pct: 0.5, clientId }),\n\t\t\tnull,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"update-tool-call-progress\",\n\t\t\t`UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t\t\tJSON.stringify({ pct: 0.75, clientId, updatedAt: now }),\n\t\t\ttoolCallIdValue,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"update-existing-tool-call-progress\",\n\t\t\t`UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t\t\tJSON.stringify({ pct: 0.25, clientId, updatedAt: now }),\n\t\t\tlatestToolCallId,\n\t\t);\n\n\t\treturn seq;\n\t});\n\tsteps.push({\n\t\tname: \"transaction-total\",\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: writeCount,\n\t});\n\treturn {\n\t\tname: \"mutation-mix\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function timedQuery>(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n\tsteps: AgentConcurrent2Step[],\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now();\n\ttry {\n\t\tconst rows = await sql(query, ...values);\n\t\tconst durationMs = Math.round(performance.now() - startedAt);\n\t\trecordAgentConcurrent2Query(stats, name, query, durationMs, rows.length, false);\n\t\tsteps.push({\n\t\t\tname,\n\t\t\tdurationMs,\n\t\t\trowCount: rows.length,\n\t\t});\n\t\treturn rows;\n\t} catch (error) {\n\t\tconst durationMs = Math.round(performance.now() - startedAt);\n\t\trecordAgentConcurrent2Query(stats, name, query, durationMs, 0, true);\n\t\tthrow error;\n\t}\n}\n\nasync function executeTrackedQuery>(\n\texecute: >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t) => Promise,\n\tstats: AgentConcurrent2QueryStatsSet,\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now();\n\ttry {\n\t\tconst rows = await execute(query, ...values);\n\t\trecordAgentConcurrent2Query(\n\t\t\tstats,\n\t\t\tname,\n\t\t\tquery,\n\t\t\tMath.round(performance.now() - startedAt),\n\t\t\trows.length,\n\t\t\tfalse,\n\t\t);\n\t\treturn rows;\n\t} catch (error) {\n\t\trecordAgentConcurrent2Query(\n\t\t\tstats,\n\t\t\tname,\n\t\t\tquery,\n\t\t\tMath.round(performance.now() - startedAt),\n\t\t\t0,\n\t\t\ttrue,\n\t\t);\n\t\tthrow error;\n\t}\n}\n\nfunction recordAgentConcurrent2Query(\n\tstats: AgentConcurrent2QueryStatsSet,\n\tname: string,\n\tquery: string,\n\tdurationMs: number,\n\trowCount: number,\n\tfailed: boolean,\n): void {\n\tconst classification = classifyAgentConcurrent2Query(query);\n\tfor (const target of [stats.cycle, stats.wake, stats.actor]) {\n\t\ttarget.total++;\n\t\ttarget.rows += rowCount;\n\t\tif (failed) target.errors++;\n\t\tif (durationMs >= SLOW_QUERY_MS) target.slow++;\n\t\tif (durationMs > target.maxMs) {\n\t\t\ttarget.maxMs = durationMs;\n\t\t\ttarget.maxStep = `${name}:${classification.table}`;\n\t\t}\n\t\ttarget.byOperation[classification.operation] =\n\t\t\t(target.byOperation[classification.operation] ?? 0) + 1;\n\t\ttarget.byTable[classification.table] =\n\t\t\t(target.byTable[classification.table] ?? 0) + 1;\n\t\tif (classification.kind === \"read\") {\n\t\t\ttarget.reads++;\n\t\t} else if (classification.kind === \"mutation\") {\n\t\t\ttarget.mutations++;\n\t\t} else if (classification.kind === \"tx\") {\n\t\t\ttarget.tx++;\n\t\t} else {\n\t\t\ttarget.other++;\n\t\t}\n\t}\n}\n\nfunction classifyAgentConcurrent2Query(query: string): {\n\toperation: string;\n\tkind: \"read\" | \"mutation\" | \"tx\" | \"other\";\n\ttable: string;\n} {\n\tconst normalized = query.trim().replace(/\\s+/g, \" \");\n\tconst operation = normalized.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? \"other\";\n\tconst table = extractAgentConcurrent2Table(normalized, operation);\n\tif (operation === \"select\") {\n\t\treturn { operation, kind: \"read\", table };\n\t}\n\tif (\n\t\toperation === \"insert\" ||\n\t\toperation === \"update\" ||\n\t\toperation === \"delete\" ||\n\t\toperation === \"replace\"\n\t) {\n\t\treturn { operation, kind: \"mutation\", table };\n\t}\n\tif (operation === \"begin\" || operation === \"commit\" || operation === \"rollback\") {\n\t\treturn { operation, kind: \"tx\", table };\n\t}\n\treturn { operation, kind: \"other\", table };\n}\n\nfunction extractAgentConcurrent2Table(query: string, operation: string): string {\n\tconst lower = query.toLowerCase();\n\tif (operation === \"select\") {\n\t\treturn firstMatch(lower, /\\bfrom\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"insert\" || operation === \"replace\") {\n\t\treturn firstMatch(lower, /\\binto\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"update\") {\n\t\treturn firstMatch(lower, /\\bupdate\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"delete\") {\n\t\treturn firstMatch(lower, /\\bfrom\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"begin\" || operation === \"commit\" || operation === \"rollback\") {\n\t\treturn \"transaction\";\n\t}\n\treturn \"unknown\";\n}\n\nfunction firstMatch(value: string, pattern: RegExp): string | null {\n\treturn pattern.exec(value)?.[1] ?? null;\n}\n\nfunction hasPendingLaunch(value: unknown): boolean {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn false;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(value) as { pendingLaunch?: unknown };\n\t\treturn parsed.pendingLaunch !== null && parsed.pendingLaunch !== undefined;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));\n}\n\nasync function createAgentConcurrent2Schema(database: RawRivetDB): Promise {\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS executor_tools (\n\t\texecutor_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\tschema TEXT NOT NULL,\n\t\tupdated_at TEXT NOT NULL,\n\t\tPRIMARY KEY (executor_id, tool_name)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv (\n\t\tkey TEXT PRIMARY KEY,\n\t\tvalue TEXT,\n\t\tupdated_at TEXT NOT NULL\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_events (\n\t\tseq INTEGER PRIMARY KEY,\n\t\tevent_type TEXT NOT NULL,\n\t\tpayload TEXT NOT NULL,\n\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_added_events (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\tseq INTEGER NOT NULL UNIQUE\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS messages (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\trole TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')),\n\t\tcontent TEXT NOT NULL,\n\t\tmeta TEXT,\n\t\tuser_state TEXT,\n\t\tcreated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tread_at TEXT,\n\t\tparent_tool_use_id TEXT,\n\t\ttool_result_for_message_id TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs (\n\t\tsource_message_id TEXT NOT NULL,\n\t\tassistant_message_id TEXT NOT NULL,\n\t\ttool_use_id TEXT NOT NULL,\n\t\tblock_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tPRIMARY KEY (source_message_id, block_type, tool_use_id)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS tool_calls (\n\t\tid TEXT PRIMARY KEY,\n\t\tprovider_tool_use_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\targs TEXT NOT NULL,\n\t\texecutor_id TEXT,\n\t\tmessage_id TEXT NOT NULL,\n\t\tissued_at TEXT NOT NULL,\n\t\texpires_at TEXT,\n\t\tstate TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')),\n\t\tresult TEXT,\n\t\tprogress TEXT,\n\t\tcompleted_at TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS e2b_sandbox (\n\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\tsandbox_id TEXT,\n\t\t\trestart_attempts INTEGER NOT NULL DEFAULT 0,\n\t\t\ttraffic_access_token TEXT,\n\t\t\tproject_id TEXT,\n\t\t\trepository_url TEXT,\n\t\t\tadditional_repositories TEXT,\n\t\t\tsetup TEXT,\n\t\t\tcreated_at TEXT NOT NULL,\n\t\t\tupdated_at TEXT NOT NULL\n\t\t)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)`,\n\t);\n}\n\nasync function seedAgentConcurrent2Data(database: RawRivetDB): Promise {\n\tconst existing = await database.execute(`SELECT COUNT(*) AS count FROM messages`);\n\tif (Number(existing[0]?.count ?? 0) > 0) {\n\t\treturn;\n\t}\n\n\tconst now = new Date(\"2026-05-16T03:58:18.661Z\").getTime();\n\tconst text = (size: number) => \"x\".repeat(size);\n\tconst isoAt = (index: number) => new Date(now + index * 1_000).toISOString();\n\n\tawait batchInsert(database, `INSERT INTO thread_meta_kv (key, value, updated_at)`, [\n\t\t[\"executor_type\", \"local-client\", isoAt(0)],\n\t\t[\"workspace_intent\", JSON.stringify({ spec: null, pendingLaunch: null }), isoAt(0)],\n\t\t[\"executor_status\", JSON.stringify({ available: true, message: \"ready\" }), isoAt(0)],\n\t]);\n\n\tconst messageRows: unknown[][] = [];\n\tfor (let index = 1; index <= MESSAGE_COUNT; index++) {\n\t\tconst role = index % 2 === 0 ? \"assistant\" : \"user\";\n\t\tmessageRows.push([\n\t\t\tmessageId(index),\n\t\t\trole,\n\t\t\ttext(MESSAGE_CONTENT_BYTES),\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tisoAt(index),\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tnull,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`,\n\t\tmessageRows,\n\t\t20,\n\t);\n\n\tconst messageToolRefRows: unknown[][] = [];\n\tfor (let index = 0; index < MESSAGE_TOOL_REF_COUNT / 2; index++) {\n\t\tconst assistantIndex = 2 + (index % 42) * 2;\n\t\tconst sourceIndex = Math.max(1, assistantIndex - 1);\n\t\tconst resultIndex = Math.min(MESSAGE_COUNT, assistantIndex + 1);\n\t\tconst toolUseId = toolUseID(index + 1);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(sourceIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_use\",\n\t\t\t0,\n\t\t]);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(resultIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_result\",\n\t\t\t0,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`,\n\t\tmessageToolRefRows,\n\t\t50,\n\t);\n\n\tconst toolCallRows: unknown[][] = [];\n\tfor (let index = 1; index <= TOOL_CALL_COUNT; index++) {\n\t\tconst assistantIndex = 2 + ((index - 1) % 42) * 2;\n\t\ttoolCallRows.push([\n\t\t\ttoolUseID(index),\n\t\t\t`provider-${index}`,\n\t\t\t`tool_${index % 21}`,\n\t\t\tJSON.stringify({ path: `/tmp/file-${index}` }),\n\t\t\t\"seed-executor\",\n\t\t\tmessageId(assistantIndex),\n\t\t\tisoAt(index),\n\t\t\tnull,\n\t\t\t\"completed\",\n\t\t\tJSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\trun: { status: \"done\", result: text(TOOL_CALL_RESULT_BYTES) },\n\t\t\t}),\n\t\t\tnull,\n\t\t\tisoAt(index + 100),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`,\n\t\ttoolCallRows,\n\t\t20,\n\t);\n\n\tconst executorToolRows: unknown[][] = [];\n\tfor (let index = 1; index <= EXECUTOR_TOOL_COUNT; index++) {\n\t\tconst schema = JSON.stringify({\n\t\t\tname: `tool_${index}`,\n\t\t\tdescription: text(EXECUTOR_TOOL_SCHEMA_BYTES),\n\t\t\tinput_schema: { type: \"object\", properties: {} },\n\t\t});\n\t\texecutorToolRows.push([\"seed-executor\", `tool_${index}`, schema, isoAt(index)]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`,\n\t\texecutorToolRows,\n\t\t42,\n\t);\n\n\tconst threadEventRows: unknown[][] = [];\n\tfor (let index = 1; index <= THREAD_EVENT_COUNT; index++) {\n\t\tthreadEventRows.push([\n\t\t\tindex,\n\t\t\tindex % 3 === 0 ? \"message_added\" : \"agent_state_changed\",\n\t\t\tJSON.stringify({ type: \"seed_event\", body: text(THREAD_EVENT_PAYLOAD_BYTES) }),\n\t\t\tisoAt(index),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at)`,\n\t\tthreadEventRows,\n\t\t25,\n\t);\n\n\tconst messageAddedRows: unknown[][] = [];\n\tfor (let index = 1; index <= MESSAGE_COUNT; index++) {\n\t\tmessageAddedRows.push([messageId(index), index]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_added_events (message_id, seq)`,\n\t\tmessageAddedRows,\n\t\t50,\n\t);\n\n\tawait database.execute(\n\t\t`INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ cwd: \"/workspace\", body: text(3_620) }),\n\t\tisoAt(0),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ maxTokens: 20_000, body: text(55) }),\n\t\tisoAt(0),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO e2b_sandbox (id, sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup, created_at, updated_at)\n\t\t\tVALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\"sandbox-seed\",\n\t\t0,\n\t\t\"token-seed\",\n\t\t\"project-seed\",\n\t\t\"https://example.invalid/repo.git\",\n\t\tJSON.stringify([]),\n\t\tJSON.stringify({ commands: [] }),\n\t\tisoAt(0),\n\t\tisoAt(0),\n\t);\n}\n\nasync function batchInsert(\n\tdatabase: RawRivetDB,\n\tinsertPrefix: string,\n\trows: unknown[][],\n\tbatchSize = 100,\n): Promise {\n\tif (rows.length === 0) {\n\t\treturn;\n\t}\n\tconst columnCount = rows[0]?.length ?? 0;\n\tif (columnCount === 0) {\n\t\treturn;\n\t}\n\tconst rowPlaceholder = `(${\"?,\".repeat(columnCount).slice(0, -1)})`;\n\tfor (let index = 0; index < rows.length; index += batchSize) {\n\t\tconst chunk = rows.slice(index, index + batchSize);\n\t\tconst values = chunk.map(() => rowPlaceholder).join(\",\");\n\t\tconst bindings = chunk.flat();\n\t\tawait database.execute(`${insertPrefix} VALUES ${values}`, ...bindings);\n\t}\n}\n\nfunction messageId(index: number): string {\n\treturn `M-${String(index).padStart(22, \"0\")}`;\n}\n\nfunction toolUseID(index: number): string {\n\treturn `toolu_${String(index).padStart(22, \"0\")}`;\n}\n\nfunction safeId(value: string): string {\n\treturn value.replace(/[^a-zA-Z0-9_-]/g, \"-\").slice(0, 80);\n}\n","import { actor, type RivetMessageEvent, type UniversalWebSocket } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const DEFAULT_ON_SLEEP_DURATION_MS = 5_000;\nexport const DEFAULT_ON_SLEEP_TICK_MS = 1_000;\nconst SLEEP_TIMEOUT_MS = 10 * 60 * 1000;\nconst SLEEP_GRACE_PERIOD_MS = 30 * 60 * 1000;\nconst ACTOR_STOPPED_CLOSE_CODE = 1000;\nconst ACTOR_STOPPED_CLOSE_REASON = \"actor stopped\";\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction formatError(error: unknown): string {\n\tif (error instanceof Error) return error.stack ?? error.message;\n\treturn String(error);\n}\n\nexport const sigtermSleepProbe = actor({\n\tstate: {\n\t\tlabel: \"unprepared\",\n\t\twakeCount: 0,\n\t\tsleepCount: 0,\n\t\tonSleepDurationMs: DEFAULT_ON_SLEEP_DURATION_MS,\n\t\tonSleepTickMs: DEFAULT_ON_SLEEP_TICK_MS,\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t\tonSleepStartedAt: null as number | null,\n\t\tonSleepAsyncFinishedAt: null as number | null,\n\t\tonSleepFinishedAt: null as number | null,\n\t\tonSleepLastError: null as string | null,\n\t},\n\tcreateVars: () => ({\n\t\twebsockets: new Set(),\n\t}),\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS sigterm_sleep_log (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tevent TEXT NOT NULL,\n\t\t\t\t\tsleep_count INTEGER NOT NULL,\n\t\t\t\t\tdetail TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tc.vars.websockets.add(websocket);\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe websocket connected\",\n\t\t\tlabel: c.state.label,\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionId,\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.messageCount += 1;\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data !== \"string\") return;\n\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\tif (parsed.type === \"ping\") {\n\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {}\n\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"echo\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\treceived: data,\n\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", (event) => {\n\t\t\tc.vars.websockets.delete(websocket);\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"sigterm sleep probe websocket closed\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tcode: event.code,\n\t\t\t\treason: event.reason,\n\t\t\t});\n\t\t});\n\t},\n\tonWake: async (c) => {\n\t\tc.state.wakeCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe onWake\",\n\t\t\tlabel: c.state.label,\n\t\t\twakeCount: c.state.wakeCount,\n\t\t\tsleepCount: c.state.sleepCount,\n\t\t});\n\t\tawait c.db.execute(\n\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\"wake\",\n\t\t\tc.state.sleepCount,\n\t\t\t`wake-${c.state.wakeCount}`,\n\t\t\tDate.now(),\n\t\t);\n\t},\n\tonSleep: async (c) => {\n\t\tconst sleepCount = c.state.sleepCount + 1;\n\t\tconst startedAt = Date.now();\n\t\tc.state.sleepCount = sleepCount;\n\t\tc.state.onSleepStartedAt = startedAt;\n\t\tc.state.onSleepAsyncFinishedAt = null;\n\t\tc.state.onSleepFinishedAt = null;\n\t\tc.state.onSleepLastError = null;\n\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe onSleep start\",\n\t\t\tlabel: c.state.label,\n\t\t\tsleepCount,\n\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t});\n\n\t\ttry {\n\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"onSleepStarted\",\n\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\t\t\ttimestamp: startedAt,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-start\",\n\t\t\t\tsleepCount,\n\t\t\t\tc.state.label,\n\t\t\t\tstartedAt,\n\t\t\t);\n\n\t\t\tconst deadline = startedAt + c.state.onSleepDurationMs;\n\t\t\tlet tickIndex = 0;\n\t\t\twhile (Date.now() < deadline) {\n\t\t\t\tconst waitMs = Math.min(\n\t\t\t\t\tc.state.onSleepTickMs,\n\t\t\t\t\tMath.max(0, deadline - Date.now()),\n\t\t\t\t);\n\t\t\t\tif (waitMs > 0) await sleep(waitMs);\n\n\t\t\t\ttickIndex += 1;\n\t\t\t\tconst tickAt = Date.now();\n\t\t\t\tconst detail = `tick=${tickIndex} elapsed-ms=${tickAt - startedAt}`;\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t\"on-sleep-tick\",\n\t\t\t\t\tsleepCount,\n\t\t\t\t\tdetail,\n\t\t\t\t\ttickAt,\n\t\t\t\t);\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"sigterm sleep probe onSleep tick\",\n\t\t\t\t\tlabel: c.state.label,\n\t\t\t\t\tsleepCount,\n\t\t\t\t\ttickIndex,\n\t\t\t\t\telapsedMs: tickAt - startedAt,\n\t\t\t\t});\n\n\t\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: \"onSleepTick\",\n\t\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\t\ttickIndex,\n\t\t\t\t\t\t\telapsedMs: tickAt - startedAt,\n\t\t\t\t\t\t\ttimestamp: tickAt,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst asyncFinishedAt = Date.now();\n\t\t\tc.state.onSleepAsyncFinishedAt = asyncFinishedAt;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-after-await\",\n\t\t\t\tsleepCount,\n\t\t\t\t`delay-ms=${asyncFinishedAt - startedAt}`,\n\t\t\t\tasyncFinishedAt,\n\t\t\t);\n\n\t\t\tconst finishedAt = Date.now();\n\t\t\tc.state.onSleepFinishedAt = finishedAt;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-finish\",\n\t\t\t\tsleepCount,\n\t\t\t\tc.state.label,\n\t\t\t\tfinishedAt,\n\t\t\t);\n\n\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"onSleepFinished\",\n\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\telapsedMs: finishedAt - startedAt,\n\t\t\t\t\t\ttimestamp: finishedAt,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\twebsocket.close(\n\t\t\t\t\tACTOR_STOPPED_CLOSE_CODE,\n\t\t\t\t\tACTOR_STOPPED_CLOSE_REASON,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"sigterm sleep probe onSleep finish\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tsleepCount,\n\t\t\t\telapsedMs: finishedAt - startedAt,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = formatError(error);\n\t\t\tc.state.onSleepLastError = message;\n\t\t\tc.log.error({\n\t\t\t\tmsg: \"sigterm sleep probe onSleep error\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tsleepCount,\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t},\n\tactions: {\n\t\tprepare: async (\n\t\t\tc,\n\t\t\tlabel = `sigterm-sleep-probe-${Date.now()}`,\n\t\t\tonSleepDurationMs = DEFAULT_ON_SLEEP_DURATION_MS,\n\t\t\tonSleepTickMs = DEFAULT_ON_SLEEP_TICK_MS,\n\t\t) => {\n\t\t\tif (!Number.isFinite(onSleepDurationMs) || onSleepDurationMs < 0) {\n\t\t\t\tthrow new Error(\"onSleepDurationMs must be a finite non-negative number\");\n\t\t\t}\n\t\t\tif (!Number.isFinite(onSleepTickMs) || onSleepTickMs <= 0) {\n\t\t\t\tthrow new Error(\"onSleepTickMs must be a finite positive number\");\n\t\t\t}\n\t\t\tc.state.label = label;\n\t\t\tc.state.onSleepDurationMs = onSleepDurationMs;\n\t\t\tc.state.onSleepTickMs = onSleepTickMs;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"prepared\",\n\t\t\t\tc.state.sleepCount,\n\t\t\t\tlabel,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t};\n\t\t},\n\t\tgetProof: async (c) => {\n\t\t\tconst rows = await c.db.execute<{\n\t\t\t\tid: number;\n\t\t\t\tevent: string;\n\t\t\t\tsleep_count: number;\n\t\t\t\tdetail: string | null;\n\t\t\t\tcreated_at: number;\n\t\t\t}>(\"SELECT * FROM sigterm_sleep_log ORDER BY id\");\n\t\t\treturn {\n\t\t\t\tstate: {\n\t\t\t\t\tlabel: c.state.label,\n\t\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\tonSleepStartedAt: c.state.onSleepStartedAt,\n\t\t\t\t\tonSleepAsyncFinishedAt: c.state.onSleepAsyncFinishedAt,\n\t\t\t\t\tonSleepFinishedAt: c.state.onSleepFinishedAt,\n\t\t\t\t\tonSleepLastError: c.state.onSleepLastError,\n\t\t\t\t},\n\t\t\t\trows,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepTimeout: SLEEP_TIMEOUT_MS,\n\t\tsleepGracePeriod: SLEEP_GRACE_PERIOD_MS,\n\t},\n});\n","import { actor, setup } from 'rivetkit'\nimport { db } from 'rivetkit/db'\n\nexport type SlowReconnectRequest =\n\t| { type: 'client_resume'; version: number }\n\t| {\n\t\ttype: 'executor_connect'\n\t\tclientId: string\n\t\texecutorType?: 'local-client' | 'sandbox' | 'virtual'\n\t}\n\t| {\n\t\ttype: 'repro_reconnect'\n\t\tclientId?: string\n\t\tstaggerHandleMs?: number\n\t}\n\nexport interface SlowReconnectStep {\n\tname: string\n\tdurationMs: number\n\trowCount: number\n}\n\nexport interface SlowReconnectWorkloadResult {\n\tname: string\n\ttotalMs: number\n\tsteps: SlowReconnectStep[]\n}\n\nexport interface SlowReconnectResultMessage {\n\ttype: 'slow_reconnect_result'\n\ttrigger: SlowReconnectRequest['type']\n\ttotalMs: number\n\tresults: SlowReconnectWorkloadResult[]\n}\n\nexport interface SlowReconnectErrorMessage {\n\ttype: 'slow_reconnect_error'\n\ttrigger: SlowReconnectRequest['type'] | 'unknown'\n\terror: string\n}\n\nexport interface SlowReconnectVars {\n\tsql: Db | null\n}\n\ninterface RawRivetDB {\n\texecute: (query: string, ...args: unknown[]) => Promise[]>\n}\n\ntype SQLPrimitive = string | number | boolean | null\n\ntype Db = (>(\n\tquery: string,\n\t...values: SQLPrimitive[]\n) => Promise) & {\n\twithTransaction(fn: (tx: Db) => Promise): Promise\n}\n\nclass AsyncMutex {\n\tprivate locked = false\n\tprivate waiters: Array<() => void> = []\n\n\tasync acquire(): Promise {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true\n\t\t\treturn\n\t\t}\n\t\tawait new Promise((resolve) => this.waiters.push(resolve))\n\t\tthis.locked = true\n\t}\n\n\trelease(): void {\n\t\tconst next = this.waiters.shift()\n\t\tif (next) {\n\t\t\tnext()\n\t\t\treturn\n\t\t}\n\t\tthis.locked = false\n\t}\n}\n\nfunction createDb(execute: >(\n\tquery: string,\n\t...values: SQLPrimitive[]\n) => Promise): Db {\n\tconst mutex = new AsyncMutex()\n\tlet activeTransaction: Db | null = null\n\n\tconst createTransactionDb = (): Db => {\n\t\tconst tx = Object.assign(\n\t\t\t>(query: string, ...values: SQLPrimitive[]) =>\n\t\t\t\texecute(query, ...values),\n\t\t\t{\n\t\t\t\twithTransaction: async (fn: (tx: Db) => Promise): Promise => fn(tx),\n\t\t\t},\n\t\t)\n\t\treturn tx\n\t}\n\n\tconst queryWithMutex = async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tif (activeTransaction) {\n\t\t\treturn activeTransaction(query, ...values)\n\t\t}\n\t\tawait mutex.acquire()\n\t\ttry {\n\t\t\treturn await execute(query, ...values)\n\t\t} finally {\n\t\t\tmutex.release()\n\t\t}\n\t}\n\n\tconst sql = Object.assign(queryWithMutex, {\n\t\twithTransaction: async (fn: (tx: Db) => Promise): Promise => {\n\t\t\tif (activeTransaction) {\n\t\t\t\treturn fn(activeTransaction)\n\t\t\t}\n\t\t\tawait mutex.acquire()\n\t\t\tconst tx = createTransactionDb()\n\t\t\ttry {\n\t\t\t\tawait execute('BEGIN')\n\t\t\t\tactiveTransaction = tx\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fn(tx)\n\t\t\t\t\tactiveTransaction = null\n\t\t\t\t\tawait execute('COMMIT')\n\t\t\t\t\treturn result\n\t\t\t\t} catch (error) {\n\t\t\t\t\tactiveTransaction = null\n\t\t\t\t\tawait execute('ROLLBACK')\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tactiveTransaction = null\n\t\t\t\tmutex.release()\n\t\t\t}\n\t\t},\n\t})\n\treturn sql\n}\n\nconst MESSAGE_COUNT = 84\nconst MESSAGE_TOOL_REF_COUNT = 122\nconst TOOL_CALL_COUNT = 61\nconst EXECUTOR_TOOL_COUNT = 42\nconst THREAD_EVENT_COUNT = 233\n\nconst MESSAGE_CONTENT_BYTES = 10_620\nconst THREAD_EVENT_PAYLOAD_BYTES = 4_036\nconst TOOL_CALL_RESULT_BYTES = 10_975\nconst EXECUTOR_TOOL_SCHEMA_BYTES = 2_235\n\nexport const slowReconnectActor = actor({\n\tstate: { runCount: 0 },\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait createSlowReconnectSchema(database)\n\t\t},\n\t}),\n\tvars: { sql: null } as SlowReconnectVars,\n\tonWebSocket: (c, ws) => {\n\t\tconst sock = ws as unknown as WebSocket\n\t\tif (sock.readyState === WebSocket.OPEN) {\n\t\t\tsock.send('pong')\n\t\t}\n\n\t\tws.addEventListener('message', (event) => {\n\t\t\tconst promise = handleSlowReconnectWebSocketMessage(c, sock, event.data)\n\t\t\tvoid c.keepAwake(promise)\n\t\t})\n\t},\n\tactions: {\n\t\tprepare: async (c) => {\n\t\t\tawait createSlowReconnectSchema(c.db)\n\t\t\treturn await seedSlowReconnectData(c.db)\n\t\t},\n\t\treproReconnect: async (c, clientId?: string) => {\n\t\t\tc.vars.sql ??= createSlowReconnectDb(c.db)\n\t\t\tc.state.runCount++\n\t\t\treturn await runReconnectRepro(c.vars.sql, clientId ?? `action-${c.state.runCount}`, 0)\n\t\t},\n\t\tgetRunCount: (c) => c.state.runCount,\n\t\tsleep: (c) => {\n\t\t\tc.sleep()\n\t\t\treturn true\n\t\t},\n\t},\n})\n\nasync function handleSlowReconnectWebSocketMessage(\n\tc: { db: RawRivetDB; vars: SlowReconnectVars; state: { runCount: number } },\n\tsock: WebSocket,\n\tdata: unknown,\n): Promise {\n\tif (data === 'ping') {\n\t\tif (sock.readyState === WebSocket.OPEN) {\n\t\t\tsock.send('pong')\n\t\t}\n\t\treturn\n\t}\n\n\tlet trigger: SlowReconnectRequest['type'] | 'unknown' = 'unknown'\n\ttry {\n\t\tconst request = parseSlowReconnectRequest(data)\n\t\ttrigger = request.type\n\t\tc.vars.sql ??= createSlowReconnectDb(c.db)\n\t\tc.state.runCount++\n\n\t\tif (request.type === 'client_resume') {\n\t\t\tconst startedAt = performance.now()\n\t\t\tconst result = await runCatchupSnapshot(c.vars.sql, request.version)\n\t\t\tsendJSON(sock, {\n\t\t\t\ttype: 'slow_reconnect_result',\n\t\t\t\ttrigger: request.type,\n\t\t\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\t\t\tresults: [result],\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tconst clientId =\n\t\t\trequest.type === 'executor_connect'\n\t\t\t\t? request.clientId\n\t\t\t\t: (request.clientId ?? `slow-reconnect-${c.state.runCount}`)\n\t\tconst staggerHandleMs = request.type === 'repro_reconnect' ? (request.staggerHandleMs ?? 0) : 0\n\t\tconst result = await runReconnectRepro(c.vars.sql, clientId, staggerHandleMs)\n\n\t\tif (request.type === 'executor_connect') {\n\t\t\tsendJSON(sock, {\n\t\t\t\ttype: 'executor_connected',\n\t\t\t\texecutorId: clientId,\n\t\t\t\tregisteredToolCount: EXECUTOR_TOOL_COUNT,\n\t\t\t\tguidanceInventory: [],\n\t\t\t\tresumeBootstrap: true,\n\t\t\t})\n\t\t}\n\n\t\tsendJSON(sock, {\n\t\t\ttype: 'slow_reconnect_result',\n\t\t\ttrigger: request.type,\n\t\t\t...result,\n\t\t})\n\t} catch (error) {\n\t\tsendJSON(sock, {\n\t\t\ttype: 'slow_reconnect_error',\n\t\t\ttrigger,\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t})\n\t}\n}\n\nfunction parseSlowReconnectRequest(data: unknown): SlowReconnectRequest {\n\tif (typeof data !== 'string') {\n\t\tthrow new Error('slowReconnectActor request must be a string')\n\t}\n\tconst parsed = JSON.parse(data) as unknown\n\tif (!parsed || typeof parsed !== 'object') {\n\t\tthrow new Error('slowReconnectActor request must be an object')\n\t}\n\tconst request = parsed as Record\n\tif (request.type === 'client_resume') {\n\t\treturn { type: 'client_resume', version: numberField(request, 'version') }\n\t}\n\tif (request.type === 'executor_connect') {\n\t\tconst executorType = request.executorType\n\t\treturn {\n\t\t\ttype: 'executor_connect',\n\t\t\tclientId: stringField(request, 'clientId'),\n\t\t\t...(executorType === 'local-client' ||\n\t\t\t\texecutorType === 'sandbox' ||\n\t\t\t\texecutorType === 'virtual'\n\t\t\t\t? { executorType }\n\t\t\t\t: {}),\n\t\t}\n\t}\n\tif (request.type === 'repro_reconnect') {\n\t\treturn {\n\t\t\ttype: 'repro_reconnect',\n\t\t\t...(typeof request.clientId === 'string' ? { clientId: request.clientId } : {}),\n\t\t\t...(typeof request.staggerHandleMs === 'number'\n\t\t\t\t? { staggerHandleMs: request.staggerHandleMs }\n\t\t\t\t: {}),\n\t\t}\n\t}\n\tthrow new Error(`Unknown slowReconnectActor request type: ${String(request.type)}`)\n}\n\nfunction stringField(record: Record, field: string): string {\n\tconst value = record[field]\n\tif (typeof value !== 'string' || value.length === 0) {\n\t\tthrow new Error(`slowReconnectActor request ${field} must be a non-empty string`)\n\t}\n\treturn value\n}\n\nfunction numberField(record: Record, field: string): number {\n\tconst value = record[field]\n\tif (typeof value !== 'number' || !Number.isFinite(value)) {\n\t\tthrow new Error(`slowReconnectActor request ${field} must be a finite number`)\n\t}\n\treturn value\n}\n\nfunction sendJSON(\n\tsock: WebSocket,\n\tmessage: SlowReconnectResultMessage | SlowReconnectErrorMessage | object,\n): void {\n\tif (sock.readyState === WebSocket.OPEN) {\n\t\tsock.send(JSON.stringify(message))\n\t}\n}\n\nfunction createSlowReconnectDb(db: RawRivetDB): Db {\n\treturn createDb(async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tconst converted = values.map((value) =>\n\t\t\ttypeof value === 'boolean' ? (value ? 1 : 0) : value,\n\t\t)\n\t\treturn (await db.execute(query, ...converted)) as T[]\n\t})\n}\n\nasync function runReconnectRepro(\n\tsql: Db,\n\tclientId: string,\n\tstaggerHandleMs: number,\n): Promise> {\n\tconst startedAt = performance.now()\n\tconst buildToolPlanContext = await runBuildToolPlanContext(sql)\n\tconst catchupSnapshot = await runCatchupSnapshot(sql, 0)\n\tconst recoverToolCalls = await runRecoverToolCalls(sql)\n\tconst handleExecutorConnect = await delay(staggerHandleMs).then(() =>\n\t\trunHandleExecutorConnect(sql, clientId),\n\t)\n\n\tconst results = [\n\t\thandleExecutorConnect,\n\t\tbuildToolPlanContext,\n\t\tcatchupSnapshot,\n\t\trecoverToolCalls,\n\t]\n\treturn {\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tresults,\n\t}\n}\n\nasync function runHandleExecutorConnect(\n\tsql: Db,\n\tclientId: string,\n): Promise {\n\tconst startedAt = performance.now()\n\tconst steps: SlowReconnectStep[] = []\n\tconst nextSeq = await sql.withTransaction(async (tx) => {\n\t\tconst latestExecutor = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'load-latest-executor-id',\n\t\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t\t)\n\t\tconst latestExecutorId = String(latestExecutor[0]?.executor_id ?? 'seed-executor')\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'select-cached-executor-tools',\n\t\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\t\tlatestExecutorId,\n\t\t)\n\t\tconst executorType = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'select-executor-type',\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_type'`,\n\t\t)\n\t\tif (!executorType[0]?.value) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tsteps,\n\t\t\t\t'set-executor-type',\n\t\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`,\n\t\t\t\t'local-client',\n\t\t\t\tnew Date().toISOString(),\n\t\t\t)\n\t\t}\n\t\tconst sandboxIntent = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'select-sandbox-intent',\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'sandbox_intent'`,\n\t\t)\n\t\tif (hasPendingLaunch(sandboxIntent[0]?.value)) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tsteps,\n\t\t\t\t'clear-pending-launch',\n\t\t\t\t`UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'sandbox_intent'`,\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tnew Date().toISOString(),\n\t\t\t)\n\t\t}\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'select-next-thread-event-seq',\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t)\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1)\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t'insert-executor-connected-event',\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t'executor_connected',\n\t\t\tJSON.stringify({ type: 'executor_connected', executorId: clientId }),\n\t\t\tnew Date().toISOString(),\n\t\t)\n\t\treturn seq\n\t})\n\tsteps.push({\n\t\tname: 'transaction-total',\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: nextSeq,\n\t})\n\treturn {\n\t\tname: 'handle-executor-connect',\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t}\n}\n\nasync function runBuildToolPlanContext(sql: Db): Promise {\n\tconst startedAt = performance.now()\n\tconst steps: SlowReconnectStep[] = []\n\tconst latestExecutor = await timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'load-latest-executor-id',\n\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t)\n\tconst latestExecutorId = String(latestExecutor[0]?.executor_id ?? 'seed-executor')\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'select-executor-tools',\n\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\tlatestExecutorId,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'count-uncancelled-top-level',\n\t\t`SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL`,\n\t)\n\tconst unresolvedRows = await timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'find-unresolved-assistant-message',\n\t\t`SELECT m.*\n\t\t\tFROM message_tool_refs AS tool_use\n\t\t\tJOIN messages AS m\n\t\t\t\tON m.message_id = tool_use.assistant_message_id\n\t\t\tWHERE tool_use.block_type = 'tool_use'\n\t\t\t\tAND tool_use.cancelled = 0\n\t\t\t\tAND m.cancelled = 0\n\t\t\t\tAND m.role = 'assistant'\n\t\t\t\tAND m.parent_tool_use_id IS NULL\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\t\tWHERE tool_result.assistant_message_id = tool_use.assistant_message_id\n\t\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\t\tAND tool_result.tool_use_id = tool_use.tool_use_id\n\t\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL\n\t\t\t\t)\n\t\t\tGROUP BY m.message_id\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t)\n\tconst unresolvedMessageId = unresolvedRows[0]?.message_id\n\tif (typeof unresolvedMessageId === 'string') {\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tsteps,\n\t\t\t'get-persisted-tool-result-ids',\n\t\t\t`SELECT tool_result.tool_use_id\n\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\tWHERE tool_result.assistant_message_id = ?\n\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL`,\n\t\t\tunresolvedMessageId,\n\t\t)\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tsteps,\n\t\t\t'get-tool-calls-by-message-id',\n\t\t\t`SELECT * FROM tool_calls WHERE message_id = ?`,\n\t\t\tunresolvedMessageId,\n\t\t)\n\t}\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'is-last-message-cancelled-assistant',\n\t\t`SELECT role, cancelled FROM messages\n\t\t\tWHERE parent_tool_use_id IS NULL\n\t\t\tORDER BY created_at DESC\n\t\t\tLIMIT 1`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'get-last-uncancelled',\n\t\t`SELECT m.* FROM messages m\n\t\t\tWHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t)\n\treturn {\n\t\tname: 'build-tool-plan-context',\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t}\n}\n\nasync function runCatchupSnapshot(sql: Db, version: number): Promise {\n\tconst startedAt = performance.now()\n\tconst steps: SlowReconnectStep[] = []\n\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'thread-events-list-since-version',\n\t\t`SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`,\n\t\tversion,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'environment-snapshot',\n\t\t`SELECT snapshot FROM environment_snapshot WHERE id = 1`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'thread-settings-snapshot',\n\t\t`SELECT settings FROM thread_settings_snapshot WHERE id = 1`,\n\t)\n\tawait timedQuery(sql, steps, 'retry-state', `SELECT * FROM retry_state WHERE id = 1`)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'queued-messages',\n\t\t`SELECT * FROM queued_messages ORDER BY created_at ASC`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'executor-artifacts',\n\t\t`SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC`,\n\t)\n\tawait timedQuery(sql, steps, 'tool-approvals', `SELECT * FROM tool_approvals ORDER BY timestamp ASC`)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'compaction-summaries',\n\t\t`SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'executor-status',\n\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_status'`,\n\t)\n\n\tsteps.sort((a, b) => b.durationMs - a.durationMs)\n\treturn { name: 'catchup-snapshot', totalMs: Math.round(performance.now() - startedAt), steps }\n}\n\nasync function runRecoverToolCalls(sql: Db): Promise {\n\tconst startedAt = performance.now()\n\tconst steps: SlowReconnectStep[] = []\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'hydrate-tool-progress',\n\t\t`SELECT id, progress\n\t\t\tFROM tool_calls\n\t\t\tWHERE progress IS NOT NULL\n\t\t\t\tAND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'get-pending-tool-calls',\n\t\t`SELECT * FROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')\n\t\t\tORDER BY issued_at ASC`,\n\t)\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t'get-next-tool-expiry',\n\t\t`SELECT MIN(expires_at) AS expires_at\n\t\t\tFROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t)\n\treturn { name: 'recover-tool-calls', totalMs: Math.round(performance.now() - startedAt), steps }\n}\n\nasync function timedQuery>(\n\tsql: Db,\n\tsteps: SlowReconnectStep[],\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now()\n\tconst rows = await sql(query, ...values)\n\tsteps.push({\n\t\tname,\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: rows.length,\n\t})\n\treturn rows\n}\n\nfunction hasPendingLaunch(value: unknown): boolean {\n\tif (typeof value !== 'string' || value.length === 0) {\n\t\treturn false\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(value) as { pendingLaunch?: unknown }\n\t\treturn parsed.pendingLaunch !== null && parsed.pendingLaunch !== undefined\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)))\n}\n\nasync function createSlowReconnectSchema(database: RawRivetDB): Promise {\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS executor_tools (\n\t\texecutor_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\tschema TEXT NOT NULL,\n\t\tupdated_at TEXT NOT NULL,\n\t\tPRIMARY KEY (executor_id, tool_name)\n\t)`)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)`,\n\t)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv (\n\t\tkey TEXT PRIMARY KEY,\n\t\tvalue TEXT,\n\t\tupdated_at TEXT NOT NULL\n\t)`)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_events (\n\t\tseq INTEGER PRIMARY KEY,\n\t\tevent_type TEXT NOT NULL,\n\t\tpayload TEXT NOT NULL,\n\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t)`)\n\tawait database.execute(`CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)`)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_added_events (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\tseq INTEGER NOT NULL UNIQUE\n\t)`)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS messages (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\trole TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')),\n\t\tcontent TEXT NOT NULL,\n\t\tmeta TEXT,\n\t\tuser_state TEXT,\n\t\tcreated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tread_at TEXT,\n\t\tparent_tool_use_id TEXT,\n\t\ttool_result_for_message_id TEXT\n\t)`)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)`,\n\t)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs (\n\t\tsource_message_id TEXT NOT NULL,\n\t\tassistant_message_id TEXT NOT NULL,\n\t\ttool_use_id TEXT NOT NULL,\n\t\tblock_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tPRIMARY KEY (source_message_id, block_type, tool_use_id)\n\t)`)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0`,\n\t)\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS tool_calls (\n\t\tid TEXT PRIMARY KEY,\n\t\tprovider_tool_use_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\targs TEXT NOT NULL,\n\t\texecutor_id TEXT,\n\t\tmessage_id TEXT NOT NULL,\n\t\tissued_at TEXT NOT NULL,\n\t\texpires_at TEXT,\n\t\tstate TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')),\n\t\tresult TEXT,\n\t\tprogress TEXT,\n\t\tcompleted_at TEXT\n\t)`)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)`,\n\t)\n\tawait database.execute(`CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)`)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))`,\n\t)\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)`,\n\t)\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)`,\n\t)\n}\n\nasync function seedSlowReconnectData(database: RawRivetDB): Promise<{\n\tseeded: boolean\n\tmessages: number\n\ttoolCalls: number\n\tthreadEvents: number\n}> {\n\tconst existing = await database.execute(`SELECT COUNT(*) AS count FROM messages`)\n\tif (Number(existing[0]?.count ?? 0) > 0) {\n\t\tconst [toolCalls] = await database.execute(`SELECT COUNT(*) AS count FROM tool_calls`)\n\t\tconst [threadEvents] = await database.execute(`SELECT COUNT(*) AS count FROM thread_events`)\n\t\treturn {\n\t\t\tseeded: false,\n\t\t\tmessages: Number(existing[0]?.count ?? 0),\n\t\t\ttoolCalls: Number(toolCalls?.count ?? 0),\n\t\t\tthreadEvents: Number(threadEvents?.count ?? 0),\n\t\t}\n\t}\n\n\t// Randomize the seeded data volume so each actor's database differs\n\t// substantially in size. This exercises the reconnect repro across a wide\n\t// range of catch-up payload sizes instead of a single fixed shape.\n\tconst vary = (base: number, min = 1) =>\n\t\tMath.max(min, Math.round(base * (0.25 + Math.random() * 3.75)))\n\tconst messageCount = vary(MESSAGE_COUNT, 2)\n\tconst messageToolRefCount = vary(MESSAGE_TOOL_REF_COUNT, 2)\n\tconst toolCallCount = vary(TOOL_CALL_COUNT)\n\tconst executorToolCount = vary(EXECUTOR_TOOL_COUNT)\n\tconst threadEventCount = vary(THREAD_EVENT_COUNT)\n\tconst assistantSpan = Math.max(1, Math.floor(messageCount / 2))\n\n\tconst now = new Date('2026-05-16T03:58:18.661Z').getTime()\n\tconst text = (size: number) => 'x'.repeat(size)\n\tconst isoAt = (index: number) => new Date(now + index * 1_000).toISOString()\n\n\tawait batchInsert(database, `INSERT INTO thread_meta_kv (key, value, updated_at)`, [\n\t\t['executor_type', 'local-client', isoAt(0)],\n\t\t['sandbox_intent', JSON.stringify({ spec: null, pendingLaunch: null }), isoAt(0)],\n\t\t['executor_status', JSON.stringify({ available: true, message: 'ready' }), isoAt(0)],\n\t])\n\n\tconst messageRows: unknown[][] = []\n\tfor (let index = 1; index <= messageCount; index++) {\n\t\tconst role = index % 2 === 0 ? 'assistant' : 'user'\n\t\tmessageRows.push([\n\t\t\tmessageId(index),\n\t\t\trole,\n\t\t\ttext(MESSAGE_CONTENT_BYTES),\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tisoAt(index),\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tnull,\n\t\t])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`,\n\t\tmessageRows,\n\t\t20,\n\t)\n\n\tconst messageToolRefRows: unknown[][] = []\n\tfor (let index = 0; index < messageToolRefCount / 2; index++) {\n\t\tconst assistantIndex = 2 + (index % assistantSpan) * 2\n\t\tconst sourceIndex = Math.max(1, assistantIndex - 1)\n\t\tconst resultIndex = Math.min(messageCount, assistantIndex + 1)\n\t\tconst toolUseId = toolUseID(index + 1)\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(sourceIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t'tool_use',\n\t\t\t0,\n\t\t])\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(resultIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t'tool_result',\n\t\t\t0,\n\t\t])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`,\n\t\tmessageToolRefRows,\n\t\t50,\n\t)\n\n\tconst toolCallRows: unknown[][] = []\n\tfor (let index = 1; index <= toolCallCount; index++) {\n\t\tconst assistantIndex = 2 + ((index - 1) % assistantSpan) * 2\n\t\ttoolCallRows.push([\n\t\t\ttoolUseID(index),\n\t\t\t`provider-${index}`,\n\t\t\t`tool_${index % 21}`,\n\t\t\tJSON.stringify({ path: `/tmp/file-${index}` }),\n\t\t\t'seed-executor',\n\t\t\tmessageId(assistantIndex),\n\t\t\tisoAt(index),\n\t\t\tnull,\n\t\t\t'completed',\n\t\t\tJSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\trun: { status: 'done', result: text(TOOL_CALL_RESULT_BYTES) },\n\t\t\t}),\n\t\t\tnull,\n\t\t\tisoAt(index + 100),\n\t\t])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`,\n\t\ttoolCallRows,\n\t\t20,\n\t)\n\n\tconst executorToolRows: unknown[][] = []\n\tfor (let index = 1; index <= executorToolCount; index++) {\n\t\tconst schema = JSON.stringify({\n\t\t\tname: `tool_${index}`,\n\t\t\tdescription: text(EXECUTOR_TOOL_SCHEMA_BYTES),\n\t\t\tinput_schema: { type: 'object', properties: {} },\n\t\t})\n\t\texecutorToolRows.push(['seed-executor', `tool_${index}`, schema, isoAt(index)])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`,\n\t\texecutorToolRows,\n\t\t42,\n\t)\n\n\tconst threadEventRows: unknown[][] = []\n\tfor (let index = 1; index <= threadEventCount; index++) {\n\t\tthreadEventRows.push([\n\t\t\tindex,\n\t\t\tindex % 3 === 0 ? 'message_added' : 'agent_state_changed',\n\t\t\tJSON.stringify({ type: 'seed_event', body: text(THREAD_EVENT_PAYLOAD_BYTES) }),\n\t\t\tisoAt(index),\n\t\t])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at)`,\n\t\tthreadEventRows,\n\t\t25,\n\t)\n\n\tconst messageAddedRows: unknown[][] = []\n\tfor (let index = 1; index <= messageCount; index++) {\n\t\tmessageAddedRows.push([messageId(index), index])\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_added_events (message_id, seq)`,\n\t\tmessageAddedRows,\n\t\t50,\n\t)\n\n\tawait database.execute(\n\t\t`INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ cwd: '/workspace', body: text(3_620) }),\n\t\tisoAt(0),\n\t)\n\tawait database.execute(\n\t\t`INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ maxTokens: 20_000, body: text(55) }),\n\t\tisoAt(0),\n\t)\n\treturn {\n\t\tseeded: true,\n\t\tmessages: messageCount,\n\t\ttoolCalls: toolCallCount,\n\t\tthreadEvents: threadEventCount,\n\t}\n}\n\n// The engine rejects a remote SQLite statement whose bind params exceed 128\n// KiB. Keep each chunk under a budget below that so wide rows (large message\n// content) never trip the limit, regardless of the row-count batch size.\nconst MAX_BATCH_BIND_BYTES = 120 * 1024\n\nfunction bindParamBytes(value: unknown): number {\n\tif (value === null || value === undefined) {\n\t\treturn 0\n\t}\n\tif (typeof value === 'string') {\n\t\treturn Buffer.byteLength(value, 'utf8')\n\t}\n\tif (typeof value === 'number') {\n\t\treturn 8\n\t}\n\tif (value instanceof Uint8Array) {\n\t\treturn value.byteLength\n\t}\n\treturn Buffer.byteLength(String(value), 'utf8')\n}\n\nasync function batchInsert(\n\tdatabase: RawRivetDB,\n\tinsertPrefix: string,\n\trows: unknown[][],\n\tbatchSize = 100,\n): Promise {\n\tif (rows.length === 0) {\n\t\treturn\n\t}\n\tconst columnCount = rows[0]?.length ?? 0\n\tif (columnCount === 0) {\n\t\treturn\n\t}\n\tconst rowPlaceholder = `(${'?,'.repeat(columnCount).slice(0, -1)})`\n\tconst rowBytes = rows.map((row) =>\n\t\trow.reduce((sum: number, value) => sum + bindParamBytes(value), 0),\n\t)\n\n\tlet index = 0\n\twhile (index < rows.length) {\n\t\tconst chunk: unknown[][] = []\n\t\tlet chunkBytes = 0\n\t\twhile (\n\t\t\tindex < rows.length &&\n\t\t\tchunk.length < batchSize &&\n\t\t\t// Always take at least one row so an oversized single row still runs\n\t\t\t// (and surfaces the engine error) rather than looping forever.\n\t\t\t(chunk.length === 0 ||\n\t\t\t\tchunkBytes + rowBytes[index]! <= MAX_BATCH_BIND_BYTES)\n\t\t) {\n\t\t\tchunk.push(rows[index]!)\n\t\t\tchunkBytes += rowBytes[index]!\n\t\t\tindex++\n\t\t}\n\t\tconst values = chunk.map(() => rowPlaceholder).join(',')\n\t\tconst bindings = chunk.flat()\n\t\tawait database.execute(`${insertPrefix} VALUES ${values}`, ...bindings)\n\t}\n}\n\nfunction messageId(index: number): string {\n\treturn `M-${String(index).padStart(22, '0')}`\n}\n\nfunction toolUseID(index: number): string {\n\treturn `toolu_${String(index).padStart(22, '0')}`\n}\n\nexport const registry = setup({\n\tuse: { slowReconnectActor },\n\tmaxIncomingMessageSize: 5 * 1024 * 1024,\n\tmaxOutgoingMessageSize: 5 * 1024 * 1024,\n})\n\nif (import.meta.main) {\n\tregistry.start()\n}\n","import { openai } from \"@ai-sdk/openai\";\nimport { generateText, tool } from \"ai\";\nimport { actor, event } from \"rivetkit\";\nimport { z } from \"zod\";\nimport { getWeather } from \"./my-tools.ts\";\nimport type { Message } from \"./types.ts\";\n\nexport const aiAgent = actor({\n\t// Persistent state that survives restarts: https://rivet.dev/docs/actors/state\n\tstate: {\n\t\tmessages: [] as Message[],\n\t},\n\tevents: {\n\t\tmessageReceived: event(),\n\t},\n\n\tactions: {\n\t\t// Callable functions from clients: https://rivet.dev/docs/actors/actions\n\t\tgetMessages: (c) => c.state.messages,\n\n\t\tsendMessage: async (c, userMessage: string) => {\n\t\t\tconst userMsg: Message = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: userMessage,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\t// State changes are automatically persisted\n\t\t\tc.state.messages.push(userMsg);\n\n\t\t\tconst { text } = await generateText({\n\t\t\t\tmodel: openai(\"gpt-4o-mini\"),\n\t\t\t\tprompt: userMessage,\n\t\t\t\tmessages: c.state.messages,\n\t\t\t\ttools: {\n\t\t\t\t\tweather: tool({\n\t\t\t\t\t\tdescription: \"Get the weather in a location\",\n\t\t\t\t\t\tparameters: z.object({\n\t\t\t\t\t\t\tlocation: z\n\t\t\t\t\t\t\t\t.string()\n\t\t\t\t\t\t\t\t.describe(\n\t\t\t\t\t\t\t\t\t\"The location to get the weather for\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}),\n\t\t\t\t\t\texecute: async ({ location }) => {\n\t\t\t\t\t\t\treturn await getWeather(location);\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst assistantMsg: Message = {\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: text,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tc.state.messages.push(assistantMsg);\n\n\t\t\t// Send events to all connected clients: https://rivet.dev/docs/actors/events\n\t\t\tc.broadcast(\"messageReceived\", assistantMsg);\n\n\t\t\treturn assistantMsg;\n\t\t},\n\t},\n});\n","export async function getWeather(location: string) {\n\t// Mock weather API response\n\treturn {\n\t\tlocation,\n\t\ttemperature: Math.floor(Math.random() * 30) + 10,\n\t\tcondition: [\"sunny\", \"cloudy\", \"rainy\", \"snowy\"][\n\t\t\tMath.floor(Math.random() * 4)\n\t\t],\n\t\thumidity: Math.floor(Math.random() * 50) + 30,\n\t};\n}\n","import { registry } from \"./index.ts\";\nimport { resolveMode } from \"./mode.ts\";\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport type { Server as HttpServer } from \"node:http\";\nimport * as v8 from \"node:v8\";\n\nconst app = new Hono();\nconst port = Number.parseInt(process.env.PORT ?? \"3000\", 10);\nconst mode = resolveMode();\n\nprocess.on(\"exit\", (code) => {\n\tconsole.log(JSON.stringify({ kind: \"process_exit\", code, pid: process.pid }));\n});\nif (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS === \"1\") {\n\tfor (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n\t\tprocess.on(signal, () => {\n\t\t\tconsole.log(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tkind: \"process_signal\",\n\t\t\t\t\tsignal,\n\t\t\t\t\tpid: process.pid,\n\t\t\t\t\tppid: process.ppid,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tprocess.exit(signal === \"SIGINT\" ? 130 : 143);\n\t\t});\n\t}\n}\nprocess.on(\"beforeExit\", (code) => {\n\tconsole.log(JSON.stringify({ kind: \"process_before_exit\", code, pid: process.pid }));\n});\nprocess.on(\"uncaughtException\", (error) => {\n\tconsole.error(\n\t\tJSON.stringify({\n\t\t\tkind: \"uncaught_exception\",\n\t\t\terror: error.stack ?? error.message,\n\t\t}),\n\t);\n});\nprocess.on(\"unhandledRejection\", (reason) => {\n\tconsole.error(\n\t\tJSON.stringify({\n\t\t\tkind: \"unhandled_rejection\",\n\t\t\terror: reason instanceof Error ? reason.stack ?? reason.message : String(reason),\n\t\t}),\n\t);\n});\n\nasync function memoryBreakdown(forceGc: boolean) {\n\tconst gc = (globalThis as typeof globalThis & { gc?: () => void }).gc;\n\tif (forceGc && typeof gc === \"function\") gc();\n\n\tconst memory = process.memoryUsage();\n\tconst heap = v8.getHeapStatistics();\n\tconst spaces = v8.getHeapSpaceStatistics();\n\tconst nativeNonV8Estimate = Math.max(0, memory.rss - heap.total_heap_size);\n\n\treturn {\n\t\tpid: process.pid,\n\t\ttimestamp: new Date().toISOString(),\n\t\tuptimeSeconds: process.uptime(),\n\t\tgcRequested: forceGc,\n\t\tgcAvailable: typeof gc === \"function\",\n\t\tprocess: {\n\t\t\trssBytes: memory.rss,\n\t\t\theapTotalBytes: memory.heapTotal,\n\t\t\theapUsedBytes: memory.heapUsed,\n\t\t\texternalBytes: memory.external,\n\t\t\tarrayBuffersBytes: memory.arrayBuffers,\n\t\t},\n\t\tv8: {\n\t\t\ttotalHeapSizeBytes: heap.total_heap_size,\n\t\t\tusedHeapSizeBytes: heap.used_heap_size,\n\t\t\theapSizeLimitBytes: heap.heap_size_limit,\n\t\t\tmallocedMemoryBytes: heap.malloced_memory,\n\t\t\texternalMemoryBytes: heap.external_memory,\n\t\t\tpeakMallocedMemoryBytes: heap.peak_malloced_memory,\n\t\t\tspaces: spaces.map((space) => ({\n\t\t\t\tname: space.space_name,\n\t\t\t\tsizeBytes: space.space_size,\n\t\t\t\tusedBytes: space.space_used_size,\n\t\t\t\tavailableBytes: space.space_available_size,\n\t\t\t\tphysicalSizeBytes: space.physical_space_size,\n\t\t\t})),\n\t\t},\n\t\testimates: {\n\t\t\tjsHeapResidentBytes: memory.heapTotal,\n\t\t\tjsHeapUsedBytes: memory.heapUsed,\n\t\t\tv8ExternalBytes: memory.external,\n\t\t\tnativeNonV8ResidentEstimateBytes: nativeNonV8Estimate,\n\t\t},\n\t\tresourceUsage: process.resourceUsage(),\n\t};\n}\n\nfunction requestHeaders(headers: Headers) {\n\tconst entries: Array<[string, string]> = [];\n\theaders.forEach((value, key) => {\n\t\tentries.push([\n\t\t\tkey,\n\t\t\tkey === \"authorization\" || key === \"x-rivet-token\"\n\t\t\t\t? \"\"\n\t\t\t\t: value,\n\t\t]);\n\t});\n\treturn Object.fromEntries(entries);\n}\n\napp.get(\"/debug/memory\", async (c) => {\n\tconst forceGc = c.req.query(\"gc\") === \"1\";\n\treturn c.json(await memoryBreakdown(forceGc));\n});\n\napp.get(\"/health\", () => registry.routes.health());\n\napp.get(\"/metadata\", () => registry.routes.metadata());\n\napp.get(\"/metrics\", (c) => registry.routes.prometheusMetrics(c.req.raw));\n\napp.post(\"/debug/heap-snapshot\", (c) => {\n\tif (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS !== \"1\") {\n\t\treturn c.json({ error: \"disabled\" }, 404);\n\t}\n\n\tconst path = c.req.query(\"path\");\n\tif (!path) {\n\t\treturn c.json({ error: \"missing path\" }, 400);\n\t}\n\n\tconst writtenPath = v8.writeHeapSnapshot(path);\n\treturn c.json({ path: writtenPath });\n});\n\napp.use(\"*\", async (c, next) => {\n\tconst startedAt = Date.now();\n\tawait next();\n\t// console.log(\n\t// \tJSON.stringify({\n\t// \t\tkind: \"request\",\n\t// \t\tmethod: c.req.method,\n\t// \t\tpath: new URL(c.req.url).pathname,\n\t// \t\theaders: requestHeaders(c.req.raw.headers),\n\t// \t\tstatus: c.res.status,\n\t// \t\tdurationMs: Date.now() - startedAt,\n\t// \t}),\n\t// );\n});\n\n// Only wire the serverless handler in serverless modes. In `serverful` mode\n// the runner connects via `registry.start()` and any stray hit on\n// `/api/rivet/start` would spin up a second envoy alongside the persistent\n// one.\nif (mode === \"serverful\") {\n\tregistry.start();\n} else {\n\tapp.all(\"/api/rivet/*\", (c) => registry.handler(c.req.raw));\n\tapp.all(\"/api/rivet\", (c) => registry.handler(c.req.raw));\n}\n\nconst server = serve({ fetch: app.fetch, port }, () => {\n\tif (mode === \"serverful\") {\n\t\tconsole.log(\n\t\t\t`kitchen sink (serverful) listening on http://127.0.0.1:${port}`,\n\t\t);\n\t} else {\n\t\tconsole.log(\n\t\t\t`kitchen sink (${mode}) listening on http://127.0.0.1:${port}/api/rivet`,\n\t\t);\n\t}\n});\nconst httpServer = server as unknown as HttpServer;\nhttpServer.requestTimeout = 0;\nhttpServer.headersTimeout = 0;\nhttpServer.keepAliveTimeout = 0;\nhttpServer.timeout = 0;\n"],"mappings":"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","names":["setup","event","actor","event","actor","event","actor","actor","event","actor","actor","actor","actor","UserError","actor","actor","actor","actor","actor","actor","db","actor","db","todos","actor","db","actor","event","db","actor","event","c","actor","UserError","actor","event","actor","actor","text","actor","event","Hono","actor","app","actor","event","actor","event","actor","event","actor","actor","event","sleep","actor","event","actor","actor","actor","event","queue","actor","event","queue","payload","actor","event","queue","payload","actor","event","Loop","workflow","actor","event","workflow","Loop","actor","event","Loop","workflow","actor","event","workflow","Loop","actor","event","Loop","workflow","actor","event","workflow","batch","Loop","actor","event","queue","Loop","workflow","actor","queue","event","workflow","Loop","actor","event","queue","Loop","workflow","actor","queue","event","workflow","Loop","actor","event","Loop","workflow","actor","event","workflow","Loop","actor","event","Loop","workflow","actor","event","workflow","Loop","actor","queue","Loop","workflow","actor","workflow","Loop","queue","actor","actor","events","result1","result2","actor","actor","db","actor","db","actor","db","batch","actor","db","actor","db","DEFAULT_ROW_BYTES","CHAT_LOG_CHUNK_BYTES","CHAT_LOG_INSERT_BATCH_SIZE","positiveInteger","buildChatLogMessage","seedChatLog","checksum","actor","db","payload","counter","mode","event","actor","db","DEFAULT_ROW_BYTES","numberField","queryOne","actor","db","sleep","positiveInteger","typedRows","payload","event","actor","event","actor","db","send","payload","sleep","event","actor","db","send","event","result","delay","text","batchInsert","actor","db","sleep","event","actor","db","AsyncMutex","MESSAGE_COUNT","MESSAGE_TOOL_REF_COUNT","TOOL_CALL_COUNT","EXECUTOR_TOOL_COUNT","THREAD_EVENT_COUNT","MESSAGE_CONTENT_BYTES","THREAD_EVENT_PAYLOAD_BYTES","TOOL_CALL_RESULT_BYTES","EXECUTOR_TOOL_SCHEMA_BYTES","event","result","runCatchupSnapshot","numberField","stringField","runBuildToolPlanContext","runRecoverToolCalls","delay","timedQuery","hasPendingLaunch","text","batchInsert","messageId","toolUseID","actor","event","actor","event","text","numberFromEnv","registry","setup","sleep","Hono","registry"]} \ No newline at end of file +{"version":3,"sources":["../src/server.ts","../src/index.ts","../src/actors/actions/action-inputs.ts","../src/actors/actions/action-timeout.ts","../src/actors/actions/action-types.ts","../src/actors/actions/error-handling.ts","../src/actors/ai/ai-agent.ts","../src/actors/ai/my-tools.ts","../src/actors/connections/conn-state.ts","../src/actors/connections/reject-connection.ts","../src/actors/connections/request-access.ts","../src/actors/counter/conn-params.ts","../src/actors/counter/counter.ts","../src/actors/counter/counter-conn.ts","../src/actors/counter/lifecycle.ts","../src/actors/counter/ping-pong-counter.ts","../src/actors/http/raw-fetch-counter.ts","../src/actors/http/raw-http.ts","../src/actors/http/raw-http-request-properties.ts","../src/actors/http/raw-websocket.ts","../src/actors/http/raw-websocket-chat-room.ts","../src/actors/http/raw-websocket-serverless-smoke.ts","../src/actors/http/tunnel-stress.ts","../src/actors/inter-actor/cross-actor-actions.ts","../src/actors/lifecycle/destroy.ts","../src/actors/lifecycle/hibernation.ts","../src/actors/lifecycle/run.ts","../src/actors/lifecycle/scheduled.ts","../src/actors/lifecycle/sleep.ts","../src/actors/queue/worker.ts","../src/actors/queue/worker-timeout.ts","../src/actors/state/actor-onstatechange.ts","../src/actors/state/kv.ts","../src/actors/state/large-payloads.ts","../src/actors/state/metadata.ts","../src/actors/state/parallelism-test.ts","../src/actors/state/sqlite-drizzle/mod.ts","../src/actors/state/sqlite-drizzle/drizzle/meta/_journal.json","../src/actors/state/sqlite-drizzle/drizzle/0000_left_wrecking_crew.sql","../src/actors/state/sqlite-drizzle/drizzle/migrations.js","../src/actors/state/sqlite-drizzle/schema.ts","../src/actors/state/sqlite-raw.ts","../src/actors/state/vars.ts","../src/actors/testing/grow-db.ts","../src/actors/testing/inline-client.ts","../src/actors/testing/load-test-agent.ts","../src/actors/testing/load-test-agent-2.ts","../src/actors/testing/mock-agentic-loop.ts","../src/actors/testing/raw-sqlite-fuzzer.ts","../src/actors/testing/sigterm-sleep-probe.ts","../src/actors/testing/sleep-close-fuzz.ts","../src/actors/testing/slow-reconnect-actor.ts","../src/actors/testing/sqlite-cold-start-bench.ts","../src/actors/testing/sqlite-memory-pressure.ts","../src/actors/testing/sqlite-realworld-bench.ts","../src/actors/testing/test-counter.ts","../src/actors/testing/test-counter-sqlite.ts","../src/actors/testing/test-sqlite-bench.ts","../src/actors/testing/test-sqlite-load.ts","../src/actors/workflow/approval.ts","../src/actors/workflow/batch.ts","../src/actors/workflow/dashboard.ts","../src/actors/workflow/history-examples.ts","../src/actors/workflow/order.ts","../src/actors/workflow/payment.ts","../src/actors/workflow/race.ts","../src/actors/workflow/timer.ts","../src/actors/workflow/workflow-fixtures.ts","../src/mode.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport type { Server as HttpServer } from \"node:http\";\nimport { resolve } from \"node:path\";\nimport * as v8 from \"node:v8\";\nimport { serve } from \"@hono/node-server\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { Hono } from \"hono\";\nimport { registry } from \"./index.ts\";\nimport { resolveMode } from \"./mode.ts\";\n\nconst app = new Hono();\nconst port = Number.parseInt(process.env.PORT ?? \"3000\", 10);\nconst mode = resolveMode();\n\nprocess.on(\"exit\", (code) => {\n\tconsole.log(\n\t\tJSON.stringify({ kind: \"process_exit\", code, pid: process.pid }),\n\t);\n});\nif (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS === \"1\") {\n\tfor (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n\t\tprocess.on(signal, () => {\n\t\t\tconsole.log(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tkind: \"process_signal\",\n\t\t\t\t\tsignal,\n\t\t\t\t\tpid: process.pid,\n\t\t\t\t\tppid: process.ppid,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tprocess.exit(signal === \"SIGINT\" ? 130 : 143);\n\t\t});\n\t}\n}\nprocess.on(\"beforeExit\", (code) => {\n\tconsole.log(\n\t\tJSON.stringify({ kind: \"process_before_exit\", code, pid: process.pid }),\n\t);\n});\nprocess.on(\"uncaughtException\", (error) => {\n\tconsole.error(\n\t\tJSON.stringify({\n\t\t\tkind: \"uncaught_exception\",\n\t\t\terror: error.stack ?? error.message,\n\t\t}),\n\t);\n});\nprocess.on(\"unhandledRejection\", (reason) => {\n\tconsole.error(\n\t\tJSON.stringify({\n\t\t\tkind: \"unhandled_rejection\",\n\t\t\terror:\n\t\t\t\treason instanceof Error\n\t\t\t\t\t? (reason.stack ?? reason.message)\n\t\t\t\t\t: String(reason),\n\t\t}),\n\t);\n});\n\nasync function memoryBreakdown(forceGc: boolean) {\n\tconst gc = (globalThis as typeof globalThis & { gc?: () => void }).gc;\n\tif (forceGc && typeof gc === \"function\") gc();\n\n\tconst memory = process.memoryUsage();\n\tconst heap = v8.getHeapStatistics();\n\tconst spaces = v8.getHeapSpaceStatistics();\n\tconst nativeNonV8Estimate = Math.max(0, memory.rss - heap.total_heap_size);\n\n\treturn {\n\t\tpid: process.pid,\n\t\ttimestamp: new Date().toISOString(),\n\t\tuptimeSeconds: process.uptime(),\n\t\tgcRequested: forceGc,\n\t\tgcAvailable: typeof gc === \"function\",\n\t\tprocess: {\n\t\t\trssBytes: memory.rss,\n\t\t\theapTotalBytes: memory.heapTotal,\n\t\t\theapUsedBytes: memory.heapUsed,\n\t\t\texternalBytes: memory.external,\n\t\t\tarrayBuffersBytes: memory.arrayBuffers,\n\t\t},\n\t\tv8: {\n\t\t\ttotalHeapSizeBytes: heap.total_heap_size,\n\t\t\tusedHeapSizeBytes: heap.used_heap_size,\n\t\t\theapSizeLimitBytes: heap.heap_size_limit,\n\t\t\tmallocedMemoryBytes: heap.malloced_memory,\n\t\t\texternalMemoryBytes: heap.external_memory,\n\t\t\tpeakMallocedMemoryBytes: heap.peak_malloced_memory,\n\t\t\tspaces: spaces.map((space) => ({\n\t\t\t\tname: space.space_name,\n\t\t\t\tsizeBytes: space.space_size,\n\t\t\t\tusedBytes: space.space_used_size,\n\t\t\t\tavailableBytes: space.space_available_size,\n\t\t\t\tphysicalSizeBytes: space.physical_space_size,\n\t\t\t})),\n\t\t},\n\t\testimates: {\n\t\t\tjsHeapResidentBytes: memory.heapTotal,\n\t\t\tjsHeapUsedBytes: memory.heapUsed,\n\t\t\tv8ExternalBytes: memory.external,\n\t\t\tnativeNonV8ResidentEstimateBytes: nativeNonV8Estimate,\n\t\t},\n\t\tresourceUsage: process.resourceUsage(),\n\t};\n}\n\nfunction _requestHeaders(headers: Headers) {\n\tconst entries: Array<[string, string]> = [];\n\theaders.forEach((value, key) => {\n\t\tentries.push([\n\t\t\tkey,\n\t\t\tkey === \"authorization\" || key === \"x-rivet-token\"\n\t\t\t\t? \"\"\n\t\t\t\t: value,\n\t\t]);\n\t});\n\treturn Object.fromEntries(entries);\n}\n\napp.get(\"/debug/memory\", async (c) => {\n\tconst forceGc = c.req.query(\"gc\") === \"1\";\n\treturn c.json(await memoryBreakdown(forceGc));\n});\n\napp.get(\"/health\", (c) => c.json({ ok: true }));\napp.get(\"/metadata\", () => registry.routes.metadata());\napp.get(\"/metrics\", (c) => registry.routes.prometheusMetrics(c.req.raw));\n\napp.post(\"/debug/heap-snapshot\", (c) => {\n\tif (process.env.SQLITE_MEMORY_SOAK_DIAGNOSTICS !== \"1\") {\n\t\treturn c.json({ error: \"disabled\" }, 404);\n\t}\n\n\tconst path = c.req.query(\"path\");\n\tif (!path) {\n\t\treturn c.json({ error: \"missing path\" }, 400);\n\t}\n\n\tconst writtenPath = v8.writeHeapSnapshot(path);\n\treturn c.json({ path: writtenPath });\n});\n\napp.use(\"*\", async (_c, next) => {\n\tconst _startedAt = Date.now();\n\tawait next();\n\t// console.log(\n\t// \tJSON.stringify({\n\t// \t\tkind: \"request\",\n\t// \t\tmethod: c.req.method,\n\t// \t\tpath: new URL(c.req.url).pathname,\n\t// \t\theaders: requestHeaders(c.req.raw.headers),\n\t// \t\tstatus: c.res.status,\n\t// \t\tdurationMs: Date.now() - startedAt,\n\t// \t}),\n\t// );\n});\n\n// Only wire the serverless handler in serverless modes. In `serverful` mode\n// the runner connects via `registry.start()` and any stray hit on\n// `/api/rivet/start` would spin up a second envoy alongside the persistent\n// one.\nif (mode === \"serverful\") {\n\tregistry.start();\n} else {\n\tapp.all(\"/api/rivet/*\", (c) => registry.handler(c.req.raw));\n\tapp.all(\"/api/rivet\", (c) => registry.handler(c.req.raw));\n}\n\n// Serve the built frontend when it is present. The Vite build emits `dist/`,\n// which only exists in production images, so dev runs skip this branch.\nconst distDir = \"dist\";\nconst indexPath = resolve(process.cwd(), distDir, \"index.html\");\nif (existsSync(indexPath)) {\n\tapp.use(\"/*\", serveStatic({ root: distDir }));\n\tconst indexHtml = readFileSync(indexPath, \"utf8\");\n\tapp.get(\"/*\", (c) => {\n\t\tconst path = new URL(c.req.url).pathname;\n\t\tconst last = path.slice(path.lastIndexOf(\"/\") + 1);\n\t\t// Fall through to 404 for asset-like paths so missing files do not\n\t\t// resolve to the SPA shell.\n\t\tif (last.includes(\".\")) return c.notFound();\n\t\treturn c.html(indexHtml);\n\t});\n}\n\nconst server = serve({ fetch: app.fetch, port }, () => {\n\tif (mode === \"serverful\") {\n\t\tconsole.log(\n\t\t\t`kitchen sink (serverful) listening on http://127.0.0.1:${port}`,\n\t\t);\n\t} else {\n\t\tconsole.log(\n\t\t\t`kitchen sink (${mode}) listening on http://127.0.0.1:${port}/api/rivet`,\n\t\t);\n\t}\n});\nconst httpServer = server as unknown as HttpServer;\nhttpServer.requestTimeout = 0;\nhttpServer.headersTimeout = 0;\nhttpServer.keepAliveTimeout = 0;\nhttpServer.timeout = 0;\n","import { setup } from \"rivetkit\";\n// Actions\nimport { inputActor } from \"./actors/actions/action-inputs.ts\";\nimport {\n\tdefaultTimeoutActor,\n\tlongTimeoutActor,\n\tshortTimeoutActor,\n\tsyncTimeoutActor,\n} from \"./actors/actions/action-timeout.ts\";\nimport {\n\tasyncActionActor,\n\tpromiseActor,\n\tsyncActionActor,\n} from \"./actors/actions/action-types.ts\";\nimport {\n\tcustomTimeoutActor,\n\terrorHandlingActor,\n} from \"./actors/actions/error-handling.ts\";\n// AI\nimport { aiAgent } from \"./actors/ai/ai-agent.ts\";\n// Connections\nimport { connStateActor } from \"./actors/connections/conn-state.ts\";\nimport { rejectConnectionActor } from \"./actors/connections/reject-connection.ts\";\nimport { requestAccessActor } from \"./actors/connections/request-access.ts\";\nimport { counterWithParams } from \"./actors/counter/conn-params.ts\";\n// Counter\nimport { counter } from \"./actors/counter/counter.ts\";\nimport { counterConn } from \"./actors/counter/counter-conn.ts\";\nimport { counterWithLifecycle } from \"./actors/counter/lifecycle.ts\";\nimport { pingPongCounter } from \"./actors/counter/ping-pong-counter.ts\";\nimport { rawFetchCounter } from \"./actors/http/raw-fetch-counter.ts\";\n// HTTP\nimport {\n\trawHttpActor,\n\trawHttpHonoActor,\n\trawHttpNoHandlerActor,\n\trawHttpVoidReturnActor,\n} from \"./actors/http/raw-http.ts\";\nimport { rawHttpRequestPropertiesActor } from \"./actors/http/raw-http-request-properties.ts\";\nimport {\n\trawWebSocketActor,\n\trawWebSocketBinaryActor,\n} from \"./actors/http/raw-websocket.ts\";\nimport { rawWebSocketChatRoom } from \"./actors/http/raw-websocket-chat-room.ts\";\nimport { rawWebSocketServerlessSmoke } from \"./actors/http/raw-websocket-serverless-smoke.ts\";\nimport { tunnelStress } from \"./actors/http/tunnel-stress.ts\";\n// Inter-actor\nimport {\n\tcheckout,\n\tinventory,\n} from \"./actors/inter-actor/cross-actor-actions.ts\";\nimport { destroyActor, destroyObserver } from \"./actors/lifecycle/destroy.ts\";\nimport { hibernationActor } from \"./actors/lifecycle/hibernation.ts\";\n// Lifecycle\nimport {\n\trunWithEarlyExit,\n\trunWithError,\n\trunWithoutHandler,\n\trunWithQueueConsumer,\n\trunWithTicks,\n} from \"./actors/lifecycle/run.ts\";\nimport { scheduled } from \"./actors/lifecycle/scheduled.ts\";\nimport {\n\tsleep,\n\tsleepWithLongRpc,\n\tsleepWithNoSleepOption,\n\tsleepWithRawHttp,\n\tsleepWithRawWebSocket,\n} from \"./actors/lifecycle/sleep.ts\";\n// Queues\nimport { worker } from \"./actors/queue/worker.ts\";\nimport { workerTimeout } from \"./actors/queue/worker-timeout.ts\";\n// State\nimport { onStateChangeActor } from \"./actors/state/actor-onstatechange.ts\";\nimport { kvActor } from \"./actors/state/kv.ts\";\nimport {\n\tlargePayloadActor,\n\tlargePayloadConnActor,\n} from \"./actors/state/large-payloads.ts\";\nimport { metadataActor } from \"./actors/state/metadata.ts\";\nimport { parallelismTest } from \"./actors/state/parallelism-test.ts\";\nimport { sqliteDrizzleActor } from \"./actors/state/sqlite-drizzle/mod.ts\";\nimport { sqliteRawActor } from \"./actors/state/sqlite-raw.ts\";\nimport {\n\tdriverCtxActor,\n\tdynamicVarActor,\n\tnestedVarActor,\n\tstaticVarActor,\n\tuniqueVarActor,\n} from \"./actors/state/vars.ts\";\n// Testing\nimport { growDb } from \"./actors/testing/grow-db.ts\";\nimport { inlineClientActor } from \"./actors/testing/inline-client.ts\";\nimport { loadTestAgent } from \"./actors/testing/load-test-agent.ts\";\nimport { loadTestAgent2 } from \"./actors/testing/load-test-agent-2.ts\";\nimport { mockAgenticLoop } from \"./actors/testing/mock-agentic-loop.ts\";\nimport { rawSqliteFuzzer } from \"./actors/testing/raw-sqlite-fuzzer.ts\";\nimport { sigtermSleepProbe } from \"./actors/testing/sigterm-sleep-probe.ts\";\nimport { sleepCloseFuzz } from \"./actors/testing/sleep-close-fuzz.ts\";\nimport { slowReconnectActor } from \"./actors/testing/slow-reconnect-actor.ts\";\nimport { sqliteColdStartBench } from \"./actors/testing/sqlite-cold-start-bench.ts\";\nimport { sqliteMemoryPressure } from \"./actors/testing/sqlite-memory-pressure.ts\";\nimport { sqliteRealworldBench } from \"./actors/testing/sqlite-realworld-bench.ts\";\nimport { testCounter } from \"./actors/testing/test-counter.ts\";\nimport { testCounterSqlite } from \"./actors/testing/test-counter-sqlite.ts\";\nimport { testSqliteBench } from \"./actors/testing/test-sqlite-bench.ts\";\nimport { testSqliteLoad } from \"./actors/testing/test-sqlite-load.ts\";\nimport { approval } from \"./actors/workflow/approval.ts\";\nimport { batch } from \"./actors/workflow/batch.ts\";\nimport { dashboard } from \"./actors/workflow/dashboard.ts\";\nimport {\n\tworkflowHistoryFailed,\n\tworkflowHistoryFull,\n\tworkflowHistoryInProgress,\n\tworkflowHistoryJoin,\n\tworkflowHistoryLoop,\n\tworkflowHistoryRace,\n\tworkflowHistoryRetrying,\n\tworkflowHistorySimple,\n} from \"./actors/workflow/history-examples.ts\";\nimport { order } from \"./actors/workflow/order.ts\";\nimport { payment } from \"./actors/workflow/payment.ts\";\nimport { race } from \"./actors/workflow/race.ts\";\nimport { timer } from \"./actors/workflow/timer.ts\";\n// Workflows\nimport {\n\tworkflowCounterActor,\n\tworkflowQueueActor,\n\tworkflowQueueTimeoutActor,\n\tworkflowSleepActor,\n} from \"./actors/workflow/workflow-fixtures.ts\";\nimport { resolveMode } from \"./mode.ts\";\n\nfunction numberFromEnv(name: string, fallback: number): number {\n\tconst value = process.env[name];\n\tif (value === undefined || value === \"\") return fallback;\n\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed)) {\n\t\tthrow new Error(`${name} must be a finite number`);\n\t}\n\n\treturn parsed;\n}\n\nfunction serverlessPoolConfig() {\n\t// Only the local serverless mode self-registers its pool with the engine.\n\t// In the deployed `serverless` mode the pool is configured externally on\n\t// the engine cluster, and the `serverful` mode uses a long-lived runner\n\t// connection rather than a serverless pool.\n\tif (resolveMode() !== \"serverless-local\") return undefined;\n\n\tconst url =\n\t\tprocess.env.RIVET_SERVERLESS_URL ??\n\t\tprocess.env.KITCHEN_SINK_SERVERLESS_URL ??\n\t\t\"http://127.0.0.1:3000/api/rivet\";\n\n\treturn {\n\t\tname: process.env.RIVET_POOL,\n\t\turl,\n\t\trequestLifespan: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_REQUEST_LIFESPAN\",\n\t\t\t15 * 60,\n\t\t),\n\t\tdrainGracePeriod: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_DRAIN_GRACE_PERIOD\",\n\t\t\t15 * 60,\n\t\t),\n\t\tmetadataPollInterval: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_METADATA_POLL_INTERVAL_MS\",\n\t\t\t1000,\n\t\t),\n\t\tmetadata: {\n\t\t\tsource: \"kitchen-sink\",\n\t\t\tsmoke: \"raw-websocket-serverless\",\n\t\t},\n\t};\n}\n\nexport const registry = setup({\n\tsqlite:\n\t\tprocess.env.RIVET_KITCHEN_SINK_REMOTE_SQLITE === \"1\"\n\t\t\t? { backend: \"remote\" }\n\t\t\t: undefined,\n\tconfigurePool: serverlessPoolConfig(),\n\tserverless: {\n\t\tpublicToken:\n\t\t\tprocess.env.RIVET_PUBLIC_TOKEN ?? process.env.RIVET_TOKEN ?? \"dev\",\n\t\tmaxStartPayloadBytes: numberFromEnv(\n\t\t\t\"RIVET_SERVERLESS_MAX_START_PAYLOAD_BYTES\",\n\t\t\t16 * 1024 * 1024,\n\t\t),\n\t},\n\tuse: {\n\t\t// Overview + state basics\n\t\tcounter,\n\t\tcounterConn,\n\t\tcounterWithParams,\n\t\tcounterWithLifecycle,\n\t\tpingPongCounter,\n\t\t// Core API\n\t\tinputActor,\n\t\tsyncActionActor,\n\t\tasyncActionActor,\n\t\tpromiseActor,\n\t\tshortTimeoutActor,\n\t\tlongTimeoutActor,\n\t\tdefaultTimeoutActor,\n\t\tsyncTimeoutActor,\n\t\tcustomTimeoutActor,\n\t\terrorHandlingActor,\n\t\t// State and storage\n\t\tonStateChangeActor,\n\t\tmetadataActor,\n\t\tstaticVarActor,\n\t\tnestedVarActor,\n\t\tdynamicVarActor,\n\t\tuniqueVarActor,\n\t\tdriverCtxActor,\n\t\tkvActor,\n\t\tlargePayloadActor,\n\t\tlargePayloadConnActor,\n\t\tsqliteRawActor,\n\t\tsqliteDrizzleActor,\n\t\tparallelismTest,\n\t\t// Realtime and connections\n\t\tconnStateActor,\n\t\trejectConnectionActor,\n\t\trequestAccessActor,\n\t\t// HTTP and WebSocket\n\t\trawHttpActor,\n\t\trawHttpNoHandlerActor,\n\t\trawHttpVoidReturnActor,\n\t\trawHttpHonoActor,\n\t\trawHttpRequestPropertiesActor,\n\t\trawWebSocketActor,\n\t\trawWebSocketBinaryActor,\n\t\trawFetchCounter,\n\t\trawWebSocketChatRoom,\n\t\trawWebSocketServerlessSmoke,\n\t\ttunnelStress,\n\t\t// Lifecycle and scheduling\n\t\trunWithTicks,\n\t\trunWithQueueConsumer,\n\t\trunWithEarlyExit,\n\t\trunWithError,\n\t\trunWithoutHandler,\n\t\tsleep,\n\t\tsleepWithLongRpc,\n\t\tsleepWithNoSleepOption,\n\t\tsleepWithRawHttp,\n\t\tsleepWithRawWebSocket,\n\t\tscheduled,\n\t\tdestroyActor,\n\t\tdestroyObserver,\n\t\thibernationActor,\n\t\t// Queues\n\t\tworker,\n\t\tworkerTimeout,\n\t\t// Workflows\n\t\ttimer,\n\t\torder,\n\t\tbatch,\n\t\tapproval,\n\t\tdashboard,\n\t\trace,\n\t\tpayment,\n\t\tworkflowHistorySimple,\n\t\tworkflowHistoryLoop,\n\t\tworkflowHistoryJoin,\n\t\tworkflowHistoryRace,\n\t\tworkflowHistoryFull,\n\t\tworkflowHistoryInProgress,\n\t\tworkflowHistoryRetrying,\n\t\tworkflowHistoryFailed,\n\t\tworkflowCounterActor,\n\t\tworkflowQueueActor,\n\t\tworkflowSleepActor,\n\t\tworkflowQueueTimeoutActor,\n\t\t// Inter-actor\n\t\tinventory,\n\t\tcheckout,\n\t\t// Testing fixtures\n\t\tinlineClientActor,\n\t\ttestCounter,\n\t\ttestCounterSqlite,\n\t\ttestSqliteLoad,\n\t\ttestSqliteBench,\n\t\tsqliteColdStartBench,\n\t\tsqliteRealworldBench,\n\t\trawSqliteFuzzer,\n\t\tsqliteMemoryPressure,\n\t\tgrowDb,\n\t\tmockAgenticLoop,\n\t\tsleepCloseFuzz,\n\t\tloadTestAgent,\n\t\tloadTestAgent2,\n\t\tsigtermSleepProbe,\n\t\tslowReconnectActor,\n\t\t// AI\n\t\taiAgent,\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport interface State {\n\tinitialInput?: unknown;\n\tonCreateInput?: unknown;\n}\n\n// Test actor that can capture input during creation\nexport const inputActor = actor({\n\tcreateState: (_c, input): State => {\n\t\treturn {\n\t\t\tinitialInput: input,\n\t\t\tonCreateInput: undefined,\n\t\t};\n\t},\n\n\tonCreate: (c, input) => {\n\t\tc.state.onCreateInput = input;\n\t},\n\n\tactions: {\n\t\tgetInputs: (c) => {\n\t\t\treturn {\n\t\t\t\tinitialInput: c.state.initialInput,\n\t\t\t\tonCreateInput: c.state.onCreateInput,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Short timeout actor\nexport const shortTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 50, // 50ms timeout\n\t},\n\tactions: {\n\t\tquickAction: async (_c) => {\n\t\t\treturn \"quick response\";\n\t\t},\n\t\tslowAction: async (_c) => {\n\t\t\t// This action should timeout\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\treturn \"slow response\";\n\t\t},\n\t},\n});\n\n// Long timeout actor\nexport const longTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 200, // 200ms timeout\n\t},\n\tactions: {\n\t\tdelayedAction: async (_c) => {\n\t\t\t// This action should complete within timeout\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\treturn \"delayed response\";\n\t\t},\n\t},\n});\n\n// Default timeout actor\nexport const defaultTimeoutActor = actor({\n\tstate: { value: 0 },\n\tactions: {\n\t\tnormalAction: async (_c) => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t\treturn \"normal response\";\n\t\t},\n\t},\n});\n\n// Sync actor (timeout shouldn't apply)\nexport const syncTimeoutActor = actor({\n\tstate: { value: 0 },\n\toptions: {\n\t\tactionTimeout: 50, // 50ms timeout\n\t},\n\tactions: {\n\t\tsyncAction: (_c) => {\n\t\t\treturn \"sync response\";\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\n// Actor with synchronous actions\nexport const syncActionActor = actor({\n\tstate: { value: 0 },\n\tactions: {\n\t\t// Simple synchronous action that returns a value directly\n\t\tincrement: (c, amount = 1) => {\n\t\t\tc.state.value += amount;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Synchronous action that returns an object\n\t\tgetInfo: (c) => {\n\t\t\treturn {\n\t\t\t\tcurrentValue: c.state.value,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t},\n\t\t// Synchronous action with no return value (void)\n\t\treset: (c) => {\n\t\t\tc.state.value = 0;\n\t\t},\n\t},\n});\n\n// Actor with asynchronous actions\nexport const asyncActionActor = actor({\n\tstate: { value: 0, data: null as any },\n\tactions: {\n\t\t// Async action with a delay\n\t\tdelayedIncrement: async (c, amount = 1) => {\n\t\t\tawait Promise.resolve();\n\t\t\tc.state.value += amount;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Async action that simulates an API call\n\t\tfetchData: async (c, id: string) => {\n\t\t\tawait Promise.resolve();\n\n\t\t\t// Simulate response data\n\t\t\tconst data = { id, timestamp: Date.now() };\n\t\t\tc.state.data = data;\n\t\t\treturn data;\n\t\t},\n\t\t// Async action with error handling\n\t\tasyncWithError: async (_c, shouldError: boolean) => {\n\t\t\tawait Promise.resolve();\n\n\t\t\tif (shouldError) {\n\t\t\t\tthrow new UserError(\"Intentional error\");\n\t\t\t}\n\n\t\t\treturn \"Success\";\n\t\t},\n\t},\n});\n\n// Actor with promise actions\nexport const promiseActor = actor({\n\tstate: { results: [] as string[] },\n\tactions: {\n\t\t// Action that returns a resolved promise\n\t\tresolvedPromise: (_c) => {\n\t\t\treturn Promise.resolve(\"resolved value\");\n\t\t},\n\t\t// Action that returns a promise that resolves after a delay\n\t\tdelayedPromise: (c): Promise => {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tc.state.results.push(\"delayed\");\n\t\t\t\tresolve(\"delayed value\");\n\t\t\t});\n\t\t},\n\t\t// Action that returns a rejected promise\n\t\trejectedPromise: (_c) => {\n\t\t\treturn Promise.reject(new UserError(\"promised rejection\"));\n\t\t},\n\t\t// Action to check the collected results\n\t\tgetResults: (c) => {\n\t\t\treturn c.state.results;\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\nexport const errorHandlingActor = actor({\n\tstate: {\n\t\terrorLog: [] as string[],\n\t},\n\tactions: {\n\t\t// Action that throws a UserError with just a message\n\t\tthrowSimpleError: () => {\n\t\t\tthrow new UserError(\"Simple error message\");\n\t\t},\n\n\t\t// Action that throws a UserError with code and metadata\n\t\tthrowDetailedError: () => {\n\t\t\tthrow new UserError(\"Detailed error message\", {\n\t\t\t\tcode: \"detailed_error\",\n\t\t\t\tmetadata: {\n\t\t\t\t\treason: \"test\",\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\n\t\t// Action that throws an internal error\n\t\tthrowInternalError: () => {\n\t\t\tthrow new Error(\"This is an internal error\");\n\t\t},\n\n\t\t// Action that returns successfully\n\t\tsuccessfulAction: () => {\n\t\t\treturn \"success\";\n\t\t},\n\n\t\t// Action that times out (simulated with a long delay)\n\t\ttimeoutAction: async (_c) => {\n\t\t\t// This action should time out if the timeout is configured\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tresolve(\"This should not be reached if timeout works\");\n\t\t\t\t}, 10000); // 10 seconds\n\t\t\t});\n\t\t},\n\n\t\t// Action with configurable delay to test timeout edge cases\n\t\tdelayedAction: async (_c, delayMs: number) => {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tresolve(`Completed after ${delayMs}ms`);\n\t\t\t\t}, delayMs);\n\t\t\t});\n\t\t},\n\n\t\t// Log an error for inspection\n\t\tlogError: (c, error: string) => {\n\t\t\tc.state.errorLog.push(error);\n\t\t\treturn c.state.errorLog;\n\t\t},\n\n\t\t// Get the error log\n\t\tgetErrorLog: (c) => {\n\t\t\treturn c.state.errorLog;\n\t\t},\n\n\t\t// Clear the error log\n\t\tclearErrorLog: (c) => {\n\t\t\tc.state.errorLog = [];\n\t\t\treturn true;\n\t\t},\n\t},\n\toptions: {\n\t\tactionTimeout: 500, // 500ms timeout for actions\n\t},\n});\n\n// Actor with custom timeout\nexport const customTimeoutActor = actor({\n\tstate: {},\n\tactions: {\n\t\tquickAction: async () => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\t\treturn \"Quick action completed\";\n\t\t},\n\t\tslowAction: async () => {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 300));\n\t\t\treturn \"Slow action completed\";\n\t\t},\n\t},\n\toptions: {\n\t\tactionTimeout: 200, // 200ms timeout\n\t},\n});\n","import { openai } from \"@ai-sdk/openai\";\nimport { generateText, tool } from \"ai\";\nimport { actor, event } from \"rivetkit\";\nimport { z } from \"zod\";\nimport { getWeather } from \"./my-tools.ts\";\nimport type { Message } from \"./types.ts\";\n\nexport const aiAgent = actor({\n\t// Persistent state that survives restarts: https://rivet.dev/docs/actors/state\n\tstate: {\n\t\tmessages: [] as Message[],\n\t},\n\tevents: {\n\t\tmessageReceived: event(),\n\t},\n\n\tactions: {\n\t\t// Callable functions from clients: https://rivet.dev/docs/actors/actions\n\t\tgetMessages: (c) => c.state.messages,\n\n\t\tsendMessage: async (c, userMessage: string) => {\n\t\t\tconst userMsg: Message = {\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: userMessage,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\t// State changes are automatically persisted\n\t\t\tc.state.messages.push(userMsg);\n\n\t\t\tconst { text } = await generateText({\n\t\t\t\tmodel: openai(\"gpt-4o-mini\"),\n\t\t\t\tprompt: userMessage,\n\t\t\t\tmessages: c.state.messages,\n\t\t\t\ttools: {\n\t\t\t\t\tweather: tool({\n\t\t\t\t\t\tdescription: \"Get the weather in a location\",\n\t\t\t\t\t\tparameters: z.object({\n\t\t\t\t\t\t\tlocation: z\n\t\t\t\t\t\t\t\t.string()\n\t\t\t\t\t\t\t\t.describe(\n\t\t\t\t\t\t\t\t\t\"The location to get the weather for\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}),\n\t\t\t\t\t\texecute: async ({ location }) => {\n\t\t\t\t\t\t\treturn await getWeather(location);\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst assistantMsg: Message = {\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: text,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tc.state.messages.push(assistantMsg);\n\n\t\t\t// Send events to all connected clients: https://rivet.dev/docs/actors/events\n\t\t\tc.broadcast(\"messageReceived\", assistantMsg);\n\n\t\t\treturn assistantMsg;\n\t\t},\n\t},\n});\n","export async function getWeather(location: string) {\n\t// Mock weather API response\n\treturn {\n\t\tlocation,\n\t\ttemperature: Math.floor(Math.random() * 30) + 10,\n\t\tcondition: [\"sunny\", \"cloudy\", \"rainy\", \"snowy\"][\n\t\t\tMath.floor(Math.random() * 4)\n\t\t],\n\t\thumidity: Math.floor(Math.random() * 50) + 30,\n\t};\n}\n","import { actor, event } from \"rivetkit\";\n\nexport type ConnState = {\n\tusername: string;\n\trole: string;\n\tcounter: number;\n\tcreatedAt: number;\n\tnoCount: boolean;\n};\n\nexport const connStateActor = actor({\n\tstate: {\n\t\tsharedCounter: 0,\n\t\tdisconnectionCount: 0,\n\t},\n\tevents: {\n\t\tuserConnected: event<{ id: string; username: string; role: string }>(),\n\t\tuserDisconnected: event<{ id: string }>(),\n\t\tdirectMessage: event<{ from: string; message: string }>(),\n\t},\n\t// Define connection state\n\tcreateConnState: (\n\t\t_c,\n\t\tparams: { username?: string; role?: string; noCount?: boolean },\n\t): ConnState => {\n\t\treturn {\n\t\t\tusername: params?.username || \"anonymous\",\n\t\t\trole: params?.role || \"user\",\n\t\t\tcounter: 0,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tnoCount: params?.noCount ?? false,\n\t\t};\n\t},\n\t// Lifecycle hook when a connection is established\n\tonConnect: (c, conn) => {\n\t\t// Broadcast event about the new connection\n\t\tc.broadcast(\"userConnected\", {\n\t\t\tid: conn.id,\n\t\t\tusername: \"anonymous\",\n\t\t\trole: \"user\",\n\t\t});\n\t},\n\t// Lifecycle hook when a connection is closed\n\tonDisconnect: (c, conn) => {\n\t\tif (!conn.state?.noCount) {\n\t\t\tc.state.disconnectionCount += 1;\n\t\t\tc.broadcast(\"userDisconnected\", {\n\t\t\t\tid: conn.id,\n\t\t\t});\n\t\t}\n\t},\n\tactions: {\n\t\t// Action to increment the connection's counter\n\t\tincrementConnCounter: (c, amount = 1) => {\n\t\t\tc.conn.state.counter += amount;\n\t\t},\n\n\t\t// Action to increment the shared counter\n\t\tincrementSharedCounter: (c, amount = 1) => {\n\t\t\tc.state.sharedCounter += amount;\n\t\t\treturn c.state.sharedCounter;\n\t\t},\n\n\t\t// Get the connection state\n\t\tgetConnectionState: (c) => {\n\t\t\treturn { id: c.conn.id, ...c.conn.state };\n\t\t},\n\n\t\t// Check all active connections\n\t\tgetConnectionIds: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.filter((c) => !c[1].state?.noCount)\n\t\t\t\t.map((x) => x[0])\n\t\t\t\t.toArray();\n\t\t},\n\n\t\t// Get disconnection count\n\t\tgetDisconnectionCount: (c) => {\n\t\t\treturn c.state.disconnectionCount;\n\t\t},\n\n\t\t// Get all active connection states\n\t\tgetAllConnectionStates: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.map(([id, conn]) => ({ id, ...conn.state }))\n\t\t\t\t.toArray();\n\t\t},\n\n\t\t// Send message to a specific connection with matching ID\n\t\tsendToConnection: (c, targetId: string, message: string) => {\n\t\t\tif (c.conns.has(targetId)) {\n\t\t\t\tc.conns\n\t\t\t\t\t.get(targetId)\n\t\t\t\t\t?.send(\"directMessage\", { from: c.conn.id, message });\n\t\t\t\treturn true;\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\t// Update connection state (simulated for tests)\n\t\tupdateConnection: (\n\t\t\tc,\n\t\t\tupdates: Partial<{ username: string; role: string }>,\n\t\t) => {\n\t\t\tif (updates.username) c.conn.state.username = updates.username;\n\t\t\tif (updates.role) c.conn.state.role = updates.role;\n\t\t\treturn c.conn.state;\n\t\t},\n\t\tdisconnectSelf: (c, reason?: string) => {\n\t\t\tc.conn.disconnect(reason ?? \"test.disconnect\");\n\t\t\treturn true;\n\t\t},\n\t},\n});\n","import { actor, UserError } from \"rivetkit\";\n\nexport const rejectConnectionActor = actor({\n\tonBeforeConnect: async (_c, params: { reject?: boolean }) => {\n\t\tif (params?.reject) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\tthrow new UserError(\"Rejected connection\", {\n\t\t\t\tcode: \"rejected\",\n\t\t\t});\n\t\t}\n\t},\n\tactions: {\n\t\tping: () => \"pong\",\n\t},\n});\n","import { actor, type RivetMessageEvent } from \"rivetkit\";\n\n/**\n * Test fixture to verify request object access in all lifecycle hooks\n */\nexport const requestAccessActor = actor({\n\tstate: {\n\t\t// Track request info from different hooks\n\t\tonBeforeConnectRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tcreateConnStateRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tonRequestRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t\tonWebSocketRequest: {\n\t\t\thasRequest: false,\n\t\t\trequestUrl: null as string | null,\n\t\t\trequestMethod: null as string | null,\n\t\t\trequestHeaders: {} as Record,\n\t\t},\n\t},\n\tcreateConnState: (c, params: { trackRequest?: boolean }) => {\n\t\t// In createConnState, the state isn't available yet.\n\n\t\tlet requestInfo: {\n\t\t\thasRequest: boolean;\n\t\t\trequestUrl: string;\n\t\t\trequestMethod: string;\n\t\t\trequestHeaders: Record;\n\t\t} | null = null;\n\n\t\tif (params?.trackRequest && c.request) {\n\t\t\tconst headers: Record = {};\n\t\t\tc.request.headers.forEach((value, key) => {\n\t\t\t\theaders[key] = value;\n\t\t\t});\n\t\t\trequestInfo = {\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: c.request.url,\n\t\t\t\trequestMethod: c.request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\ttrackRequest: params?.trackRequest || false,\n\t\t\trequestInfo,\n\t\t};\n\t},\n\tonConnect: (c, conn) => {\n\t\t// Copy request info from connection state if it was tracked\n\t\tif (conn.state.requestInfo) {\n\t\t\tc.state.createConnStateRequest = conn.state.requestInfo;\n\t\t}\n\t},\n\tonBeforeConnect: (c, params) => {\n\t\tif (params?.trackRequest) {\n\t\t\tif (c.request) {\n\t\t\t\tc.state.onBeforeConnectRequest.hasRequest = true;\n\t\t\t\tc.state.onBeforeConnectRequest.requestUrl = c.request.url;\n\t\t\t\tc.state.onBeforeConnectRequest.requestMethod = c.request.method;\n\n\t\t\t\t// Store select headers\n\t\t\t\tconst headers: Record = {};\n\t\t\t\tc.request.headers.forEach((value, key) => {\n\t\t\t\t\theaders[key] = value;\n\t\t\t\t});\n\t\t\t\tc.state.onBeforeConnectRequest.requestHeaders = headers;\n\t\t\t} else {\n\t\t\t\t// Track that we tried but request was not available\n\t\t\t\tc.state.onBeforeConnectRequest.hasRequest = false;\n\t\t\t}\n\t\t}\n\t},\n\tonRequest: (c, request) => {\n\t\t// Store request info\n\t\tc.state.onRequestRequest.hasRequest = true;\n\t\tc.state.onRequestRequest.requestUrl = request.url;\n\t\tc.state.onRequestRequest.requestMethod = request.method;\n\n\t\t// Store select headers\n\t\tconst headers: Record = {};\n\t\trequest.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tc.state.onRequestRequest.requestHeaders = headers;\n\n\t\t// Return response with request info\n\t\treturn new Response(\n\t\t\tJSON.stringify({\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: request.url,\n\t\t\t\trequestMethod: request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t}),\n\t\t\t{\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t},\n\t\t);\n\t},\n\tonWebSocket: (c, websocket) => {\n\t\tif (!c.request) throw \"Missing request\";\n\t\t// Store request info\n\t\tc.state.onWebSocketRequest.hasRequest = true;\n\t\tc.state.onWebSocketRequest.requestUrl = c.request.url;\n\t\tc.state.onWebSocketRequest.requestMethod = c.request.method;\n\n\t\t// Store select headers\n\t\tconst headers: Record = {};\n\t\tc.request.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\t\tc.state.onWebSocketRequest.requestHeaders = headers;\n\n\t\t// Send request info on connection\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\thasRequest: true,\n\t\t\t\trequestUrl: c.request.url,\n\t\t\t\trequestMethod: c.request.method,\n\t\t\t\trequestHeaders: headers,\n\t\t\t}),\n\t\t);\n\n\t\t// Echo messages back\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\twebsocket.send(event.data);\n\t\t});\n\t},\n\tactions: {\n\t\tgetRequestInfo: (c) => {\n\t\t\treturn {\n\t\t\t\tonBeforeConnect: c.state.onBeforeConnectRequest,\n\t\t\t\tcreateConnState: c.state.createConnStateRequest,\n\t\t\t\tonRequest: c.state.onRequestRequest,\n\t\t\t\tonWebSocket: c.state.onWebSocketRequest,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const counterWithParams = actor({\n\tstate: { count: 0, initializers: [] as string[] },\n\tevents: {\n\t\tnewCount: event<{ count: number; by: string }>(),\n\t},\n\tcreateConnState: (_c, params: { name?: string }) => {\n\t\treturn {\n\t\t\tname: params.name || \"anonymous\",\n\t\t};\n\t},\n\tonConnect: (c, conn) => {\n\t\t// Record connection name\n\t\tc.state.initializers.push(conn.state.name);\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\tc.broadcast(\"newCount\", {\n\t\t\t\tcount: c.state.count,\n\t\t\t\tby: c.conn.state.name,\n\t\t\t});\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetInitializers: (c) => {\n\t\t\treturn c.state.initializers;\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\tevent,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\n\nexport const counter = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: { count: 0 },\n\tevents: {\n\t\tnewCount: event(),\n\t},\n\tonWebSocket(_c, websocket: UniversalWebSocket) {\n\t\t// Plain echo for the rtt counter-latency harness. Any message in →\n\t\t// the same payload back out. No state mutation, no awaits — keeps the\n\t\t// echo path as close to raw WS RTT as possible.\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tif (websocket.readyState !== 1) return;\n\t\t\twebsocket.send(event.data as string | ArrayBuffer);\n\t\t});\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\tc.broadcast(\"newCount\", c.state.count);\n\t\t\treturn c.state.count;\n\t\t},\n\t\tsetCount: (c, x: number) => {\n\t\t\tc.state.count = x;\n\t\t\tc.broadcast(\"newCount\", x);\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.state.count;\n\t\t},\n\t\tnoop: (_c) => {\n\t\t\treturn { ok: true };\n\t\t},\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const counterConn = actor({\n\tstate: {\n\t\tconnectionCount: 0,\n\t},\n\tevents: {\n\t\tnewCount: event(),\n\t},\n\tconnState: { count: 0 },\n\tonConnect: (c, _conn) => {\n\t\tc.state.connectionCount += 1;\n\t},\n\tonDisconnect: (c, _conn) => {\n\t\t// Note: We can't determine if disconnect was graceful from here\n\t\t// For testing purposes, we'll decrement on all disconnects\n\t\t// In real scenarios, you'd use connection tracking with timeouts\n\t\tc.state.connectionCount -= 1;\n\t},\n\tactions: {\n\t\tincrement: (c, x: number) => {\n\t\t\tc.conn.state.count += x;\n\t\t\tc.broadcast(\"newCount\", c.conn.state.count);\n\t\t},\n\t\tsetCount: (c, x: number) => {\n\t\t\tc.conn.state.count = x;\n\t\t\tc.broadcast(\"newCount\", x);\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\tgetConnectionCount: (c) => {\n\t\t\treturn c.state.connectionCount;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\ntype ConnParams = { trackLifecycle?: boolean } | undefined;\n\nexport const counterWithLifecycle = actor({\n\tstate: {\n\t\tcount: 0,\n\t\tevents: [] as string[],\n\t},\n\tcreateConnState: (_c, _params: ConnParams) => ({\n\t\tjoinTime: Date.now(),\n\t}),\n\tonWake: (c) => {\n\t\tc.state.events.push(\"onWake\");\n\t},\n\tonSleep: async (c) => {\n\t\tc.state.events.push(\"onSleep:start\");\n\t\tawait new Promise((resolve) => setTimeout(resolve, 1000));\n\t\tc.state.events.push(\"onSleep:end\");\n\t},\n\tonBeforeConnect: (c, params: ConnParams) => {\n\t\tif (params?.trackLifecycle) c.state.events.push(\"onBeforeConnect\");\n\t},\n\tonConnect: (c, conn) => {\n\t\tif (conn.params?.trackLifecycle) c.state.events.push(\"onConnect\");\n\t},\n\tonDisconnect: (c, conn) => {\n\t\tif (conn.params?.trackLifecycle) c.state.events.push(\"onDisconnect\");\n\t},\n\tactions: {\n\t\tgetEvents: (c) => {\n\t\t\treturn c.state.events;\n\t\t},\n\t\tincrement: (c, x: number) => {\n\t\t\tc.state.count += x;\n\t\t\treturn c.state.count;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const pingPongCounter = actor({\n\tstate: {\n\t\tpingCount: 0,\n\t},\n\tonWebSocket(ctx, websocket) {\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data !== \"string\") return;\n\n\t\t\tlet parsed: any;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(data);\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (parsed?.type === \"ping\") {\n\t\t\t\tctx.state.pingCount = ctx.state.pingCount + 1;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\tpingCount: ctx.state.pingCount,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t},\n\tactions: {\n\t\tgetPingCount(c) {\n\t\t\treturn c.state.pingCount;\n\t\t},\n\t\tresetPingCount(c) {\n\t\t\tc.state.pingCount = 0;\n\t\t\treturn c.state.pingCount;\n\t\t},\n\t},\n});\n","import { Hono } from \"hono\";\nimport { type ActorContextOf, actor } from \"rivetkit\";\n\nexport const rawFetchCounter = actor({\n\tstate: {\n\t\tcount: 0,\n\t},\n\tcreateVars: () => {\n\t\t// Setup router\n\t\treturn { router: createCounterRouter() };\n\t},\n\tonRequest: (c, request) => {\n\t\treturn c.vars.router.fetch(request, { actor: c });\n\t},\n\tactions: {\n\t\t// ...actions...\n\t},\n});\n\nfunction createCounterRouter(): Hono {\n\tconst app = new Hono<{\n\t\tBindings: { actor: ActorContextOf };\n\t}>();\n\n\tapp.get(\"/count\", (c) => {\n\t\tconst { actor } = c.env;\n\n\t\treturn c.json({\n\t\t\tcount: actor.state.count,\n\t\t});\n\t});\n\n\tapp.post(\"/increment\", (c) => {\n\t\tconst { actor } = c.env;\n\n\t\tactor.state.count++;\n\t\treturn c.json({\n\t\t\tcount: actor.state.count,\n\t\t});\n\t});\n\n\treturn app;\n}\n","import { Hono } from \"hono\";\nimport { actor, type RequestContext } from \"rivetkit\";\n\nexport const rawHttpActor = actor({\n\tstate: {\n\t\trequestCount: 0,\n\t},\n\tonRequest(\n\t\tctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\tconst url = new URL(request.url);\n\t\tconst method = request.method;\n\n\t\t// Track request count\n\t\tctx.state.requestCount++;\n\n\t\t// Handle different endpoints\n\t\tif (url.pathname === \"/api/hello\") {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({ message: \"Hello from actor!\" }),\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (url.pathname === \"/api/echo\" && method === \"POST\") {\n\t\t\treturn new Response(request.body, {\n\t\t\t\theaders: request.headers,\n\t\t\t});\n\t\t}\n\n\t\tif (url.pathname === \"/api/state\") {\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\trequestCount: ctx.state.requestCount,\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (url.pathname === \"/api/headers\") {\n\t\t\tconst headers: Record = {};\n\t\t\trequest.headers.forEach((value, key) => {\n\t\t\t\theaders[key] = value;\n\t\t\t});\n\t\t\treturn new Response(JSON.stringify(headers), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\t// Return 404 for unhandled paths\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t},\n\tactions: {},\n});\n\nexport const rawHttpNoHandlerActor = actor({\n\tactions: {},\n});\n\nexport const rawHttpVoidReturnActor = actor({\n\tonRequest(_ctx, _request) {\n\t\t// Intentionally return void to test error handling\n\t\treturn undefined as any;\n\t},\n\tactions: {},\n});\n\nexport const rawHttpHonoActor = actor({\n\tcreateVars() {\n\t\tconst router = new Hono();\n\n\t\t// Set up routes\n\t\trouter.get(\"/\", (c: any) =>\n\t\t\tc.json({ message: \"Welcome to Hono actor!\" }),\n\t\t);\n\n\t\trouter.get(\"/users\", (c: any) =>\n\t\t\tc.json([\n\t\t\t\t{ id: 1, name: \"Alice\" },\n\t\t\t\t{ id: 2, name: \"Bob\" },\n\t\t\t]),\n\t\t);\n\n\t\trouter.get(\"/users/:id\", (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\treturn c.json({\n\t\t\t\tid: parseInt(id, 10),\n\t\t\t\tname: id === \"1\" ? \"Alice\" : \"Bob\",\n\t\t\t});\n\t\t});\n\n\t\trouter.post(\"/users\", async (c: any) => {\n\t\t\tconst body = await c.req.json();\n\t\t\treturn c.json({ id: 3, ...body }, 201);\n\t\t});\n\n\t\trouter.put(\"/users/:id\", async (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\tconst body = await c.req.json();\n\t\t\treturn c.json({ id: parseInt(id, 10), ...body });\n\t\t});\n\n\t\trouter.delete(\"/users/:id\", (c: any) => {\n\t\t\tconst id = c.req.param(\"id\");\n\t\t\treturn c.json({ message: `User ${id} deleted` });\n\t\t});\n\n\t\t// Return the router as a var\n\t\treturn { router };\n\t},\n\tonRequest(\n\t\tctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\t// Use the Hono router from vars\n\t\treturn ctx.vars.router.fetch(request);\n\t},\n\tactions: {},\n});\n","import { actor, type RequestContext } from \"rivetkit\";\n\nexport const rawHttpRequestPropertiesActor = actor({\n\tactions: {},\n\tonRequest(\n\t\t_ctx: RequestContext,\n\t\trequest: Request,\n\t) {\n\t\t// Extract all relevant Request properties\n\t\tconst url = new URL(request.url);\n\t\tconst method = request.method;\n\n\t\t// Get all headers\n\t\tconst headers: Record = {};\n\t\trequest.headers.forEach((value, key) => {\n\t\t\theaders[key] = value;\n\t\t});\n\n\t\t// Handle body based on content type\n\t\tconst handleBody = async () => {\n\t\t\tif (!request.body) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst contentType = request.headers.get(\"content-type\") || \"\";\n\n\t\t\ttry {\n\t\t\t\tif (contentType.includes(\"application/json\")) {\n\t\t\t\t\tconst text = await request.text();\n\t\t\t\t\treturn text ? JSON.parse(text) : null;\n\t\t\t\t} else {\n\t\t\t\t\t// For non-JSON, return as text\n\t\t\t\t\tconst text = await request.text();\n\t\t\t\t\treturn text || null; // Return null for empty bodies\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\t// If body parsing fails, return null\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\n\t\t// Special handling for HEAD and OPTIONS requests\n\t\tif (method === \"HEAD\") {\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 200,\n\t\t\t});\n\t\t}\n\n\t\tif (method === \"OPTIONS\") {\n\t\t\treturn new Response(null, {\n\t\t\t\tstatus: 204,\n\t\t\t});\n\t\t}\n\n\t\t// Return all request properties as JSON\n\t\treturn handleBody().then((body) => {\n\t\t\tconst responseData = {\n\t\t\t\t// URL properties\n\t\t\t\turl: request.url,\n\t\t\t\tpathname: url.pathname,\n\t\t\t\tsearch: url.search,\n\t\t\t\tsearchParams: Object.fromEntries(url.searchParams.entries()),\n\t\t\t\thash: url.hash,\n\n\t\t\t\t// Method\n\t\t\t\tmethod: request.method,\n\n\t\t\t\t// Headers\n\t\t\t\theaders: headers,\n\n\t\t\t\t// Body\n\t\t\t\tbody,\n\t\t\t\tbodyText:\n\t\t\t\t\ttypeof body === \"string\"\n\t\t\t\t\t\t? body\n\t\t\t\t\t\t: body === null && request.body !== null\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: null,\n\n\t\t\t\t// Additional properties that might be available\n\t\t\t\t// Note: Some properties like cache, credentials, mode, etc.\n\t\t\t\t// might not be available in all environments\n\t\t\t\tcache: request.cache || null,\n\t\t\t\tcredentials: request.credentials || null,\n\t\t\t\tmode: request.mode || null,\n\t\t\t\tredirect: request.redirect || null,\n\t\t\t\treferrer: request.referrer || null,\n\t\t\t};\n\n\t\t\treturn new Response(JSON.stringify(responseData), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t});\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const rawWebSocketActor = actor({\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t},\n\tonWebSocket(ctx, websocket) {\n\t\tctx.state.connectionCount = ctx.state.connectionCount + 1;\n\t\tconsole.log(\n\t\t\t`[ACTOR] New connection, count: ${ctx.state.connectionCount}`,\n\t\t);\n\n\t\t// Send welcome message\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t}),\n\t\t);\n\t\tconsole.log(\"[ACTOR] Sent welcome message\");\n\n\t\t// Echo messages back\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tctx.state.messageCount = ctx.state.messageCount + 1;\n\t\t\tconsole.log(\n\t\t\t\t`[ACTOR] Message received, total count: ${ctx.state.messageCount}, data:`,\n\t\t\t\tevent.data,\n\t\t\t);\n\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\tif (parsed.type === \"ping\") {\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"getStats\") {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`[ACTOR] Sending stats - connections: ${ctx.state.connectionCount}, messages: ${ctx.state.messageCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"stats\",\n\t\t\t\t\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t\t\t\t\t\tmessageCount: ctx.state.messageCount,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"getRequestInfo\") {\n\t\t\t\t\t\t// Send back the request URL info if available\n\t\t\t\t\t\tconst url = ctx.request?.url || \"ws://actor/websocket\";\n\t\t\t\t\t\tconst urlObj = new URL(url);\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"requestInfo\",\n\t\t\t\t\t\t\t\turl: url,\n\t\t\t\t\t\t\t\tpathname: urlObj.pathname,\n\t\t\t\t\t\t\t\tsearch: urlObj.search,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Echo back\n\t\t\t\t\t\twebsocket.send(data);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// If not JSON, just echo it back\n\t\t\t\t\twebsocket.send(data);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Echo binary data\n\t\t\t\twebsocket.send(data);\n\t\t\t}\n\t\t});\n\n\t\t// Handle close\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tctx.state.connectionCount = ctx.state.connectionCount - 1;\n\t\t\tconsole.log(\n\t\t\t\t`[ACTOR] Connection closed, count: ${ctx.state.connectionCount}`,\n\t\t\t);\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(ctx: any) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: ctx.state.connectionCount,\n\t\t\t\tmessageCount: ctx.state.messageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n\nexport const rawWebSocketBinaryActor = actor({\n\tonWebSocket(_ctx, websocket) {\n\t\t// Handle binary data\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (data instanceof ArrayBuffer || data instanceof Uint8Array) {\n\t\t\t\t// Reverse the bytes and send back\n\t\t\t\tconst bytes = new Uint8Array(data);\n\t\t\t\tconst reversed = new Uint8Array(bytes.length);\n\t\t\t\tfor (let i = 0; i < bytes.length; i++) {\n\t\t\t\t\treversed[i] = bytes[bytes.length - 1 - i];\n\t\t\t\t}\n\t\t\t\twebsocket.send(reversed);\n\t\t\t}\n\t\t});\n\t},\n\tactions: {},\n});\n","import { actor } from \"rivetkit\";\n\nexport const rawWebSocketChatRoom = actor({\n\tstate: {\n\t\tmessages: [] as Array<{\n\t\t\tid: string;\n\t\t\ttext: string;\n\t\t\ttimestamp: number;\n\t\t}>,\n\t},\n\tcreateVars: () => {\n\t\treturn {\n\t\t\tsockets: new Set(),\n\t\t};\n\t},\n\tonWebSocket(ctx, socket) {\n\t\t// Add socket to the set\n\t\tctx.vars.sockets.add(socket);\n\n\t\t// Send recent messages to new connection\n\t\tsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"init\",\n\t\t\t\tmessages: ctx.state.messages,\n\t\t\t}),\n\t\t);\n\n\t\t// Handle incoming messages\n\t\tsocket.addEventListener(\"message\", (event: any) => {\n\t\t\ttry {\n\t\t\t\tconst data = JSON.parse(event.data);\n\n\t\t\t\tif (data.type === \"message\" && data.text) {\n\t\t\t\t\tconst message = {\n\t\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\t\ttext: data.text,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t};\n\n\t\t\t\t\t// Add to state\n\t\t\t\t\tctx.state.messages.push(message);\n\t\t\t\t\tctx.saveState({});\n\n\t\t\t\t\t// Keep only last 50 messages\n\t\t\t\t\tif (ctx.state.messages.length > 50) {\n\t\t\t\t\t\tctx.state.messages.shift();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Broadcast to all connected clients\n\t\t\t\t\tconst broadcast = JSON.stringify({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\t...message,\n\t\t\t\t\t});\n\n\t\t\t\t\tfor (const ws of ctx.vars.sockets) {\n\t\t\t\t\t\tif (ws.readyState === 1) {\n\t\t\t\t\t\t\t// OPEN\n\t\t\t\t\t\t\tws.send(broadcast);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(\"Failed to process message:\", e);\n\t\t\t}\n\t\t});\n\n\t\t// Remove socket on close\n\t\tsocket.addEventListener(\"close\", () => {\n\t\t\tctx.vars.sockets.delete(socket);\n\t\t});\n\t},\n\tactions: {},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport const rawWebSocketServerlessSmoke = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tsleepCount: 0,\n\t\ttotalTickCount: 0,\n\t\ttotalMessageCount: 0,\n\t},\n\tasync onSleep(c) {\n\t\tconst delayMs = 10 + Math.floor(Math.random() * 1_991);\n\t\tc.state.sleepCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"raw websocket serverless smoke onSleep delay\",\n\t\t\tdelayMs,\n\t\t\tsleepCount: c.state.sleepCount,\n\t\t});\n\t\tawait sleep(delayMs);\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\t\tlet index = 0;\n\n\t\tconst sendTick = () => {\n\t\t\tif (websocket.readyState !== 1) return;\n\n\t\t\tconst timestamp = Date.now();\n\t\t\tconst message = {\n\t\t\t\ttype: \"tick\",\n\t\t\t\tconnectionId,\n\t\t\t\tindex,\n\t\t\t\ttimestamp,\n\t\t\t\tiso: new Date(timestamp).toISOString(),\n\t\t\t\ttotalTickCount: c.state.totalTickCount,\n\t\t\t};\n\n\t\t\tc.state.totalTickCount += 1;\n\t\t\tindex += 1;\n\t\t\twebsocket.send(JSON.stringify(message));\n\t\t};\n\n\t\tc.log.info({\n\t\t\tmsg: \"raw websocket serverless smoke connected\",\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\tsendTick();\n\t\tconst interval = setInterval(sendTick, 1_000);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.totalMessageCount += 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"raw websocket serverless smoke received message\",\n\t\t\t\tconnectionId,\n\t\t\t\ttotalMessageCount: c.state.totalMessageCount,\n\t\t\t});\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"ack\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\tindex,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\treceived: event.data,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(interval);\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"raw websocket serverless smoke disconnected\",\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\ttotalTickCount: c.state.totalTickCount,\n\t\t\t\ttotalMessageCount: c.state.totalMessageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\n\nexport const tunnelStress = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t\theartbeatCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tconst sendHeartbeat = () => {\n\t\t\tif (websocket.readyState !== 1) return;\n\n\t\t\tc.state.heartbeatCount += 1;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"heartbeat\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\theartbeatCount: c.state.heartbeatCount,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t};\n\n\t\tconst heartbeat = setInterval(sendHeartbeat, 1_000);\n\t\tsendHeartbeat();\n\n\t\twebsocket.addEventListener(\n\t\t\t\"message\",\n\t\t\tasync (event: RivetMessageEvent) => {\n\t\t\t\t// Fast-path ping: echo back without touching KV so the client can measure raw RTT\n\t\t\t\t// without the per-message storage write. Used by the counter-latency client's first\n\t\t\t\t// two probes after WS open.\n\t\t\t\tif (typeof event.data === \"string\") {\n\t\t\t\t\tlet parsed: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsed = JSON.parse(event.data);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tparsed = undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tparsed &&\n\t\t\t\t\t\ttypeof parsed === \"object\" &&\n\t\t\t\t\t\t(parsed as { type?: unknown }).type === \"ping\"\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst id = (parsed as { id?: unknown }).id;\n\t\t\t\t\t\tif (websocket.readyState === 1) {\n\t\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tc.state.messageCount += 1;\n\t\t\t\tawait c.kv.put(\"counter\", String(c.state.messageCount));\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"reply\",\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\treceived: event.data,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t},\n\t\t);\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(heartbeat);\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\theartbeatCount: c.state.heartbeatCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport interface InventoryInput {\n\tinitialStock: number;\n\titemName: string;\n}\n\nexport interface InventoryState {\n\titemName: string;\n\tstock: number;\n\treservations: string[]; // Track which checkouts have reserved items\n}\n\nexport interface CheckoutInput {\n\tcustomerName: string;\n}\n\nexport interface CheckoutItem {\n\titemId: string;\n\titemName: string;\n\tquantity: number;\n}\n\nexport interface CheckoutResult {\n\tsuccess: boolean;\n\tmessage: string;\n\tremainingStock?: number;\n}\n\nexport interface CheckoutState {\n\tcustomerName: string;\n\titems: CheckoutItem[];\n\tcompleted: boolean;\n}\n\n// Inventory actor manages stock for a specific item\nexport const inventory = actor({\n\t// Each item has its own inventory actor instance\n\tcreateState: (_c, input?: InventoryInput): InventoryState => ({\n\t\titemName: input?.itemName ?? \"Widget\",\n\t\tstock: input?.initialStock ?? 100,\n\t\treservations: [],\n\t}),\n\n\tactions: {\n\t\t// Check current stock\n\t\tgetStock: (c) => ({\n\t\t\titemName: c.state.itemName,\n\t\t\tstock: c.state.stock,\n\t\t}),\n\n\t\t// Reserve items for checkout (called by checkout actor)\n\t\treserveItems: (c, checkoutId: string, quantity: number) => {\n\t\t\tif (c.state.stock < quantity) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tmessage: `Insufficient stock. Available: ${c.state.stock}, Requested: ${quantity}`,\n\t\t\t\t\tavailableStock: c.state.stock,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Reserve the items\n\t\t\tc.state.stock -= quantity;\n\t\t\tc.state.reservations.push(checkoutId);\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: `Reserved ${quantity} items for checkout ${checkoutId}`,\n\t\t\t\tremainingStock: c.state.stock,\n\t\t\t};\n\t\t},\n\n\t\t// Release reserved items if checkout is cancelled\n\t\treleaseItems: (c, checkoutId: string, quantity: number) => {\n\t\t\tconst index = c.state.reservations.indexOf(checkoutId);\n\t\t\tif (index > -1) {\n\t\t\t\tc.state.reservations.splice(index, 1);\n\t\t\t\tc.state.stock += quantity;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tremainingStock: c.state.stock,\n\t\t\t};\n\t\t},\n\t},\n});\n\n// Checkout actor manages the checkout process and communicates with inventory\nexport const checkout = actor({\n\tcreateState: (_c, input?: CheckoutInput): CheckoutState => ({\n\t\tcustomerName: input?.customerName ?? \"Guest\",\n\t\titems: [],\n\t\tcompleted: false,\n\t}),\n\n\tactions: {\n\t\t// Add item to checkout and reserve from inventory\n\t\taddItem: async (\n\t\t\tc,\n\t\t\titemId: string,\n\t\t\tquantity: number,\n\t\t): Promise => {\n\t\t\t// Use server-side client to communicate with inventory actor\n\t\t\t// https://rivet.dev/docs/actors/communicating-between-actors\n\t\t\tconst inventoryActor = c.client().inventory.getOrCreate([itemId]);\n\n\t\t\t// Get item details\n\t\t\tconst itemInfo = await inventoryActor.getStock();\n\n\t\t\t// Try to reserve items from inventory\n\t\t\tconst reservation = await inventoryActor.reserveItems(\n\t\t\t\tc.actorId, // Use checkout ID as reservation ID\n\t\t\t\tquantity,\n\t\t\t);\n\n\t\t\tif (!reservation.success) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tmessage: reservation.message,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Add item to checkout\n\t\t\tc.state.items.push({\n\t\t\t\titemId,\n\t\t\t\titemName: itemInfo.itemName,\n\t\t\t\tquantity,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: `Added ${quantity} ${itemInfo.itemName} to checkout`,\n\t\t\t\tremainingStock: reservation.remainingStock,\n\t\t\t};\n\t\t},\n\n\t\t// Get checkout summary\n\t\tgetSummary: (c) => ({\n\t\t\tcustomerName: c.state.customerName,\n\t\t\titems: c.state.items,\n\t\t\tcompleted: c.state.completed,\n\t\t\ttotalItems: c.state.items.reduce(\n\t\t\t\t(sum, item) => sum + item.quantity,\n\t\t\t\t0,\n\t\t\t),\n\t\t}),\n\n\t\t// Complete the checkout\n\t\tcompleteCheckout: (c) => {\n\t\t\tc.state.completed = true;\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Checkout completed successfully\",\n\t\t\t};\n\t\t},\n\n\t\t// Cancel checkout and release all reservations\n\t\tcancelCheckout: async (c) => {\n\t\t\t// Release all reserved items\n\t\t\tfor (const item of c.state.items) {\n\t\t\t\tconst inventoryActor = c\n\t\t\t\t\t.client()\n\t\t\t\t\t.inventory.getOrCreate([item.itemId]);\n\t\t\t\tawait inventoryActor.releaseItems(c.actorId, item.quantity);\n\t\t\t}\n\n\t\t\t// Clear the cart\n\t\t\tc.state.items = [];\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tmessage: \"Checkout cancelled, items returned to inventory\",\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\nexport const destroyObserver = actor({\n\tstate: { destroyedActors: [] as string[] },\n\tactions: {\n\t\tnotifyDestroyed: (c, actorKey: string) => {\n\t\t\tc.state.destroyedActors.push(actorKey);\n\t\t},\n\t\twasDestroyed: (c, actorKey: string) => {\n\t\t\treturn c.state.destroyedActors.includes(actorKey);\n\t\t},\n\t\treset: (c) => {\n\t\t\tc.state.destroyedActors = [];\n\t\t},\n\t},\n});\n\nexport const destroyActor = actor({\n\tstate: { value: 0, key: \"\" },\n\tonWake: (c) => {\n\t\t// Store the actor key so we can reference it in onDestroy\n\t\tc.state.key = c.key.join(\"/\");\n\t},\n\tonDestroy: async (c) => {\n\t\tconst client = c.client();\n\t\tconst observer = client.destroyObserver.getOrCreate([\"observer\"]);\n\t\tawait observer.notifyDestroyed(c.state.key);\n\t},\n\tactions: {\n\t\tsetValue: async (c, newValue: number) => {\n\t\t\tc.state.value = newValue;\n\t\t\tawait c.saveState({ immediate: true });\n\t\t\treturn c.state.value;\n\t\t},\n\t\tgetValue: (c) => {\n\t\t\treturn c.state.value;\n\t\t},\n\t\tdestroy: (c) => {\n\t\t\tc.destroy();\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const HIBERNATION_SLEEP_TIMEOUT = 500;\n\nexport type HibernationConnState = {\n\tcount: number;\n\tconnectCount: number;\n\tdisconnectCount: number;\n};\n\nexport const hibernationActor = actor({\n\tstate: {\n\t\tsleepCount: 0,\n\t\twakeCount: 0,\n\t},\n\tcreateConnState: (_c): HibernationConnState => {\n\t\treturn {\n\t\t\tcount: 0,\n\t\t\tconnectCount: 0,\n\t\t\tdisconnectCount: 0,\n\t\t};\n\t},\n\tonWake: (c) => {\n\t\tc.state.wakeCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonConnect: (_c, conn) => {\n\t\tconn.state.connectCount += 1;\n\t},\n\tonDisconnect: (_c, conn) => {\n\t\tconn.state.disconnectCount += 1;\n\t},\n\tactions: {\n\t\t// Basic RPC that returns a simple value\n\t\tping: (_c) => {\n\t\t\treturn \"pong\";\n\t\t},\n\t\t// Increment the connection's count\n\t\tconnIncrement: (c) => {\n\t\t\tc.conn.state.count += 1;\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\t// Get the connection's count\n\t\tgetConnCount: (c) => {\n\t\t\treturn c.conn.state.count;\n\t\t},\n\t\t// Get the connection's lifecycle counts\n\t\tgetConnLifecycleCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tconnectCount: c.conn.state.connectCount,\n\t\t\t\tdisconnectCount: c.conn.state.disconnectCount,\n\t\t\t};\n\t\t},\n\t\t// Get all connection IDs\n\t\tgetConnectionIds: (c) => {\n\t\t\treturn c.conns\n\t\t\t\t.entries()\n\t\t\t\t.map((x) => x[0])\n\t\t\t\t.toArray();\n\t\t},\n\t\t// Get actor sleep/wake counts\n\t\tgetActorCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t};\n\t\t},\n\t\t// Trigger sleep\n\t\ttriggerSleep: (c) => {\n\t\t\tc.sleep();\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: HIBERNATION_SLEEP_TIMEOUT,\n\t},\n});\n","import { actor, queue } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\nexport const RUN_SLEEP_TIMEOUT = 500;\n\n// Actor that tracks tick counts and respects abort signal\nexport const runWithTicks = actor({\n\tstate: {\n\t\ttickCount: 0,\n\t\tlastTickAt: 0,\n\t\trunStarted: false,\n\t\trunExited: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started\");\n\n\t\twhile (!c.aborted) {\n\t\t\tc.state.tickCount += 1;\n\t\t\tc.state.lastTickAt = Date.now();\n\t\t\tc.log.info({ msg: \"tick\", tickCount: c.state.tickCount });\n\n\t\t\t// Wait 50ms between ticks, or exit early if aborted\n\t\t\tawait new Promise((resolve) => {\n\t\t\t\tconst timeout = setTimeout(resolve, 50);\n\t\t\t\tc.abortSignal.addEventListener(\n\t\t\t\t\t\"abort\",\n\t\t\t\t\t() => {\n\t\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\n\t\tc.state.runExited = true;\n\t\tc.log.info(\"run handler exiting gracefully\");\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\ttickCount: c.state.tickCount,\n\t\t\tlastTickAt: c.state.lastTickAt,\n\t\t\trunStarted: c.state.runStarted,\n\t\t\trunExited: c.state.runExited,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that consumes from a queue in the run handler\nexport const runWithQueueConsumer = actor({\n\tstate: {\n\t\tmessagesReceived: [] as Array<{ name: string; body: unknown }>,\n\t\trunStarted: false,\n\t},\n\tqueues: {\n\t\tmessages: queue(),\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, waiting for messages\");\n\n\t\tfor await (const message of c.queue.iter()) {\n\t\t\tc.log.info({ msg: \"received message\", body: message.body });\n\t\t\tc.state.messagesReceived.push({\n\t\t\t\tname: message.name,\n\t\t\t\tbody: message.body,\n\t\t\t});\n\t\t}\n\n\t\tc.log.info(\"run handler exiting gracefully\");\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\tmessagesReceived: c.state.messagesReceived,\n\t\t\trunStarted: c.state.runStarted,\n\t\t}),\n\t\tsendMessage: async (c, body: unknown) => {\n\t\t\tconst client = c.client();\n\t\t\tconst handle = client.runWithQueueConsumer.getForId(c.actorId);\n\t\t\tawait handle.send(\"messages\", body);\n\t\t\treturn true;\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that exits the run handler after a short delay to test crash behavior\nexport const runWithEarlyExit = actor({\n\tstate: {\n\t\trunStarted: false,\n\t\tdestroyCalled: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, will exit after delay\");\n\t\t// Wait a bit so we can observe the runStarted state before exit\n\t\tawait new Promise((resolve) => setTimeout(resolve, 200));\n\t\tc.log.info(\"run handler exiting early\");\n\t\t// Exit without respecting abort signal\n\t},\n\tonDestroy: (c) => {\n\t\tc.state.destroyCalled = true;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\trunStarted: c.state.runStarted,\n\t\t\tdestroyCalled: c.state.destroyCalled,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor that throws an error in the run handler to test crash behavior\nexport const runWithError = actor({\n\tstate: {\n\t\trunStarted: false,\n\t\tdestroyCalled: false,\n\t},\n\trun: async (c) => {\n\t\tc.state.runStarted = true;\n\t\tc.log.info(\"run handler started, will throw error\");\n\t\tawait new Promise((resolve) => setTimeout(resolve, 50));\n\t\tthrow new Error(\"intentional error in run handler\");\n\t},\n\tonDestroy: (c) => {\n\t\tc.state.destroyCalled = true;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\trunStarted: c.state.runStarted,\n\t\t\tdestroyCalled: c.state.destroyCalled,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n\n// Actor without a run handler for comparison\nexport const runWithoutHandler = actor({\n\tstate: {\n\t\twakeCount: 0,\n\t},\n\tonWake: (c) => {\n\t\tc.state.wakeCount += 1;\n\t},\n\tactions: {\n\t\tgetState: (c) => ({\n\t\t\twakeCount: c.state.wakeCount,\n\t\t}),\n\t},\n\toptions: {\n\t\tsleepTimeout: RUN_SLEEP_TIMEOUT,\n\t},\n});\n","import { actor, event } from \"rivetkit\";\n\nexport const scheduled = actor({\n\tstate: {\n\t\tlastRun: 0,\n\t\tscheduledCount: 0,\n\t\ttaskHistory: [] as string[],\n\t},\n\tevents: {\n\t\tscheduled: event<{ time: number; count: number }>(),\n\t\tscheduledWithId: event<{\n\t\t\ttaskId: string;\n\t\t\ttime: number;\n\t\t\tcount: number;\n\t\t}>(),\n\t},\n\tactions: {\n\t\t// Schedule using 'at' with specific timestamp\n\t\tscheduleTaskAt: (c, timestamp: number) => {\n\t\t\tc.schedule.at(timestamp, \"onScheduledTask\");\n\t\t\treturn timestamp;\n\t\t},\n\n\t\t// Schedule using 'after' with delay\n\t\tscheduleTaskAfter: (c, delayMs: number) => {\n\t\t\tc.schedule.after(delayMs, \"onScheduledTask\");\n\t\t\treturn Date.now() + delayMs;\n\t\t},\n\n\t\t// Schedule with a task ID for ordering tests\n\t\tscheduleTaskAfterWithId: (c, taskId: string, delayMs: number) => {\n\t\t\tc.schedule.after(delayMs, \"onScheduledTaskWithId\", taskId);\n\t\t\treturn { taskId, scheduledFor: Date.now() + delayMs };\n\t\t},\n\n\t\t// Original method for backward compatibility\n\t\tscheduleTask: (c, delayMs: number) => {\n\t\t\tconst timestamp = Date.now() + delayMs;\n\t\t\tc.schedule.at(timestamp, \"onScheduledTask\");\n\t\t\treturn timestamp;\n\t\t},\n\n\t\t// Getters for state\n\t\tgetLastRun: (c) => {\n\t\t\treturn c.state.lastRun;\n\t\t},\n\n\t\tgetScheduledCount: (c) => {\n\t\t\treturn c.state.scheduledCount;\n\t\t},\n\n\t\tgetTaskHistory: (c) => {\n\t\t\treturn c.state.taskHistory;\n\t\t},\n\n\t\tclearHistory: (c) => {\n\t\t\tc.state.taskHistory = [];\n\t\t\tc.state.scheduledCount = 0;\n\t\t\tc.state.lastRun = 0;\n\t\t\treturn true;\n\t\t},\n\n\t\t// Scheduled task handlers\n\t\tonScheduledTask: (c) => {\n\t\t\tc.state.lastRun = Date.now();\n\t\t\tc.state.scheduledCount++;\n\t\t\tc.broadcast(\"scheduled\", {\n\t\t\t\ttime: c.state.lastRun,\n\t\t\t\tcount: c.state.scheduledCount,\n\t\t\t});\n\t\t},\n\n\t\tonScheduledTaskWithId: (c, taskId: string) => {\n\t\t\tc.state.lastRun = Date.now();\n\t\t\tc.state.scheduledCount++;\n\t\t\tc.state.taskHistory.push(taskId);\n\t\t\tc.broadcast(\"scheduledWithId\", {\n\t\t\t\ttaskId,\n\t\t\t\ttime: c.state.lastRun,\n\t\t\t\tcount: c.state.scheduledCount,\n\t\t\t});\n\t\t},\n\t},\n});\n","import { actor, event, type UniversalWebSocket } from \"rivetkit\";\nimport { promiseWithResolvers } from \"rivetkit/utils\";\n\nexport const SLEEP_TIMEOUT = 1000;\n\nexport const sleep = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\ttriggerSleep: (c) => {\n\t\t\tc.sleep();\n\t\t},\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t\tsetAlarm: async (c, duration: number) => {\n\t\t\tawait c.schedule.after(duration, \"onAlarm\");\n\t\t},\n\t\tonAlarm: (c) => {\n\t\t\tc.log.info(\"alarm called\");\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithLongRpc = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tcreateVars: () =>\n\t\t({}) as { longRunningResolve: PromiseWithResolvers },\n\tevents: {\n\t\twaiting: event<[]>(),\n\t},\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t\tlongRunningRpc: async (c) => {\n\t\t\tc.log.info(\"starting long running rpc\");\n\t\t\tc.vars.longRunningResolve = promiseWithResolvers(() => {});\n\t\t\tc.broadcast(\"waiting\");\n\t\t\tawait c.vars.longRunningResolve.promise;\n\t\t\tc.log.info(\"finished long running rpc\");\n\t\t},\n\t\tfinishLongRunningRpc: (c) => c.vars.longRunningResolve?.resolve(),\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithRawHttp = actor({\n\tstate: { startCount: 0, sleepCount: 0, requestCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonRequest: async (c, request) => {\n\t\tc.state.requestCount += 1;\n\t\tconst url = new URL(request.url);\n\n\t\tif (url.pathname === \"/long-request\") {\n\t\t\tconst duration = parseInt(\n\t\t\t\turl.searchParams.get(\"duration\") || \"1000\",\n\t\t\t\t10,\n\t\t\t);\n\t\t\tc.log.info({ msg: \"starting long fetch request\", duration });\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, duration));\n\t\t\tc.log.info(\"finished long fetch request\");\n\t\t\treturn new Response(JSON.stringify({ completed: true }), {\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\trequestCount: c.state.requestCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithRawWebSocket = actor({\n\tstate: { startCount: 0, sleepCount: 0, connectionCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tc.state.connectionCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"websocket connected\",\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"connected\",\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\twebsocket.addEventListener(\"message\", (event: any) => {\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\t\tif (parsed.type === \"getCounts\") {\n\t\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\t\ttype: \"counts\",\n\t\t\t\t\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\t\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\t\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (parsed.type === \"keepAlive\") {\n\t\t\t\t\t\t// Just acknowledge to keep connection alive\n\t\t\t\t\t\twebsocket.send(JSON.stringify({ type: \"ack\" }));\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Echo non-JSON messages\n\t\t\t\t\twebsocket.send(data);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"websocket disconnected\",\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t},\n});\n\nexport const sleepWithNoSleepOption = actor({\n\tstate: { startCount: 0, sleepCount: 0 },\n\tonWake: (c) => {\n\t\tc.state.startCount += 1;\n\t},\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t},\n\tactions: {\n\t\tgetCounts: (c) => {\n\t\t\treturn {\n\t\t\t\tstartCount: c.state.startCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: SLEEP_TIMEOUT,\n\t\tnoSleep: true,\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\n\nexport interface WorkerJob {\n\tid: string;\n\tpayload: string;\n}\n\nexport interface WorkerState {\n\tstatus: \"idle\" | \"running\";\n\tprocessed: number;\n\tlastJob: WorkerJob | null;\n}\n\nexport const worker = actor({\n\tstate: {\n\t\tstatus: \"idle\" as \"idle\" | \"running\",\n\t\tprocessed: 0,\n\t\tlastJob: null as WorkerJob | null,\n\t},\n\tevents: {\n\t\tstatusChanged: event<{\n\t\t\tstatus: \"idle\" | \"running\";\n\t\t\tprocessed: number;\n\t\t}>(),\n\t\tjobProcessed: event<{ processed: number; job: WorkerJob }>(),\n\t},\n\tqueues: {\n\t\tjobs: queue(),\n\t},\n\tasync run(c) {\n\t\tc.state.status = \"running\";\n\t\tc.broadcast(\"statusChanged\", {\n\t\t\tstatus: c.state.status,\n\t\t\tprocessed: c.state.processed,\n\t\t});\n\n\t\tfor await (const job of c.queue.iter()) {\n\t\t\tc.state.processed += 1;\n\t\t\tc.state.lastJob = job.body;\n\t\t\tc.broadcast(\"jobProcessed\", {\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tjob: job.body,\n\t\t\t});\n\t\t}\n\n\t\tc.state.status = \"idle\";\n\t},\n\tactions: {\n\t\tgetState(c): WorkerState {\n\t\t\treturn {\n\t\t\t\tstatus: c.state.status,\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tlastJob: c.state.lastJob,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\n\nconst DEFAULT_TIMEOUT_MS = 2_000;\n\nexport interface WorkerTimeoutJob {\n\tid: string;\n\tpayload: string;\n}\n\nexport interface WorkerTimeoutState {\n\tstatus: \"idle\" | \"running\";\n\tprocessed: number;\n\tticks: number;\n\tlastTickAt: number | null;\n\tlastJob: WorkerTimeoutJob | null;\n\ttimeoutMs: number;\n}\n\nexport const workerTimeout = actor({\n\tstate: {\n\t\tstatus: \"idle\" as \"idle\" | \"running\",\n\t\tprocessed: 0,\n\t\tticks: 0,\n\t\tlastTickAt: null as number | null,\n\t\tlastJob: null as WorkerTimeoutJob | null,\n\t\ttimeoutMs: DEFAULT_TIMEOUT_MS,\n\t},\n\tevents: {\n\t\ttick: event<{ ticks: number; at: number }>(),\n\t\tjobProcessed: event<{ processed: number; job: WorkerTimeoutJob }>(),\n\t},\n\tqueues: {\n\t\tjobs: queue(),\n\t},\n\trun: async (c) => {\n\t\tc.state.status = \"running\";\n\n\t\twhile (!c.aborted) {\n\t\t\tconst message = await c.queue.next({\n\t\t\t\tnames: [\"jobs\"],\n\t\t\t\ttimeout: c.state.timeoutMs,\n\t\t\t});\n\n\t\t\tif (!message) {\n\t\t\t\tconst at = Date.now();\n\t\t\t\tc.state.ticks += 1;\n\t\t\t\tc.state.lastTickAt = at;\n\t\t\t\tc.broadcast(\"tick\", {\n\t\t\t\t\tticks: c.state.ticks,\n\t\t\t\t\tat,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tc.state.processed += 1;\n\t\t\tc.state.lastJob = message.body;\n\t\t\tc.broadcast(\"jobProcessed\", {\n\t\t\t\tprocessed: c.state.processed,\n\t\t\t\tjob: message.body,\n\t\t\t});\n\t\t}\n\n\t\tc.state.status = \"idle\";\n\t},\n\tactions: {\n\t\tenqueueJob: async (c, payload: string) => {\n\t\t\tconst job = {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tpayload,\n\t\t\t};\n\t\t\tawait c.queue.send(\"jobs\", job);\n\t\t\treturn job;\n\t\t},\n\t\tsetTimeoutMs: (c, timeoutMs: number) => {\n\t\t\tc.state.timeoutMs = Math.max(100, Math.floor(timeoutMs));\n\t\t\treturn c.state.timeoutMs;\n\t\t},\n\t\tgetState: (c): WorkerTimeoutState => c.state,\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const onStateChangeActor = actor({\n\tstate: {\n\t\tvalue: 0,\n\t},\n\tvars: {\n\t\tchangeCount: 0,\n\t},\n\tactions: {\n\t\t// Action that modifies state - should trigger onStateChange\n\t\tsetValue: (c, newValue: number) => {\n\t\t\tc.state.value = newValue;\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that modifies state multiple times - should trigger onStateChange for each change\n\t\tincrementMultiple: (c, times: number) => {\n\t\t\tfor (let i = 0; i < times; i++) {\n\t\t\t\tc.state.value++;\n\t\t\t}\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that doesn't modify state - should NOT trigger onStateChange\n\t\tgetValue: (c) => {\n\t\t\treturn c.state.value;\n\t\t},\n\t\t// Action that reads and returns without modifying - should NOT trigger onStateChange\n\t\tgetDoubled: (c) => {\n\t\t\tconst doubled = c.state.value * 2;\n\t\t\treturn doubled;\n\t\t},\n\t\t// Get the count of how many times onStateChange was called\n\t\tgetChangeCount: (c) => {\n\t\t\treturn c.vars.changeCount;\n\t\t},\n\t\t// Reset change counter for testing\n\t\tresetChangeCount: (c) => {\n\t\t\tc.vars.changeCount = 0;\n\t\t},\n\t},\n\t// Track onStateChange calls\n\tonStateChange: (c) => {\n\t\tc.vars.changeCount++;\n\t},\n});\n","import { type ActorContext, actor } from \"rivetkit\";\n\nexport const kvActor = actor({\n\tactions: {\n\t\tputText: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t\tvalue: string,\n\t\t) => {\n\t\t\tawait c.kv.put(key, value);\n\t\t\treturn true;\n\t\t},\n\t\tgetText: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t) => {\n\t\t\treturn await c.kv.get(key);\n\t\t},\n\t\tlistText: async (\n\t\t\tc: ActorContext,\n\t\t\tprefix: string,\n\t\t) => {\n\t\t\tconst results = await c.kv.list(prefix, { keyType: \"text\" });\n\t\t\treturn results.map(([key, value]) => ({\n\t\t\t\tkey,\n\t\t\t\tvalue,\n\t\t\t}));\n\t\t},\n\t\troundtripArrayBuffer: async (\n\t\t\tc: ActorContext,\n\t\t\tkey: string,\n\t\t\tvalues: number[],\n\t\t) => {\n\t\t\tconst buffer = new Uint8Array(values).buffer;\n\t\t\tawait c.kv.put(key, buffer, { type: \"arrayBuffer\" });\n\t\t\tconst result = await c.kv.get(key, { type: \"arrayBuffer\" });\n\t\t\tif (!result) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn Array.from(new Uint8Array(result));\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n/**\n * Actor for testing large payloads without connections\n */\nexport const largePayloadActor = actor({\n\tstate: {},\n\tactions: {\n\t\t/**\n\t\t * Accepts a large request payload and returns its size\n\t\t */\n\t\tprocessLargeRequest: (_c, data: { items: string[] }) => {\n\t\t\treturn {\n\t\t\t\titemCount: data.items.length,\n\t\t\t\tfirstItem: data.items[0],\n\t\t\t\tlastItem: data.items[data.items.length - 1],\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Returns a large response payload\n\t\t */\n\t\tgetLargeResponse: (_c, itemCount: number) => {\n\t\t\tconst items: string[] = [];\n\t\t\tfor (let i = 0; i < itemCount; i++) {\n\t\t\t\titems.push(\n\t\t\t\t\t`Item ${i} with some additional text to increase size`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { items };\n\t\t},\n\n\t\t/**\n\t\t * Echo back the request data\n\t\t */\n\t\techo: (_c, data: unknown) => {\n\t\t\treturn data;\n\t\t},\n\t},\n});\n\n/**\n * Actor for testing large payloads with connections\n */\nexport const largePayloadConnActor = actor({\n\tstate: {},\n\tconnState: {\n\t\tlastRequestSize: 0,\n\t},\n\tactions: {\n\t\t/**\n\t\t * Accepts a large request payload and returns its size\n\t\t */\n\t\tprocessLargeRequest: (c, data: { items: string[] }) => {\n\t\t\tc.conn.state.lastRequestSize = data.items.length;\n\t\t\treturn {\n\t\t\t\titemCount: data.items.length,\n\t\t\t\tfirstItem: data.items[0],\n\t\t\t\tlastItem: data.items[data.items.length - 1],\n\t\t\t};\n\t\t},\n\n\t\t/**\n\t\t * Returns a large response payload\n\t\t */\n\t\tgetLargeResponse: (_c, itemCount: number) => {\n\t\t\tconst items: string[] = [];\n\t\t\tfor (let i = 0; i < itemCount; i++) {\n\t\t\t\titems.push(\n\t\t\t\t\t`Item ${i} with some additional text to increase size`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { items };\n\t\t},\n\n\t\t/**\n\t\t * Echo back the request data\n\t\t */\n\t\techo: (_c, data: unknown) => {\n\t\t\treturn data;\n\t\t},\n\n\t\t/**\n\t\t * Get the last request size\n\t\t */\n\t\tgetLastRequestSize: (c) => {\n\t\t\treturn c.conn.state.lastRequestSize;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Note: For testing only - metadata API will need to be mocked\n// in tests since this is implementation-specific\nexport const metadataActor = actor({\n\tstate: {\n\t\tlastMetadata: null as any,\n\t\tactorName: \"\",\n\t\t// Store tags and region in state for testing since they may not be\n\t\t// available in the context in all environments\n\t\tstoredTags: {} as Record,\n\t\tstoredRegion: null as string | null,\n\t},\n\tonWake: (c) => {\n\t\t// Store the actor name during initialization\n\t\tc.state.actorName = c.name;\n\t},\n\tactions: {\n\t\t// Set up test tags - this will be called by tests to simulate tags\n\t\tsetupTestTags: (c, tags: Record) => {\n\t\t\tc.state.storedTags = tags;\n\t\t\treturn tags;\n\t\t},\n\n\t\t// Set up test region - this will be called by tests to simulate region\n\t\tsetupTestRegion: (c, region: string) => {\n\t\t\tc.state.storedRegion = region;\n\t\t\treturn region;\n\t\t},\n\n\t\t// Get all available metadata\n\t\tgetMetadata: (c) => {\n\t\t\t// Create metadata object from stored values\n\t\t\tconst metadata = {\n\t\t\t\tname: c.name,\n\t\t\t\ttags: c.state.storedTags,\n\t\t\t\tregion: c.state.storedRegion,\n\t\t\t};\n\n\t\t\t// Store for later inspection\n\t\t\tc.state.lastMetadata = metadata;\n\t\t\treturn metadata;\n\t\t},\n\n\t\t// Get the actor name\n\t\tgetActorName: (c) => {\n\t\t\treturn c.name;\n\t\t},\n\n\t\t// Get a specific tag by key\n\t\tgetTag: (c, key: string) => {\n\t\t\treturn c.state.storedTags[key] || null;\n\t\t},\n\n\t\t// Get all tags\n\t\tgetTags: (c) => {\n\t\t\treturn c.state.storedTags;\n\t\t},\n\n\t\t// Get the region\n\t\tgetRegion: (c) => {\n\t\t\treturn c.state.storedRegion;\n\t\t},\n\n\t\t// Get the stored actor name (from onWake)\n\t\tgetStoredActorName: (c) => {\n\t\t\treturn c.state.actorName;\n\t\t},\n\n\t\t// Get last retrieved metadata\n\t\tgetLastMetadata: (c) => {\n\t\t\treturn c.state.lastMetadata;\n\t\t},\n\t},\n});\n","import { actor, event } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const parallelismTest = actor({\n\tstate: {\n\t\tstateCount: 0,\n\t},\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tcount INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(`\n\t\t\t\tINSERT OR IGNORE INTO counter (id, count) VALUES (1, 0)\n\t\t\t`);\n\t\t},\n\t}),\n\tevents: {\n\t\tstateCountChanged: event<{ count: number }>(),\n\t\tsqliteCountChanged: event<{ count: number }>(),\n\t},\n\tactions: {\n\t\tincrementState: (c) => {\n\t\t\tc.state.stateCount += 1;\n\t\t\tc.broadcast(\"stateCountChanged\", { count: c.state.stateCount });\n\t\t\treturn { count: c.state.stateCount };\n\t\t},\n\t\tgetStateCount: (c) => {\n\t\t\treturn { count: c.state.stateCount };\n\t\t},\n\t\tincrementSqlite: async (c) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t`UPDATE counter SET count = count + 1 WHERE id = 1`,\n\t\t\t);\n\t\t\tconst results = await c.db.execute<{ count: number }>(\n\t\t\t\t`SELECT count FROM counter WHERE id = 1`,\n\t\t\t);\n\t\t\tconst count = results[0].count;\n\t\t\tc.broadcast(\"sqliteCountChanged\", { count });\n\t\t\treturn { count };\n\t\t},\n\t\tgetSqliteCount: async (c) => {\n\t\t\tconst results = await c.db.execute<{ count: number }>(\n\t\t\t\t`SELECT count FROM counter WHERE id = 1`,\n\t\t\t);\n\t\t\treturn { count: results[0].count };\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: 30_000,\n\t},\n});\n","import { eq } from \"drizzle-orm\";\nimport { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db/drizzle\";\nimport migrations from \"./drizzle/migrations.js\";\nimport * as schema from \"./schema.ts\";\n\nconst { todos } = schema;\n\nexport const sqliteDrizzleActor = actor({\n\tdb: db({ schema, migrations }),\n\tactions: {\n\t\taddTodo: async (c, title: string) => {\n\t\t\tconst result = await c.db\n\t\t\t\t.insert(todos)\n\t\t\t\t.values({\n\t\t\t\t\ttitle,\n\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t})\n\t\t\t\t.returning();\n\t\t\treturn result[0];\n\t\t},\n\t\tgetTodos: async (c) => {\n\t\t\treturn await c.db.select().from(todos).orderBy(todos.createdAt);\n\t\t},\n\t\ttoggleTodo: async (c, id: number) => {\n\t\t\tconst existing = await c.db\n\t\t\t\t.select()\n\t\t\t\t.from(todos)\n\t\t\t\t.where(eq(todos.id, id));\n\t\t\tif (!existing[0]) return null;\n\t\t\tconst newCompleted = existing[0].completed ? 0 : 1;\n\t\t\tconst result = await c.db\n\t\t\t\t.update(todos)\n\t\t\t\t.set({ completed: newCompleted })\n\t\t\t\t.where(eq(todos.id, id))\n\t\t\t\t.returning();\n\t\t\treturn result[0];\n\t\t},\n\t\tdeleteTodo: async (c, id: number) => {\n\t\t\tawait c.db.delete(todos).where(eq(todos.id, id));\n\t\t\treturn { deleted: id };\n\t\t},\n\t},\n});\n","{\n \"version\": \"7\",\n \"dialect\": \"sqlite\",\n \"entries\": [\n {\n \"idx\": 0,\n \"version\": \"6\",\n \"when\": 1770921282251,\n \"tag\": \"0000_left_wrecking_crew\",\n \"breakpoints\": true\n }\n ]\n}","CREATE TABLE `todos` (\n\t`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n\t`title` text NOT NULL,\n\t`completed` integer DEFAULT 0,\n\t`created_at` integer NOT NULL\n);\n","import journal from './meta/_journal.json' with { type: 'json' };\nimport m0000 from './0000_left_wrecking_crew.sql';\n\n export default {\n journal,\n migrations: {\n m0000\n }\n }\n \n","import { integer, sqliteTable, text } from \"rivetkit/db/drizzle\";\n\nexport const todos = sqliteTable(\"todos\", {\n\tid: integer(\"id\").primaryKey({ autoIncrement: true }),\n\ttitle: text(\"title\").notNull(),\n\tcompleted: integer(\"completed\").default(0),\n\tcreatedAt: integer(\"created_at\").notNull(),\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const sqliteRawActor = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS todos (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\ttitle TEXT NOT NULL,\n\t\t\t\t\tcompleted INTEGER DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\taddTodo: async (c, title: string) => {\n\t\t\tconst createdAt = Date.now();\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO todos (title, created_at) VALUES (?, ?)\",\n\t\t\t\ttitle,\n\t\t\t\tcreatedAt,\n\t\t\t);\n\t\t\treturn { title, createdAt };\n\t\t},\n\t\tgetTodos: async (c) => {\n\t\t\treturn await c.db.execute(\n\t\t\t\t\"SELECT * FROM todos ORDER BY created_at DESC\",\n\t\t\t);\n\t\t},\n\t\ttoggleTodo: async (c, id: number) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE todos SET completed = NOT completed WHERE id = ?\",\n\t\t\t\tid,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM todos WHERE id = ?\",\n\t\t\t\tid,\n\t\t\t);\n\t\t\treturn rows[0];\n\t\t},\n\t\tdeleteTodo: async (c, id: number) => {\n\t\t\tawait c.db.execute(\"DELETE FROM todos WHERE id = ?\", id);\n\t\t\treturn { deleted: id };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\n// Actor with static vars\nexport const staticVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tvars: { counter: 42, name: \"test-actor\" },\n\tactions: {\n\t\tgetVars: (c) => {\n\t\t\treturn c.vars;\n\t\t},\n\t\tgetName: (c) => {\n\t\t\treturn c.vars.name;\n\t\t},\n\t},\n});\n\n// Actor with nested vars\nexport const nestedVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tvars: {\n\t\tcounter: 42,\n\t\tnested: {\n\t\t\tvalue: \"original\",\n\t\t\tarray: [1, 2, 3],\n\t\t\tobj: { key: \"value\" },\n\t\t},\n\t},\n\tactions: {\n\t\tgetVars: (c) => {\n\t\t\treturn c.vars;\n\t\t},\n\t\tmodifyNested: (c) => {\n\t\t\t// Attempt to modify the nested object\n\t\t\tc.vars.nested.value = \"modified\";\n\t\t\tc.vars.nested.array.push(4);\n\t\t\tc.vars.nested.obj.key = \"new-value\";\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor with dynamic vars\nexport const dynamicVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: () => {\n\t\treturn {\n\t\t\trandom: Math.random(),\n\t\t\tcomputed: `Actor-${Math.floor(Math.random() * 1000)}`,\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor with unique vars per instance\nexport const uniqueVarActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: () => {\n\t\treturn {\n\t\t\tid: Math.floor(Math.random() * 1000000),\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n\n// Actor that uses driver context\nexport const driverCtxActor = actor({\n\tstate: { value: 0 },\n\tconnState: { hello: \"world\" },\n\tcreateVars: (_c, driverCtx: any) => {\n\t\treturn {\n\t\t\thasDriverCtx: Boolean(driverCtx?.isTest),\n\t\t};\n\t},\n\tactions: {\n\t\tgetVars: (c: any) => {\n\t\t\treturn c.vars;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\n// Seeding vessel for the \"large uncompacted SQLite DB\" load test.\n//\n// The old engine build under test has hot compaction disabled, so every\n// commit accumulates as an uncompacted delta chain in FDB that never folds.\n// This actor grows a persistent SQLite database with append-only inserts and\n// one commit per batch, producing both large page volume and many deltas.\n//\n// It deliberately avoids VACUUM, DELETE, and whole-database scans (no\n// integrity_check) so the database only ever grows and each grow() call costs\n// O(added bytes), not O(current size).\n\ninterface GrowInput {\n\t// Target total on-disk size in mebibytes. grow() is idempotent to this\n\t// target: calling it again after the target is reached is a cheap no-op.\n\ttargetMb?: number;\n\t// Rows inserted per committed batch. Each batch is one BEGIN/COMMIT, so\n\t// this controls commit (delta) granularity.\n\tbatchRows?: number;\n\t// Payload bytes per row, generated server-side via randomblob().\n\trowBytes?: number;\n\t// Wall-clock budget for a single grow() call in milliseconds. When the\n\t// budget is exceeded the call returns done=false so the driver can resume\n\t// with another call. Keeps a single action under the action timeout.\n\tbudgetMs?: number;\n}\n\ninterface StorageStats {\n\tpageCount: number;\n\tpageSize: number;\n\tfreelistCount: number;\n\tsizeBytes: number;\n}\n\ninterface GrowResult extends StorageStats {\n\tdone: boolean;\n\ttargetBytes: number;\n\tbatchesThisCall: number;\n\trowsThisCall: number;\n\telapsedMs: number;\n}\n\nconst DEFAULT_TARGET_MB = 64;\nconst DEFAULT_BATCH_ROWS = 128;\nconst DEFAULT_ROW_BYTES = 8 * 1024;\nconst DEFAULT_BUDGET_MS = 120_000;\nconst MEBIBYTE = 1024 * 1024;\n\nfunction finiteInt(value: number | undefined, fallback: number): number {\n\tif (value === undefined) return fallback;\n\tif (!Number.isFinite(value) || value <= 0) {\n\t\tthrow new Error(`expected a positive finite number, got ${value}`);\n\t}\n\treturn Math.floor(value);\n}\n\nasync function queryOne(\n\tdatabase: { execute: (sql: string, ...args: unknown[]) => Promise },\n\tsql: string,\n): Promise {\n\tconst rows = await database.execute(sql);\n\tif (!rows[0]) throw new Error(`query returned no rows: ${sql}`);\n\treturn rows[0] as T;\n}\n\nasync function storageStats(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}): Promise {\n\tconst [pageCount, pageSize, freelistCount] = await Promise.all([\n\t\tqueryOne<{ page_count: number }>(database, \"PRAGMA page_count\"),\n\t\tqueryOne<{ page_size: number }>(database, \"PRAGMA page_size\"),\n\t\tqueryOne<{ freelist_count: number }>(database, \"PRAGMA freelist_count\"),\n\t]);\n\treturn {\n\t\tpageCount: pageCount.page_count,\n\t\tpageSize: pageSize.page_size,\n\t\tfreelistCount: freelistCount.freelist_count,\n\t\tsizeBytes: pageCount.page_count * pageSize.page_size,\n\t};\n}\n\nexport const growDb = actor({\n\toptions: {\n\t\t// Generous per-action timeout. grow() self-limits with budgetMs and\n\t\t// returns done=false before this fires.\n\t\tactionTimeout: 300_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS grow_rows (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tbatch INTEGER NOT NULL,\n\t\t\t\t\tpayload BLOB NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\tgrow: async (c, input: GrowInput = {}): Promise => {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst targetMb = finiteInt(input.targetMb, DEFAULT_TARGET_MB);\n\t\t\tconst batchRows = finiteInt(input.batchRows, DEFAULT_BATCH_ROWS);\n\t\t\tconst rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES);\n\t\t\tconst budgetMs = finiteInt(input.budgetMs, DEFAULT_BUDGET_MS);\n\t\t\tconst targetBytes = targetMb * MEBIBYTE;\n\n\t\t\tconst placeholders = new Array(batchRows)\n\t\t\t\t.fill(\"(?, randomblob(?), ?)\")\n\t\t\t\t.join(\", \");\n\n\t\t\tlet batchesThisCall = 0;\n\t\t\tlet rowsThisCall = 0;\n\t\t\tlet stats = await storageStats(c.db);\n\n\t\t\twhile (stats.sizeBytes < targetBytes) {\n\t\t\t\tif (performance.now() - startedAt >= budgetMs) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...stats,\n\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\ttargetBytes,\n\t\t\t\t\t\tbatchesThisCall,\n\t\t\t\t\t\trowsThisCall,\n\t\t\t\t\t\telapsedMs: Math.round(performance.now() - startedAt),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tfor (let i = 0; i < batchRows; i += 1) {\n\t\t\t\t\targs.push(batchesThisCall, rowBytes, now);\n\t\t\t\t}\n\n\t\t\t\t// One commit per batch so each iteration produces exactly one\n\t\t\t\t// uncompacted delta in FDB.\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\ttry {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t`INSERT INTO grow_rows (batch, payload, created_at) VALUES ${placeholders}`,\n\t\t\t\t\t\t...args,\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\t} catch (err) {\n\t\t\t\t\tawait c.db.execute(\"ROLLBACK\").catch(() => undefined);\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tbatchesThisCall += 1;\n\t\t\t\trowsThisCall += batchRows;\n\t\t\t\tstats = await storageStats(c.db);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...stats,\n\t\t\t\tdone: true,\n\t\t\t\ttargetBytes,\n\t\t\t\tbatchesThisCall,\n\t\t\t\trowsThisCall,\n\t\t\t\telapsedMs: Math.round(performance.now() - startedAt),\n\t\t\t};\n\t\t},\n\t\tstats: async (c): Promise => storageStats(c.db),\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { registry } from \"../../index.ts\";\n\n// The dynamic-sandbox WebSocket connect path is failing intermittently and\n// silently swapping to HTTP-only would skip event-bridge validation entirely.\n// Re-enable WebSocket coverage on every runtime; if the dynamic-sandbox\n// regression returns, root-cause it instead of restoring this gate.\nfunction isDynamicSandboxRuntime(): boolean {\n\treturn false;\n}\n\nasync function waitForConnectionOpen(connection: {\n\tconnStatus: string;\n\tonOpen(callback: () => void): () => void;\n\tonError(callback: (error: unknown) => void): () => void;\n}) {\n\tif (connection.connStatus === \"connected\") {\n\t\treturn;\n\t}\n\n\tawait new Promise((resolve, reject) => {\n\t\tconst unsubscribeOpen = connection.onOpen(() => {\n\t\t\tunsubscribeOpen();\n\t\t\tunsubscribeError();\n\t\t\tresolve();\n\t\t});\n\t\tconst unsubscribeError = connection.onError((error) => {\n\t\t\tunsubscribeOpen();\n\t\t\tunsubscribeError();\n\t\t\treject(error);\n\t\t});\n\t});\n}\n\nexport const inlineClientActor = actor({\n\tstate: { messages: [] as string[] },\n\tactions: {\n\t\t// Action that uses client to call another actor (stateless)\n\t\tcallCounterIncrement: async (c, amount: number) => {\n\t\t\tconst client = c.client();\n\t\t\tconst result = await client.counter\n\t\t\t\t.getOrCreate([\"inline-test\"])\n\t\t\t\t.increment(amount);\n\t\t\tc.state.messages.push(\n\t\t\t\t`Called counter.increment(${amount}), result: ${result}`,\n\t\t\t);\n\t\t\treturn result;\n\t\t},\n\n\t\t// Action that uses client to get counter state (stateless)\n\t\tgetCounterState: async (c) => {\n\t\t\tconst client = c.client();\n\t\t\tconst count = await client.counter\n\t\t\t\t.getOrCreate([\"inline-test\"])\n\t\t\t\t.getCount();\n\t\t\tc.state.messages.push(`Got counter state: ${count}`);\n\t\t\treturn count;\n\t\t},\n\n\t\t// Action that uses client with .connect() for stateful communication\n\t\tconnectToCounterAndIncrement: async (c, amount: number) => {\n\t\t\tconst client = c.client();\n\t\t\tconst handle = client.counter.getOrCreate([\"inline-test-stateful\"]);\n\n\t\t\tif (isDynamicSandboxRuntime()) {\n\t\t\t\tconst events: number[] = [];\n\t\t\t\tconst result1 = await handle.increment(amount);\n\t\t\t\tevents.push(result1);\n\t\t\t\tconst result2 = await handle.increment(amount * 2);\n\t\t\t\tevents.push(result2);\n\n\t\t\t\tc.state.messages.push(\n\t\t\t\t\t`Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`,\n\t\t\t\t);\n\n\t\t\t\treturn { result1, result2, events };\n\t\t\t}\n\n\t\t\tawait handle.getCount();\n\t\t\tconst connection = handle.connect();\n\t\t\tawait waitForConnectionOpen(connection);\n\n\t\t\t// Set up event listener\n\t\t\tconst events: number[] = [];\n\t\t\tconnection.on(\"newCount\", (count: number) => {\n\t\t\t\tevents.push(count);\n\t\t\t});\n\n\t\t\t// Perform increments\n\t\t\tconst result1 = await connection.increment(amount);\n\t\t\tconst result2 = await connection.increment(amount * 2);\n\n\t\t\tawait connection.dispose();\n\n\t\t\tc.state.messages.push(\n\t\t\t\t`Connected to counter, incremented by ${amount} and ${amount * 2}, results: ${result1}, ${result2}, events: ${JSON.stringify(events)}`,\n\t\t\t);\n\n\t\t\treturn { result1, result2, events };\n\t\t},\n\n\t\t// Get all messages from this actor's state\n\t\tgetMessages: (c) => {\n\t\t\treturn c.state.messages;\n\t\t},\n\n\t\t// Clear messages\n\t\tclearMessages: (c) => {\n\t\t\tc.state.messages = [];\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_TOKENS_PER_SECOND = 20;\nconst DEFAULT_DURATION_MS = 5_000;\n\nfunction send(websocket: UniversalWebSocket, payload: unknown): void {\n\tif (websocket.readyState !== 1) return;\n\twebsocket.send(JSON.stringify(payload));\n}\n\nfunction parsePositiveNumber(\n\tvalue: unknown,\n\tname: string,\n\tfallback: number,\n): number {\n\tif (value === undefined || value === null) return fallback;\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed) || parsed <= 0) {\n\t\tthrow new Error(`${name} must be a positive number`);\n\t}\n\treturn parsed;\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise {\n\tif (signal.aborted) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timeout = setTimeout(resolve, ms);\n\t\tsignal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nexport const loadTestAgent = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS messages (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tconnection_id TEXT NOT NULL,\n\t\t\t\t\trequest_id TEXT NOT NULL,\n\t\t\t\t\ttoken_index INTEGER NOT NULL,\n\t\t\t\t\ttoken TEXT NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE INDEX IF NOT EXISTS messages_request_idx\n\t\t\t\tON messages (request_id, token_index)\n\t\t\t`);\n\t\t},\n\t}),\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tinferenceCount: 0,\n\t\ttokenCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tsend(websocket, {\n\t\t\ttype: \"connected\",\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\twebsocket.addEventListener(\n\t\t\t\"message\",\n\t\t\tasync (event: RivetMessageEvent) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst message =\n\t\t\t\t\t\ttypeof event.data === \"string\"\n\t\t\t\t\t\t\t? JSON.parse(event.data)\n\t\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t// Fast-path ping: echo back without touching SQLite so the client can measure raw\n\t\t\t\t\t// RTT without the per-message storage write. Used by the counter-latency client's\n\t\t\t\t\t// first two probes after WS open.\n\t\t\t\t\tif (message && message.type === \"ping\") {\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\tid: message.id,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!message || message.type !== \"inference\") {\n\t\t\t\t\t\tthrow new Error(\"expected inference message\");\n\t\t\t\t\t}\n\n\t\t\t\t\tconst requestId =\n\t\t\t\t\t\ttypeof message.requestId === \"string\" &&\n\t\t\t\t\t\tmessage.requestId\n\t\t\t\t\t\t\t? message.requestId\n\t\t\t\t\t\t\t: crypto.randomUUID();\n\t\t\t\t\tconst tokensPerSecond = parsePositiveNumber(\n\t\t\t\t\t\tmessage.tokensPerSecond,\n\t\t\t\t\t\t\"tokensPerSecond\",\n\t\t\t\t\t\tDEFAULT_TOKENS_PER_SECOND,\n\t\t\t\t\t);\n\t\t\t\t\tconst durationMs = parsePositiveNumber(\n\t\t\t\t\t\tmessage.durationMs,\n\t\t\t\t\t\t\"durationMs\",\n\t\t\t\t\t\tDEFAULT_DURATION_MS,\n\t\t\t\t\t);\n\t\t\t\t\tconst intervalMs = 1_000 / tokensPerSecond;\n\t\t\t\t\tconst targetTokens = Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.floor((durationMs / 1_000) * tokensPerSecond),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst inference = (async () => {\n\t\t\t\t\t\tc.state.inferenceCount += 1;\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"inference-start\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\ttokensPerSecond,\n\t\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\t\ttargetTokens,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst startedAt = performance.now();\n\t\t\t\t\t\tfor (let i = 0; i < targetTokens; i++) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tc.abortSignal.aborted ||\n\t\t\t\t\t\t\t\twebsocket.readyState !== 1\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst tokenIndex = i + 1;\n\t\t\t\t\t\t\tconst token = `token-${tokenIndex}`;\n\t\t\t\t\t\t\tconst createdAt = Date.now();\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"INSERT INTO messages (connection_id, request_id, token_index, token, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\t\ttokenIndex,\n\t\t\t\t\t\t\t\ttoken,\n\t\t\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tc.state.tokenCount += 1;\n\n\t\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\t\ttype: \"token\",\n\t\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\t\ttokenIndex,\n\t\t\t\t\t\t\t\ttoken,\n\t\t\t\t\t\t\t\ttimestamp: createdAt,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst nextAt = startedAt + tokenIndex * intervalMs;\n\t\t\t\t\t\t\tconst delayMs = Math.max(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnextAt - performance.now(),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (delayMs > 0) {\n\t\t\t\t\t\t\t\tawait sleep(delayMs, c.abortSignal);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"inference-complete\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\ttokenCount: targetTokens,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t});\n\t\t\t\t\t})();\n\n\t\t\t\t\tawait c.keepAwake(inference);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t\t\t: \"unknown websocket error\",\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tinferenceCount: c.state.inferenceCount,\n\t\t\t\ttokenCount: c.state.tokenCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\ntype AgentConcurrent2Request =\n\t| { type: \"agent2_resume\"; version: number }\n\t| { type: \"agent2_connect\"; clientId: string; staggerHandleMs?: number }\n\t| { type: \"force_sleep\" }\n\t| { type: \"ping\"; id?: number };\n\ninterface AgentConcurrent2Step {\n\tname: string;\n\tdurationMs: number;\n\trowCount: number;\n}\n\ninterface AgentConcurrent2QueryStats {\n\ttotal: number;\n\treads: number;\n\tmutations: number;\n\ttx: number;\n\tother: number;\n\trows: number;\n\terrors: number;\n\tslow: number;\n\tmaxMs: number;\n\tmaxStep: string;\n\tbyOperation: Record;\n\tbyTable: Record;\n}\n\ninterface AgentConcurrent2StatsSnapshot {\n\twakeIndex: number;\n\tactorIteration: number;\n\twakeIteration: number;\n\tcycle: AgentConcurrent2QueryStats;\n\twake: AgentConcurrent2QueryStats;\n\tactor: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2WorkloadResult {\n\tname: string;\n\ttotalMs: number;\n\tsteps: AgentConcurrent2Step[];\n}\n\ninterface AgentConcurrent2ResultMessage {\n\ttype: \"agent2_result\";\n\ttrigger: AgentConcurrent2Request[\"type\"];\n\ttotalMs: number;\n\tresults: AgentConcurrent2WorkloadResult[];\n\tstats: AgentConcurrent2StatsSnapshot;\n}\n\ninterface AgentConcurrent2ErrorMessage {\n\ttype: \"agent2_error\";\n\ttrigger: AgentConcurrent2Request[\"type\"] | \"unknown\";\n\terror: string;\n\tstats?: AgentConcurrent2StatsSnapshot;\n}\n\ninterface AgentConcurrent2Vars {\n\tsql: AgentConcurrent2Db | null;\n\twakeStats: AgentConcurrent2QueryStats | null;\n\twakeStartedAt: number | null;\n\twakeIteration: number;\n}\n\ninterface RawRivetDB {\n\texecute: (\n\t\tquery: string,\n\t\t...args: unknown[]\n\t) => Promise[]>;\n}\n\ntype SQLPrimitive = string | number | boolean | null;\n\ninterface AgentConcurrent2State {\n\trunCount: number;\n\twakeCount: number;\n\tqueryStats: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2QueryStatsSet {\n\tcycle: AgentConcurrent2QueryStats;\n\twake: AgentConcurrent2QueryStats;\n\tactor: AgentConcurrent2QueryStats;\n}\n\ninterface AgentConcurrent2Runtime {\n\tsql: AgentConcurrent2Db;\n\twakeStats: AgentConcurrent2QueryStats;\n\tvars: AgentConcurrent2Vars;\n}\n\ntype AgentConcurrent2Db = (>(\n\tquery: string,\n\t...values: SQLPrimitive[]\n) => Promise) & {\n\twithTransaction(\n\t\tstats: AgentConcurrent2QueryStatsSet,\n\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t): Promise;\n};\n\nclass AsyncMutex {\n\tprivate locked = false;\n\tprivate waiters: Array<() => void> = [];\n\n\tasync acquire(): Promise {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true;\n\t\t\treturn;\n\t\t}\n\t\tawait new Promise((resolve) => this.waiters.push(resolve));\n\t\tthis.locked = true;\n\t}\n\n\trelease(): void {\n\t\tconst next = this.waiters.shift();\n\t\tif (next) {\n\t\t\tnext();\n\t\t\treturn;\n\t\t}\n\t\tthis.locked = false;\n\t}\n}\n\nfunction createSerializedDb(\n\texecute: >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t) => Promise,\n): AgentConcurrent2Db {\n\tconst mutex = new AsyncMutex();\n\tlet activeTransaction: AgentConcurrent2Db | null = null;\n\n\tconst createTransactionDb = (): AgentConcurrent2Db => {\n\t\tconst tx = Object.assign(\n\t\t\t>(\n\t\t\t\tquery: string,\n\t\t\t\t...values: SQLPrimitive[]\n\t\t\t) => execute(query, ...values),\n\t\t\t{\n\t\t\t\twithTransaction: async (\n\t\t\t\t\t_stats: AgentConcurrent2QueryStatsSet,\n\t\t\t\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t\t\t\t): Promise => fn(tx),\n\t\t\t},\n\t\t);\n\t\treturn tx;\n\t};\n\n\tconst queryWithMutex = async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tawait mutex.acquire();\n\t\ttry {\n\t\t\treturn await execute(query, ...values);\n\t\t} finally {\n\t\t\tmutex.release();\n\t\t}\n\t};\n\n\treturn Object.assign(queryWithMutex, {\n\t\twithTransaction: async (\n\t\t\tstats: AgentConcurrent2QueryStatsSet,\n\t\t\tfn: (tx: AgentConcurrent2Db) => Promise,\n\t\t): Promise => {\n\t\t\tif (activeTransaction) {\n\t\t\t\treturn fn(activeTransaction);\n\t\t\t}\n\t\t\tawait mutex.acquire();\n\t\t\tconst tx = createTransactionDb();\n\t\t\ttry {\n\t\t\t\tawait executeTrackedQuery(\n\t\t\t\t\texecute,\n\t\t\t\t\tstats,\n\t\t\t\t\t\"transaction-begin\",\n\t\t\t\t\t\"BEGIN\",\n\t\t\t\t);\n\t\t\t\tactiveTransaction = tx;\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fn(tx);\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait executeTrackedQuery(\n\t\t\t\t\t\texecute,\n\t\t\t\t\t\tstats,\n\t\t\t\t\t\t\"transaction-commit\",\n\t\t\t\t\t\t\"COMMIT\",\n\t\t\t\t\t);\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait executeTrackedQuery(\n\t\t\t\t\t\texecute,\n\t\t\t\t\t\tstats,\n\t\t\t\t\t\t\"transaction-rollback\",\n\t\t\t\t\t\t\"ROLLBACK\",\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tactiveTransaction = null;\n\t\t\t\tmutex.release();\n\t\t\t}\n\t\t},\n\t});\n}\n\nconst MESSAGE_COUNT = 84;\nconst MESSAGE_TOOL_REF_COUNT = 122;\nconst TOOL_CALL_COUNT = 61;\nconst EXECUTOR_TOOL_COUNT = 42;\nconst THREAD_EVENT_COUNT = 233;\n\nconst MESSAGE_CONTENT_BYTES = 2_600;\nconst THREAD_EVENT_PAYLOAD_BYTES = 1_000;\nconst TOOL_CALL_RESULT_BYTES = 2_700;\nconst EXECUTOR_TOOL_SCHEMA_BYTES = 550;\nconst SLOW_QUERY_MS = 1_000;\n\nfunction send(\n\twebsocket: UniversalWebSocket,\n\tmessage:\n\t\t| AgentConcurrent2ResultMessage\n\t\t| AgentConcurrent2ErrorMessage\n\t\t| object,\n): void {\n\tif (websocket.readyState === 1) {\n\t\twebsocket.send(JSON.stringify(message));\n\t}\n}\n\nexport const loadTestAgent2 = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: 5_000,\n\t},\n\tstate: {\n\t\trunCount: 0,\n\t\twakeCount: 0,\n\t\tqueryStats: createAgentConcurrent2QueryStats(),\n\t} as AgentConcurrent2State,\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait createAgentConcurrent2Schema(database);\n\t\t\tawait seedAgentConcurrent2Data(database);\n\t\t},\n\t}),\n\tvars: {\n\t\tsql: null,\n\t\twakeStats: null,\n\t\twakeStartedAt: null,\n\t\twakeIteration: 0,\n\t} as AgentConcurrent2Vars,\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tsend(websocket, {\n\t\t\ttype: \"connected\",\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tconst promise = handleAgentConcurrent2Message(\n\t\t\t\tc,\n\t\t\t\twebsocket,\n\t\t\t\tevent.data,\n\t\t\t);\n\t\t\tvoid c.keepAwake(promise);\n\t\t});\n\t},\n\tactions: {\n\t\trun: async (c, clientId?: string) => {\n\t\t\tconst runtime = ensureAgentConcurrent2Runtime(c);\n\t\t\tc.state.runCount++;\n\t\t\truntime.vars.wakeIteration++;\n\t\t\tconst cycleStats = createAgentConcurrent2QueryStats();\n\t\t\tconst stats = createAgentConcurrent2StatsSet(\n\t\t\t\tcycleStats,\n\t\t\t\truntime.wakeStats,\n\t\t\t\tc.state.queryStats,\n\t\t\t);\n\t\t\tconst result = await runAgentConcurrent2Workload(\n\t\t\t\truntime.sql,\n\t\t\t\tclientId ?? `agent2-action-${c.state.runCount}`,\n\t\t\t\t0,\n\t\t\t\tstats,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\t...result,\n\t\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t\t};\n\t\t},\n\t\tgetRunCount: (c) => c.state.runCount,\n\t\tsleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn true;\n\t\t},\n\t},\n});\n\nasync function handleAgentConcurrent2Message(\n\tc: {\n\t\tdb: RawRivetDB;\n\t\tvars: AgentConcurrent2Vars;\n\t\tstate: AgentConcurrent2State;\n\t\tsleep: () => void;\n\t},\n\twebsocket: UniversalWebSocket,\n\tdata: unknown,\n): Promise {\n\tlet trigger: AgentConcurrent2Request[\"type\"] | \"unknown\" = \"unknown\";\n\tlet cycleStats: AgentConcurrent2QueryStats | null = null;\n\ttry {\n\t\tconst request = parseAgentConcurrent2Request(data);\n\t\ttrigger = request.type;\n\n\t\tif (request.type === \"ping\") {\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"pong\",\n\t\t\t\tid: request.id,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (request.type === \"force_sleep\") {\n\t\t\tsend(websocket, { type: \"sleeping\", timestamp: Date.now() });\n\t\t\tc.sleep();\n\t\t\treturn;\n\t\t}\n\n\t\tconst runtime = ensureAgentConcurrent2Runtime(c);\n\t\tc.state.runCount++;\n\t\truntime.vars.wakeIteration++;\n\t\tcycleStats = createAgentConcurrent2QueryStats();\n\t\tconst stats = createAgentConcurrent2StatsSet(\n\t\t\tcycleStats,\n\t\t\truntime.wakeStats,\n\t\t\tc.state.queryStats,\n\t\t);\n\n\t\tif (request.type === \"agent2_resume\") {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst result = await runCatchupSnapshot(\n\t\t\t\truntime.sql,\n\t\t\t\trequest.version,\n\t\t\t\tstats,\n\t\t\t);\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"agent2_result\",\n\t\t\t\ttrigger: request.type,\n\t\t\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\t\t\tresults: [result],\n\t\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await runAgentConcurrent2Workload(\n\t\t\truntime.sql,\n\t\t\trequest.clientId,\n\t\t\trequest.staggerHandleMs ?? 0,\n\t\t\tstats,\n\t\t);\n\t\tsend(websocket, {\n\t\t\ttype: \"agent2_result\",\n\t\t\ttrigger: request.type,\n\t\t\t...result,\n\t\t\tstats: snapshotAgentConcurrent2Stats(c, cycleStats),\n\t\t});\n\t} catch (error) {\n\t\tsend(websocket, {\n\t\t\ttype: \"agent2_error\",\n\t\t\ttrigger,\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t...(cycleStats\n\t\t\t\t? { stats: snapshotAgentConcurrent2Stats(c, cycleStats) }\n\t\t\t\t: {}),\n\t\t});\n\t}\n}\n\nfunction parseAgentConcurrent2Request(data: unknown): AgentConcurrent2Request {\n\tif (typeof data !== \"string\") {\n\t\tthrow new Error(\"agent concurrent 2 request must be a string\");\n\t}\n\tconst parsed = JSON.parse(data) as unknown;\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\tthrow new Error(\"agent concurrent 2 request must be an object\");\n\t}\n\tconst request = parsed as Record;\n\n\tif (request.type === \"ping\") {\n\t\treturn {\n\t\t\ttype: \"ping\",\n\t\t\t...(typeof request.id === \"number\" ? { id: request.id } : {}),\n\t\t};\n\t}\n\tif (request.type === \"force_sleep\") {\n\t\treturn { type: \"force_sleep\" };\n\t}\n\tif (request.type === \"agent2_resume\") {\n\t\treturn {\n\t\t\ttype: \"agent2_resume\",\n\t\t\tversion: numberField(request, \"version\"),\n\t\t};\n\t}\n\tif (request.type === \"agent2_connect\") {\n\t\treturn {\n\t\t\ttype: \"agent2_connect\",\n\t\t\tclientId: stringField(request, \"clientId\"),\n\t\t\t...(typeof request.staggerHandleMs === \"number\"\n\t\t\t\t? { staggerHandleMs: request.staggerHandleMs }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tthrow new Error(\n\t\t`unknown agent concurrent 2 request type: ${String(request.type)}`,\n\t);\n}\n\nfunction stringField(record: Record, field: string): string {\n\tconst value = record[field];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`agent concurrent 2 request ${field} must be a string`);\n\t}\n\treturn value;\n}\n\nfunction numberField(record: Record, field: string): number {\n\tconst value = record[field];\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(\n\t\t\t`agent concurrent 2 request ${field} must be a finite number`,\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction createAgentConcurrent2Db(db: RawRivetDB): AgentConcurrent2Db {\n\treturn createSerializedDb(\n\t\tasync >(\n\t\t\tquery: string,\n\t\t\t...values: SQLPrimitive[]\n\t\t): Promise => {\n\t\t\tconst converted = values.map((value) =>\n\t\t\t\ttypeof value === \"boolean\" ? (value ? 1 : 0) : value,\n\t\t\t);\n\t\t\treturn (await db.execute(query, ...converted)) as T[];\n\t\t},\n\t);\n}\n\nfunction ensureAgentConcurrent2Runtime(c: {\n\tdb: RawRivetDB;\n\tvars: AgentConcurrent2Vars;\n\tstate: AgentConcurrent2State;\n}): AgentConcurrent2Runtime {\n\tc.vars.sql ??= createAgentConcurrent2Db(c.db);\n\tc.state.queryStats ??= createAgentConcurrent2QueryStats();\n\tc.state.wakeCount ??= 0;\n\tif (!c.vars.wakeStats) {\n\t\tc.vars.wakeStats = createAgentConcurrent2QueryStats();\n\t\tc.vars.wakeStartedAt = Date.now();\n\t\tc.vars.wakeIteration = 0;\n\t\tc.state.wakeCount++;\n\t}\n\treturn {\n\t\tsql: c.vars.sql,\n\t\twakeStats: c.vars.wakeStats,\n\t\tvars: c.vars,\n\t};\n}\n\nfunction createAgentConcurrent2QueryStats(): AgentConcurrent2QueryStats {\n\treturn {\n\t\ttotal: 0,\n\t\treads: 0,\n\t\tmutations: 0,\n\t\ttx: 0,\n\t\tother: 0,\n\t\trows: 0,\n\t\terrors: 0,\n\t\tslow: 0,\n\t\tmaxMs: 0,\n\t\tmaxStep: \"\",\n\t\tbyOperation: {},\n\t\tbyTable: {},\n\t};\n}\n\nfunction createAgentConcurrent2StatsSet(\n\tcycle: AgentConcurrent2QueryStats,\n\twake: AgentConcurrent2QueryStats,\n\tactor: AgentConcurrent2QueryStats,\n): AgentConcurrent2QueryStatsSet {\n\treturn { cycle, wake, actor };\n}\n\nfunction snapshotAgentConcurrent2Stats(\n\tc: { vars: AgentConcurrent2Vars; state: AgentConcurrent2State },\n\tcycle: AgentConcurrent2QueryStats,\n): AgentConcurrent2StatsSnapshot {\n\treturn {\n\t\twakeIndex: c.state.wakeCount,\n\t\tactorIteration: c.state.runCount,\n\t\twakeIteration: c.vars.wakeIteration,\n\t\tcycle: cloneAgentConcurrent2QueryStats(cycle),\n\t\twake: cloneAgentConcurrent2QueryStats(\n\t\t\tc.vars.wakeStats ?? createAgentConcurrent2QueryStats(),\n\t\t),\n\t\tactor: cloneAgentConcurrent2QueryStats(c.state.queryStats),\n\t};\n}\n\nfunction cloneAgentConcurrent2QueryStats(\n\tstats: AgentConcurrent2QueryStats,\n): AgentConcurrent2QueryStats {\n\treturn {\n\t\ttotal: stats.total,\n\t\treads: stats.reads,\n\t\tmutations: stats.mutations,\n\t\ttx: stats.tx,\n\t\tother: stats.other,\n\t\trows: stats.rows,\n\t\terrors: stats.errors,\n\t\tslow: stats.slow,\n\t\tmaxMs: stats.maxMs,\n\t\tmaxStep: stats.maxStep,\n\t\tbyOperation: { ...stats.byOperation },\n\t\tbyTable: { ...stats.byTable },\n\t};\n}\n\nasync function runAgentConcurrent2Workload(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstaggerHandleMs: number,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise> {\n\tconst startedAt = performance.now();\n\tconst buildToolPlanContext = runBuildToolPlanContext(sql, stats);\n\tconst catchupSnapshot = runCatchupSnapshot(sql, 0, stats);\n\tconst recoverToolCalls = runRecoverToolCalls(sql, stats);\n\tconst mutationMix = runMutationMix(sql, clientId, stats);\n\tconst handleExecutorConnect = delay(staggerHandleMs).then(() =>\n\t\trunHandleClientConnect(sql, clientId, stats),\n\t);\n\n\tconst results = await Promise.all([\n\t\thandleExecutorConnect,\n\t\tbuildToolPlanContext,\n\t\tcatchupSnapshot,\n\t\trecoverToolCalls,\n\t\tmutationMix,\n\t]);\n\treturn {\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tresults,\n\t};\n}\n\nasync function runHandleClientConnect(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst nextSeq = await sql.withTransaction(stats, async (tx) => {\n\t\tconst latestExecutor = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"load-latest-executor-id\",\n\t\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t\t);\n\t\tconst latestExecutorId = String(\n\t\t\tlatestExecutor[0]?.executor_id ?? \"seed-executor\",\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-cached-executor-tools\",\n\t\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\t\tlatestExecutorId,\n\t\t);\n\t\tconst executorType = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-executor-type\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_type'`,\n\t\t);\n\t\tif (!executorType[0]?.value) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tstats,\n\t\t\t\tsteps,\n\t\t\t\t\"set-executor-type\",\n\t\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`,\n\t\t\t\t\"local-client\",\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst sandboxIntent = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-workspace-intent\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'workspace_intent'`,\n\t\t);\n\t\tif (hasPendingLaunch(sandboxIntent[0]?.value)) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tstats,\n\t\t\t\tsteps,\n\t\t\t\t\"clear-pending-launch\",\n\t\t\t\t`UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'workspace_intent'`,\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-next-thread-event-seq\",\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t);\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-client-connected-event\",\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t\"client_connected\",\n\t\t\tJSON.stringify({ type: \"client_connected\", clientId }),\n\t\t\tnew Date().toISOString(),\n\t\t);\n\t\treturn seq;\n\t});\n\tsteps.push({\n\t\tname: \"transaction-total\",\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: nextSeq,\n\t});\n\treturn {\n\t\tname: \"handle-client-connect\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runBuildToolPlanContext(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst latestExecutor = await timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"load-latest-executor-id\",\n\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t);\n\tconst latestExecutorId = String(\n\t\tlatestExecutor[0]?.executor_id ?? \"seed-executor\",\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"select-executor-tools\",\n\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\tlatestExecutorId,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"count-uncancelled-top-level\",\n\t\t`SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL`,\n\t);\n\tconst unresolvedRows = await timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"find-unresolved-assistant-message\",\n\t\t`SELECT m.*\n\t\t\tFROM message_tool_refs AS tool_use\n\t\t\tJOIN messages AS m\n\t\t\t\tON m.message_id = tool_use.assistant_message_id\n\t\t\tWHERE tool_use.block_type = 'tool_use'\n\t\t\t\tAND tool_use.cancelled = 0\n\t\t\t\tAND m.cancelled = 0\n\t\t\t\tAND m.role = 'assistant'\n\t\t\t\tAND m.parent_tool_use_id IS NULL\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\t\tWHERE tool_result.assistant_message_id = tool_use.assistant_message_id\n\t\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\t\tAND tool_result.tool_use_id = tool_use.tool_use_id\n\t\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL\n\t\t\t\t)\n\t\t\tGROUP BY m.message_id\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tconst unresolvedMessageId = unresolvedRows[0]?.message_id;\n\tif (typeof unresolvedMessageId === \"string\") {\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"get-persisted-tool-result-ids\",\n\t\t\t`SELECT tool_result.tool_use_id\n\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\tWHERE tool_result.assistant_message_id = ?\n\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"get-tool-calls-by-message-id\",\n\t\t\t`SELECT * FROM tool_calls WHERE message_id = ?`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t}\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"is-last-message-cancelled-assistant\",\n\t\t`SELECT role, cancelled FROM messages\n\t\t\tWHERE parent_tool_use_id IS NULL\n\t\t\tORDER BY created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-last-uncancelled\",\n\t\t`SELECT m.* FROM messages m\n\t\t\tWHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\treturn {\n\t\tname: \"build-tool-plan-context\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runCatchupSnapshot(\n\tsql: AgentConcurrent2Db,\n\tversion: number,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tawait Promise.all([\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"thread-events-list-since-version\",\n\t\t\t`SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`,\n\t\t\tversion,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"environment-snapshot\",\n\t\t\t`SELECT snapshot FROM environment_snapshot WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"thread-settings-snapshot\",\n\t\t\t`SELECT settings FROM thread_settings_snapshot WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"retry-state\",\n\t\t\t`SELECT * FROM retry_state WHERE id = 1`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"queued-messages\",\n\t\t\t`SELECT * FROM queued_messages ORDER BY created_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"executor-artifacts\",\n\t\t\t`SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"tool-approvals\",\n\t\t\t`SELECT * FROM tool_approvals ORDER BY timestamp ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"compaction-summaries\",\n\t\t\t`SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC`,\n\t\t),\n\t\ttimedQuery(\n\t\t\tsql,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"executor-status\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_status'`,\n\t\t),\n\t]);\n\tsteps.sort((a, b) => b.durationMs - a.durationMs);\n\treturn {\n\t\tname: \"catchup-snapshot\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runRecoverToolCalls(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"hydrate-tool-progress\",\n\t\t`SELECT id, progress\n\t\t\tFROM tool_calls\n\t\t\tWHERE progress IS NOT NULL\n\t\t\t\tAND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-pending-tool-calls\",\n\t\t`SELECT * FROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')\n\t\t\tORDER BY issued_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tstats,\n\t\tsteps,\n\t\t\"get-next-tool-expiry\",\n\t\t`SELECT MIN(expires_at) AS expires_at\n\t\t\tFROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\treturn {\n\t\tname: \"recover-tool-calls\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runMutationMix(\n\tsql: AgentConcurrent2Db,\n\tclientId: string,\n\tstats: AgentConcurrent2QueryStatsSet,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: AgentConcurrent2Step[] = [];\n\tconst writeCount = await sql.withTransaction(stats, async (tx) => {\n\t\tconst now = new Date().toISOString();\n\t\tconst suffix = safeId(clientId);\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-max-thread-event-seq\",\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t);\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1);\n\t\tconst lastMessageRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-last-message-created-at\",\n\t\t\t`SELECT MAX(created_at) AS created_at FROM messages`,\n\t\t);\n\t\tconst latestToolRows = await timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-existing-tool-call\",\n\t\t\t`SELECT id FROM tool_calls ORDER BY issued_at DESC LIMIT 1`,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"select-sandbox-row\",\n\t\t\t`SELECT sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup\n\t\t\t\tFROM e2b_sandbox\n\t\t\t\tWHERE id = 1`,\n\t\t);\n\n\t\tconst messageIdValue = `agent2-message-${suffix}-${seq}`;\n\t\tconst toolUseIdValue = `agent2-tool-${suffix}-${seq}`;\n\t\tconst toolCallIdValue = `agent2-call-${suffix}-${seq}`;\n\t\tconst latestToolCallId = String(latestToolRows[0]?.id ?? toolUseID(1));\n\t\tconst lastCreatedAt = String(lastMessageRows[0]?.created_at ?? now);\n\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"upsert-agent-state\",\n\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES (?, ?, ?)`,\n\t\t\t\"last_agent_state\",\n\t\t\tJSON.stringify({ status: \"working\", clientId, lastCreatedAt }),\n\t\t\tnow,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-work-event\",\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t\"message_added\",\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"message_added\",\n\t\t\t\tmessageId: messageIdValue,\n\t\t\t}),\n\t\t\tnow,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message\",\n\t\t\t`INSERT INTO messages (role, content, meta, user_state, message_id, created_at, cancelled, parent_tool_use_id, tool_result_for_message_id)\n\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\"assistant\",\n\t\t\t\"agent concurrent 2 mutation payload\",\n\t\t\tJSON.stringify({ clientId, seq }),\n\t\t\tnull,\n\t\t\tmessageIdValue,\n\t\t\tnow,\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"delete-message-tool-refs\",\n\t\t\t`DELETE FROM message_tool_refs WHERE source_message_id = ?`,\n\t\t\tmessageIdValue,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message-added-event\",\n\t\t\t`INSERT OR IGNORE INTO message_added_events (message_id, seq) VALUES (?, ?)`,\n\t\t\tmessageIdValue,\n\t\t\tseq,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-message-tool-ref\",\n\t\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)\n\t\t\t\tVALUES (?, ?, ?, ?, ?)`,\n\t\t\tmessageIdValue,\n\t\t\tmessageIdValue,\n\t\t\ttoolUseIdValue,\n\t\t\t\"tool_use\",\n\t\t\t0,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"insert-tool-call\",\n\t\t\t`INSERT OR IGNORE INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)\n\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\ttoolCallIdValue,\n\t\t\t`provider-${toolCallIdValue}`,\n\t\t\t\"tool_1\",\n\t\t\tJSON.stringify({ path: `/tmp/${toolCallIdValue}` }),\n\t\t\t\"seed-executor\",\n\t\t\tmessageIdValue,\n\t\t\tnow,\n\t\t\tnull,\n\t\t\t\"running\",\n\t\t\tnull,\n\t\t\tJSON.stringify({ pct: 0.5, clientId }),\n\t\t\tnull,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"update-tool-call-progress\",\n\t\t\t`UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t\t\tJSON.stringify({ pct: 0.75, clientId, updatedAt: now }),\n\t\t\ttoolCallIdValue,\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tstats,\n\t\t\tsteps,\n\t\t\t\"update-existing-tool-call-progress\",\n\t\t\t`UPDATE tool_calls SET progress = ? WHERE id = ? AND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t\t\tJSON.stringify({ pct: 0.25, clientId, updatedAt: now }),\n\t\t\tlatestToolCallId,\n\t\t);\n\n\t\treturn seq;\n\t});\n\tsteps.push({\n\t\tname: \"transaction-total\",\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: writeCount,\n\t});\n\treturn {\n\t\tname: \"mutation-mix\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function timedQuery>(\n\tsql: AgentConcurrent2Db,\n\tstats: AgentConcurrent2QueryStatsSet,\n\tsteps: AgentConcurrent2Step[],\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now();\n\ttry {\n\t\tconst rows = await sql(query, ...values);\n\t\tconst durationMs = Math.round(performance.now() - startedAt);\n\t\trecordAgentConcurrent2Query(\n\t\t\tstats,\n\t\t\tname,\n\t\t\tquery,\n\t\t\tdurationMs,\n\t\t\trows.length,\n\t\t\tfalse,\n\t\t);\n\t\tsteps.push({\n\t\t\tname,\n\t\t\tdurationMs,\n\t\t\trowCount: rows.length,\n\t\t});\n\t\treturn rows;\n\t} catch (error) {\n\t\tconst durationMs = Math.round(performance.now() - startedAt);\n\t\trecordAgentConcurrent2Query(stats, name, query, durationMs, 0, true);\n\t\tthrow error;\n\t}\n}\n\nasync function executeTrackedQuery>(\n\texecute: >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t) => Promise,\n\tstats: AgentConcurrent2QueryStatsSet,\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now();\n\ttry {\n\t\tconst rows = await execute(query, ...values);\n\t\trecordAgentConcurrent2Query(\n\t\t\tstats,\n\t\t\tname,\n\t\t\tquery,\n\t\t\tMath.round(performance.now() - startedAt),\n\t\t\trows.length,\n\t\t\tfalse,\n\t\t);\n\t\treturn rows;\n\t} catch (error) {\n\t\trecordAgentConcurrent2Query(\n\t\t\tstats,\n\t\t\tname,\n\t\t\tquery,\n\t\t\tMath.round(performance.now() - startedAt),\n\t\t\t0,\n\t\t\ttrue,\n\t\t);\n\t\tthrow error;\n\t}\n}\n\nfunction recordAgentConcurrent2Query(\n\tstats: AgentConcurrent2QueryStatsSet,\n\tname: string,\n\tquery: string,\n\tdurationMs: number,\n\trowCount: number,\n\tfailed: boolean,\n): void {\n\tconst classification = classifyAgentConcurrent2Query(query);\n\tfor (const target of [stats.cycle, stats.wake, stats.actor]) {\n\t\ttarget.total++;\n\t\ttarget.rows += rowCount;\n\t\tif (failed) target.errors++;\n\t\tif (durationMs >= SLOW_QUERY_MS) target.slow++;\n\t\tif (durationMs > target.maxMs) {\n\t\t\ttarget.maxMs = durationMs;\n\t\t\ttarget.maxStep = `${name}:${classification.table}`;\n\t\t}\n\t\ttarget.byOperation[classification.operation] =\n\t\t\t(target.byOperation[classification.operation] ?? 0) + 1;\n\t\ttarget.byTable[classification.table] =\n\t\t\t(target.byTable[classification.table] ?? 0) + 1;\n\t\tif (classification.kind === \"read\") {\n\t\t\ttarget.reads++;\n\t\t} else if (classification.kind === \"mutation\") {\n\t\t\ttarget.mutations++;\n\t\t} else if (classification.kind === \"tx\") {\n\t\t\ttarget.tx++;\n\t\t} else {\n\t\t\ttarget.other++;\n\t\t}\n\t}\n}\n\nfunction classifyAgentConcurrent2Query(query: string): {\n\toperation: string;\n\tkind: \"read\" | \"mutation\" | \"tx\" | \"other\";\n\ttable: string;\n} {\n\tconst normalized = query.trim().replace(/\\s+/g, \" \");\n\tconst operation =\n\t\tnormalized.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? \"other\";\n\tconst table = extractAgentConcurrent2Table(normalized, operation);\n\tif (operation === \"select\") {\n\t\treturn { operation, kind: \"read\", table };\n\t}\n\tif (\n\t\toperation === \"insert\" ||\n\t\toperation === \"update\" ||\n\t\toperation === \"delete\" ||\n\t\toperation === \"replace\"\n\t) {\n\t\treturn { operation, kind: \"mutation\", table };\n\t}\n\tif (\n\t\toperation === \"begin\" ||\n\t\toperation === \"commit\" ||\n\t\toperation === \"rollback\"\n\t) {\n\t\treturn { operation, kind: \"tx\", table };\n\t}\n\treturn { operation, kind: \"other\", table };\n}\n\nfunction extractAgentConcurrent2Table(\n\tquery: string,\n\toperation: string,\n): string {\n\tconst lower = query.toLowerCase();\n\tif (operation === \"select\") {\n\t\treturn firstMatch(lower, /\\bfrom\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"insert\" || operation === \"replace\") {\n\t\treturn firstMatch(lower, /\\binto\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"update\") {\n\t\treturn firstMatch(lower, /\\bupdate\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (operation === \"delete\") {\n\t\treturn firstMatch(lower, /\\bfrom\\s+([a-z0-9_]+)/) ?? \"unknown\";\n\t}\n\tif (\n\t\toperation === \"begin\" ||\n\t\toperation === \"commit\" ||\n\t\toperation === \"rollback\"\n\t) {\n\t\treturn \"transaction\";\n\t}\n\treturn \"unknown\";\n}\n\nfunction firstMatch(value: string, pattern: RegExp): string | null {\n\treturn pattern.exec(value)?.[1] ?? null;\n}\n\nfunction hasPendingLaunch(value: unknown): boolean {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn false;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(value) as { pendingLaunch?: unknown };\n\t\treturn (\n\t\t\tparsed.pendingLaunch !== null && parsed.pendingLaunch !== undefined\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));\n}\n\nasync function createAgentConcurrent2Schema(\n\tdatabase: RawRivetDB,\n): Promise {\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS executor_tools (\n\t\texecutor_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\tschema TEXT NOT NULL,\n\t\tupdated_at TEXT NOT NULL,\n\t\tPRIMARY KEY (executor_id, tool_name)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv (\n\t\tkey TEXT PRIMARY KEY,\n\t\tvalue TEXT,\n\t\tupdated_at TEXT NOT NULL\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_events (\n\t\tseq INTEGER PRIMARY KEY,\n\t\tevent_type TEXT NOT NULL,\n\t\tpayload TEXT NOT NULL,\n\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_added_events (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\tseq INTEGER NOT NULL UNIQUE\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS messages (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\trole TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')),\n\t\tcontent TEXT NOT NULL,\n\t\tmeta TEXT,\n\t\tuser_state TEXT,\n\t\tcreated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tread_at TEXT,\n\t\tparent_tool_use_id TEXT,\n\t\ttool_result_for_message_id TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs (\n\t\tsource_message_id TEXT NOT NULL,\n\t\tassistant_message_id TEXT NOT NULL,\n\t\ttool_use_id TEXT NOT NULL,\n\t\tblock_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tPRIMARY KEY (source_message_id, block_type, tool_use_id)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS tool_calls (\n\t\tid TEXT PRIMARY KEY,\n\t\tprovider_tool_use_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\targs TEXT NOT NULL,\n\t\texecutor_id TEXT,\n\t\tmessage_id TEXT NOT NULL,\n\t\tissued_at TEXT NOT NULL,\n\t\texpires_at TEXT,\n\t\tstate TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')),\n\t\tresult TEXT,\n\t\tprogress TEXT,\n\t\tcompleted_at TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS e2b_sandbox (\n\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\tsandbox_id TEXT,\n\t\t\trestart_attempts INTEGER NOT NULL DEFAULT 0,\n\t\t\ttraffic_access_token TEXT,\n\t\t\tproject_id TEXT,\n\t\t\trepository_url TEXT,\n\t\t\tadditional_repositories TEXT,\n\t\t\tsetup TEXT,\n\t\t\tcreated_at TEXT NOT NULL,\n\t\t\tupdated_at TEXT NOT NULL\n\t\t)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)`,\n\t);\n}\n\nasync function seedAgentConcurrent2Data(database: RawRivetDB): Promise {\n\tconst existing = await database.execute(\n\t\t`SELECT COUNT(*) AS count FROM messages`,\n\t);\n\tif (Number(existing[0]?.count ?? 0) > 0) {\n\t\treturn;\n\t}\n\n\tconst now = new Date(\"2026-05-16T03:58:18.661Z\").getTime();\n\tconst text = (size: number) => \"x\".repeat(size);\n\tconst isoAt = (index: number) =>\n\t\tnew Date(now + index * 1_000).toISOString();\n\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_meta_kv (key, value, updated_at)`,\n\t\t[\n\t\t\t[\"executor_type\", \"local-client\", isoAt(0)],\n\t\t\t[\n\t\t\t\t\"workspace_intent\",\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tisoAt(0),\n\t\t\t],\n\t\t\t[\n\t\t\t\t\"executor_status\",\n\t\t\t\tJSON.stringify({ available: true, message: \"ready\" }),\n\t\t\t\tisoAt(0),\n\t\t\t],\n\t\t],\n\t);\n\n\tconst messageRows: unknown[][] = [];\n\tfor (let index = 1; index <= MESSAGE_COUNT; index++) {\n\t\tconst role = index % 2 === 0 ? \"assistant\" : \"user\";\n\t\tmessageRows.push([\n\t\t\tmessageId(index),\n\t\t\trole,\n\t\t\ttext(MESSAGE_CONTENT_BYTES),\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tisoAt(index),\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tnull,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`,\n\t\tmessageRows,\n\t\t20,\n\t);\n\n\tconst messageToolRefRows: unknown[][] = [];\n\tfor (let index = 0; index < MESSAGE_TOOL_REF_COUNT / 2; index++) {\n\t\tconst assistantIndex = 2 + (index % 42) * 2;\n\t\tconst sourceIndex = Math.max(1, assistantIndex - 1);\n\t\tconst resultIndex = Math.min(MESSAGE_COUNT, assistantIndex + 1);\n\t\tconst toolUseId = toolUseID(index + 1);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(sourceIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_use\",\n\t\t\t0,\n\t\t]);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(resultIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_result\",\n\t\t\t0,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`,\n\t\tmessageToolRefRows,\n\t\t50,\n\t);\n\n\tconst toolCallRows: unknown[][] = [];\n\tfor (let index = 1; index <= TOOL_CALL_COUNT; index++) {\n\t\tconst assistantIndex = 2 + ((index - 1) % 42) * 2;\n\t\ttoolCallRows.push([\n\t\t\ttoolUseID(index),\n\t\t\t`provider-${index}`,\n\t\t\t`tool_${index % 21}`,\n\t\t\tJSON.stringify({ path: `/tmp/file-${index}` }),\n\t\t\t\"seed-executor\",\n\t\t\tmessageId(assistantIndex),\n\t\t\tisoAt(index),\n\t\t\tnull,\n\t\t\t\"completed\",\n\t\t\tJSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\trun: { status: \"done\", result: text(TOOL_CALL_RESULT_BYTES) },\n\t\t\t}),\n\t\t\tnull,\n\t\t\tisoAt(index + 100),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`,\n\t\ttoolCallRows,\n\t\t20,\n\t);\n\n\tconst executorToolRows: unknown[][] = [];\n\tfor (let index = 1; index <= EXECUTOR_TOOL_COUNT; index++) {\n\t\tconst schema = JSON.stringify({\n\t\t\tname: `tool_${index}`,\n\t\t\tdescription: text(EXECUTOR_TOOL_SCHEMA_BYTES),\n\t\t\tinput_schema: { type: \"object\", properties: {} },\n\t\t});\n\t\texecutorToolRows.push([\n\t\t\t\"seed-executor\",\n\t\t\t`tool_${index}`,\n\t\t\tschema,\n\t\t\tisoAt(index),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`,\n\t\texecutorToolRows,\n\t\t42,\n\t);\n\n\tconst threadEventRows: unknown[][] = [];\n\tfor (let index = 1; index <= THREAD_EVENT_COUNT; index++) {\n\t\tthreadEventRows.push([\n\t\t\tindex,\n\t\t\tindex % 3 === 0 ? \"message_added\" : \"agent_state_changed\",\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"seed_event\",\n\t\t\t\tbody: text(THREAD_EVENT_PAYLOAD_BYTES),\n\t\t\t}),\n\t\t\tisoAt(index),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at)`,\n\t\tthreadEventRows,\n\t\t25,\n\t);\n\n\tconst messageAddedRows: unknown[][] = [];\n\tfor (let index = 1; index <= MESSAGE_COUNT; index++) {\n\t\tmessageAddedRows.push([messageId(index), index]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_added_events (message_id, seq)`,\n\t\tmessageAddedRows,\n\t\t50,\n\t);\n\n\tawait database.execute(\n\t\t`INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ cwd: \"/workspace\", body: text(3_620) }),\n\t\tisoAt(0),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ maxTokens: 20_000, body: text(55) }),\n\t\tisoAt(0),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO e2b_sandbox (id, sandbox_id, restart_attempts, traffic_access_token, project_id, repository_url, additional_repositories, setup, created_at, updated_at)\n\t\t\tVALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\"sandbox-seed\",\n\t\t0,\n\t\t\"token-seed\",\n\t\t\"project-seed\",\n\t\t\"https://example.invalid/repo.git\",\n\t\tJSON.stringify([]),\n\t\tJSON.stringify({ commands: [] }),\n\t\tisoAt(0),\n\t\tisoAt(0),\n\t);\n}\n\nasync function batchInsert(\n\tdatabase: RawRivetDB,\n\tinsertPrefix: string,\n\trows: unknown[][],\n\tbatchSize = 100,\n): Promise {\n\tif (rows.length === 0) {\n\t\treturn;\n\t}\n\tconst columnCount = rows[0]?.length ?? 0;\n\tif (columnCount === 0) {\n\t\treturn;\n\t}\n\tconst rowPlaceholder = `(${\"?,\".repeat(columnCount).slice(0, -1)})`;\n\tfor (let index = 0; index < rows.length; index += batchSize) {\n\t\tconst chunk = rows.slice(index, index + batchSize);\n\t\tconst values = chunk.map(() => rowPlaceholder).join(\",\");\n\t\tconst bindings = chunk.flat();\n\t\tawait database.execute(`${insertPrefix} VALUES ${values}`, ...bindings);\n\t}\n}\n\nfunction messageId(index: number): string {\n\treturn `M-${String(index).padStart(22, \"0\")}`;\n}\n\nfunction toolUseID(index: number): string {\n\treturn `toolu_${String(index).padStart(22, \"0\")}`;\n}\n\nfunction safeId(value: string): string {\n\treturn value.replace(/[^a-zA-Z0-9_-]/g, \"-\").slice(0, 80);\n}\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_SLEEP_GRACE_PERIOD_MS = 120_000;\nconst DEFAULT_ON_SLEEP_DELAY_MS = 0;\n\ntype EntryRow = {\n\trequest_id: string;\n\tidx: number;\n\tcreated_at: number;\n};\n\ntype CountRow = {\n\tcount: number;\n};\n\ntype SleepStateRow = {\n\tsleep_started_at: number;\n};\n\ntype DebugEventRow = {\n\tevent_id: string;\n\tname: string;\n\tactor_id: string;\n\tconnection_id: string | null;\n\trequest_id: string | null;\n\tdetails_json: string;\n\tcreated_at: number;\n};\n\ntype ExpectedRequest = {\n\trequestId: string;\n\tseconds: number;\n};\n\ntype DebugEventInput = {\n\tname: string;\n\tconnectionId?: string;\n\trequestId?: string;\n\tdetails?: Record;\n\tcreatedAt?: number;\n};\n\ntype DebugContext = {\n\tactorId: string;\n\tdb: {\n\t\texecute: (query: string, ...params: unknown[]) => Promise;\n\t};\n\tlog: {\n\t\twarn: (payload: unknown) => void;\n\t};\n};\n\nconst debugSocketsByActorId = new Map>();\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction positiveInteger(value: unknown, name: string) {\n\tif (!Number.isInteger(value) || (value as number) < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\n\treturn value as number;\n}\n\nfunction stringValue(value: unknown, name: string) {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`${name} must be a non-empty string`);\n\t}\n\n\treturn value;\n}\n\nfunction typedRows(rows: unknown[]): T[] {\n\treturn rows as T[];\n}\n\nfunction numberFromEnv(name: string, fallback: number): number {\n\tconst value = process.env[name];\n\tif (value === undefined || value === \"\") return fallback;\n\n\tconst parsed = Number(value);\n\tif (!Number.isFinite(parsed) || parsed < 0) {\n\t\tthrow new Error(`${name} must be a finite non-negative number`);\n\t}\n\n\treturn parsed;\n}\n\nfunction send(websocket: UniversalWebSocket, payload: unknown) {\n\tif (websocket.readyState !== 1) return;\n\twebsocket.send(JSON.stringify(payload));\n}\n\nfunction debugPayload(row: DebugEventRow, replayed: boolean) {\n\treturn {\n\t\ttype: \"debugEvent\",\n\t\teventId: row.event_id,\n\t\tname: row.name,\n\t\tactorId: row.actor_id,\n\t\tconnectionId: row.connection_id,\n\t\trequestId: row.request_id,\n\t\tdetails: JSON.parse(row.details_json) as Record,\n\t\tcreatedAt: row.created_at,\n\t\treplayed,\n\t};\n}\n\nfunction publishDebugEvent(row: DebugEventRow) {\n\tconst sockets = debugSocketsByActorId.get(row.actor_id);\n\tif (!sockets) return;\n\n\tfor (const socket of sockets) {\n\t\tsend(socket, debugPayload(row, false));\n\t}\n}\n\nfunction addDebugSocket(actorId: string, websocket: UniversalWebSocket) {\n\tconst sockets = debugSocketsByActorId.get(actorId) ?? new Set();\n\tsockets.add(websocket);\n\tdebugSocketsByActorId.set(actorId, sockets);\n\n\treturn () => {\n\t\tsockets.delete(websocket);\n\t\tif (sockets.size === 0) {\n\t\t\tdebugSocketsByActorId.delete(actorId);\n\t\t}\n\t};\n}\n\nasync function recordDebugEvent(c: DebugContext, input: DebugEventInput) {\n\tconst row: DebugEventRow = {\n\t\tevent_id: crypto.randomUUID(),\n\t\tname: input.name,\n\t\tactor_id: c.actorId,\n\t\tconnection_id: input.connectionId ?? null,\n\t\trequest_id: input.requestId ?? null,\n\t\tdetails_json: JSON.stringify(input.details ?? {}),\n\t\tcreated_at: input.createdAt ?? Date.now(),\n\t};\n\n\ttry {\n\t\tawait c.db.execute(\n\t\t\t\"INSERT INTO mock_agentic_debug_events (event_id, name, actor_id, connection_id, request_id, details_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t\t\trow.event_id,\n\t\t\trow.name,\n\t\t\trow.actor_id,\n\t\t\trow.connection_id,\n\t\t\trow.request_id,\n\t\t\trow.details_json,\n\t\t\trow.created_at,\n\t\t);\n\t\tpublishDebugEvent(row);\n\t} catch (error) {\n\t\tc.log.warn({\n\t\t\tmsg: \"mock agentic debug event failed\",\n\t\t\tname: input.name,\n\t\t\terr: error instanceof Error ? error.message : String(error),\n\t\t});\n\t}\n}\n\nasync function replayDebugEvents(\n\tdatabase: DebugContext[\"db\"],\n\twebsocket: UniversalWebSocket,\n) {\n\tconst rows = typedRows(\n\t\tawait database.execute(`\n\t\t\tSELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at\n\t\t\tFROM (\n\t\t\t\tSELECT event_id, name, actor_id, connection_id, request_id, details_json, created_at\n\t\t\t\tFROM mock_agentic_debug_events\n\t\t\t\tORDER BY created_at DESC\n\t\t\t\tLIMIT 200\n\t\t\t)\n\t\t\tORDER BY created_at ASC\n\t\t`),\n\t);\n\n\tfor (const row of rows) {\n\t\tsend(websocket, debugPayload(row, true));\n\t}\n}\n\nfunction verifyEntryRows(rows: EntryRow[], expectedSeconds: number) {\n\tconst seen = new Set();\n\tconst indexes = rows.map((row) => row.idx).sort((a, b) => a - b);\n\tfor (const idx of indexes) seen.add(idx);\n\n\tconst missing: number[] = [];\n\tfor (let idx = 1; idx <= expectedSeconds; idx += 1) {\n\t\tif (!seen.has(idx)) missing.push(idx);\n\t}\n\n\tconst contiguous =\n\t\trows.length === expectedSeconds &&\n\t\tmissing.length === 0 &&\n\t\tindexes.every((idx, offset) => idx === offset + 1);\n\n\treturn {\n\t\texpectedSeconds,\n\t\tcount: rows.length,\n\t\tcontiguous,\n\t\tmissing,\n\t\tindexes,\n\t\tok: contiguous,\n\t};\n}\n\nfunction verifyAllRows(rows: EntryRow[], expectedRequests: ExpectedRequest[]) {\n\tconst expectedByRequest = new Map(\n\t\texpectedRequests.map((request) => [request.requestId, request.seconds]),\n\t);\n\tconst rowsByRequest = new Map();\n\n\tfor (const row of rows) {\n\t\tconst requestRows = rowsByRequest.get(row.request_id) ?? [];\n\t\trequestRows.push(row);\n\t\trowsByRequest.set(row.request_id, requestRows);\n\t}\n\n\tconst requests = expectedRequests.map((request) => {\n\t\tconst result = verifyEntryRows(\n\t\t\trowsByRequest.get(request.requestId) ?? [],\n\t\t\trequest.seconds,\n\t\t);\n\t\treturn {\n\t\t\trequestId: request.requestId,\n\t\t\t...result,\n\t\t};\n\t});\n\n\tconst unexpectedRequestIds = [...rowsByRequest.keys()]\n\t\t.filter((requestId) => !expectedByRequest.has(requestId))\n\t\t.sort();\n\tconst expectedTotalRows = expectedRequests.reduce(\n\t\t(total, request) => total + request.seconds,\n\t\t0,\n\t);\n\tconst ok =\n\t\tunexpectedRequestIds.length === 0 &&\n\t\trows.length === expectedTotalRows &&\n\t\trequests.every((request) => request.ok);\n\n\treturn {\n\t\ttype: \"verifiedAll\",\n\t\texpectedRequests: expectedRequests.length,\n\t\texpectedTotalRows,\n\t\ttotalRows: rows.length,\n\t\trows,\n\t\tunexpectedRequestIds,\n\t\trequests,\n\t\tok,\n\t};\n}\n\nexport const mockAgenticLoop = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepGracePeriod: DEFAULT_SLEEP_GRACE_PERIOD_MS,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_entries (\n\t\t\t\t\trequest_id TEXT NOT NULL,\n\t\t\t\t\tidx INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (request_id, idx)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_mock_agentic_entries_created_at ON mock_agentic_entries(created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_sleep_state (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tsleep_started_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS mock_agentic_debug_events (\n\t\t\t\t\tevent_id TEXT PRIMARY KEY,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tactor_id TEXT NOT NULL,\n\t\t\t\t\tconnection_id TEXT,\n\t\t\t\t\trequest_id TEXT,\n\t\t\t\t\tdetails_json TEXT NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_mock_agentic_debug_events_created_at ON mock_agentic_debug_events(created_at)\",\n\t\t\t);\n\t\t},\n\t}),\n\tasync onWake(c) {\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onWake\",\n\t\t\tdetails: {\n\t\t\t\tkey: c.key,\n\t\t\t\tname: c.name,\n\t\t\t},\n\t\t});\n\t},\n\tasync onSleep(c) {\n\t\tconst delayMs = numberFromEnv(\n\t\t\t\"MOCK_AGENTIC_ON_SLEEP_DELAY_MS\",\n\t\t\tDEFAULT_ON_SLEEP_DELAY_MS,\n\t\t);\n\t\tconst sleepStartedAt = Date.now();\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onSleepStart\",\n\t\t\tcreatedAt: sleepStartedAt,\n\t\t\tdetails: {\n\t\t\t\tdelayMs,\n\t\t\t},\n\t\t});\n\t\tawait c.db.execute(\n\t\t\t\"INSERT OR REPLACE INTO mock_agentic_sleep_state (id, sleep_started_at) VALUES (1, ?)\",\n\t\t\tsleepStartedAt,\n\t\t);\n\t\tc.log.info({\n\t\t\tmsg: \"mock agentic loop onSleep delay\",\n\t\t\tdelayMs,\n\t\t\tsleepStartedAt,\n\t\t});\n\t\tawait sleep(delayMs);\n\t\tawait recordDebugEvent(c, {\n\t\t\tname: \"onSleepEnd\",\n\t\t\tdetails: {\n\t\t\t\tdelayMs,\n\t\t\t\tsleepStartedAt,\n\t\t\t\telapsedMs: Date.now() - sleepStartedAt,\n\t\t\t},\n\t\t});\n\t},\n\tasync onRequest(c, request) {\n\t\tconst url = new URL(request.url);\n\t\tif (url.pathname === \"/bypass\" || url.pathname === \"/request/bypass\") {\n\t\t\tconst [sleepState] = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"bypass\",\n\t\t\t\t\ttransport: \"http\",\n\t\t\t\t\tsleepStarted: sleepState !== undefined,\n\t\t\t\t\tsleepStartedAt: sleepState?.sleep_started_at ?? null,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\treturn new Response(\"not found\", { status: 404 });\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tconst connectionId = crypto.randomUUID();\n\t\tlet activeInference: Promise | undefined;\n\t\tconst removeDebugSocket = addDebugSocket(c.actorId, websocket);\n\n\t\tsend(websocket, {\n\t\t\ttype: \"hello\",\n\t\t\tconnectionId,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tawait replayDebugEvents(c.db, websocket);\n\t\t\t} catch (error) {\n\t\t\t\tc.log.warn({\n\t\t\t\t\tmsg: \"mock agentic debug replay failed\",\n\t\t\t\t\terr: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait recordDebugEvent(c, {\n\t\t\t\tname: \"webSocketOpen\",\n\t\t\t\tconnectionId,\n\t\t\t});\n\t\t})();\n\n\t\tconst verify = async (requestId: string, expectedSeconds: number) => {\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC\",\n\t\t\t\t\trequestId,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\ttype: \"verified\",\n\t\t\t\trequestId,\n\t\t\t\t...verifyEntryRows(rows, expectedSeconds),\n\t\t\t};\n\t\t};\n\n\t\tconst sleepStatus = async () => {\n\t\t\tconst [sleepState] = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT sleep_started_at FROM mock_agentic_sleep_state WHERE id = 1\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tsleepStarted: sleepState !== undefined,\n\t\t\t\tsleepStartedAt: sleepState?.sleep_started_at ?? null,\n\t\t\t};\n\t\t};\n\n\t\tconst runInference = async (requestId: string, seconds: number) => {\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"started\",\n\t\t\t\trequestId,\n\t\t\t\tseconds,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t});\n\n\t\t\tawait c.db.execute(\n\t\t\t\t\"DELETE FROM mock_agentic_entries WHERE request_id = ?\",\n\t\t\t\trequestId,\n\t\t\t);\n\n\t\t\tfor (let idx = 1; idx <= seconds; idx += 1) {\n\t\t\t\tawait sleep(1_000);\n\t\t\t\tconst createdAt = Date.now();\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mock_agentic_entries (request_id, idx, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\trequestId,\n\t\t\t\t\tidx,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t);\n\t\t\t\tsend(websocket, {\n\t\t\t\t\ttype: \"progress\",\n\t\t\t\t\trequestId,\n\t\t\t\t\tidx,\n\t\t\t\t\tseconds,\n\t\t\t\t\tcreatedAt,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst verification = await verify(requestId, seconds);\n\t\t\tsend(websocket, {\n\t\t\t\ttype: \"done\",\n\t\t\t\trequestId,\n\t\t\t\tseconds,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tverification,\n\t\t\t});\n\t\t};\n\n\t\twebsocket.addEventListener(\n\t\t\t\"message\",\n\t\t\tasync (event: RivetMessageEvent) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof event.data !== \"string\") {\n\t\t\t\t\t\tthrow new Error(\"message data must be a JSON string\");\n\t\t\t\t\t}\n\n\t\t\t\t\tconst message = JSON.parse(event.data) as Record<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>;\n\t\t\t\t\tconst type = stringValue(message.type, \"type\");\n\n\t\t\t\t\tif (type === \"history\") {\n\t\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY created_at ASC, request_id ASC, idx ASC\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM mock_agentic_entries\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"history\",\n\t\t\t\t\t\t\ttotalRows: count?.count ?? rows.length,\n\t\t\t\t\t\t\tentries: rows,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (type === \"ping\") {\n\t\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\tprobeId: stringValue(message.probeId, \"probeId\"),\n\t\t\t\t\t\t\t...(await sleepStatus()),\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (type === \"verify\") {\n\t\t\t\t\t\tconst requestId = stringValue(\n\t\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\t\t\"requestId\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst expectedSeconds = positiveInteger(\n\t\t\t\t\t\t\tmessage.expectedSeconds,\n\t\t\t\t\t\t\t\"expectedSeconds\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsend(\n\t\t\t\t\t\t\twebsocket,\n\t\t\t\t\t\t\tawait verify(requestId, expectedSeconds),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (type === \"infer\") {\n\t\t\t\t\t\tconst requestId = stringValue(\n\t\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\t\t\"requestId\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst seconds = positiveInteger(\n\t\t\t\t\t\t\tmessage.seconds,\n\t\t\t\t\t\t\t\"seconds\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\tawait recordDebugEvent(c, {\n\t\t\t\t\t\t\tname: \"inferenceRequested\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\tseconds,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst previousInference = activeInference;\n\t\t\t\t\t\tconst inference = (async () => {\n\t\t\t\t\t\t\tawait previousInference?.catch(() => undefined);\n\t\t\t\t\t\t\tawait runInference(requestId, seconds);\n\t\t\t\t\t\t})();\n\t\t\t\t\t\tactiveInference = inference;\n\t\t\t\t\t\tawait c.keepAwake(inference);\n\t\t\t\t\t\tif (activeInference === inference) {\n\t\t\t\t\t\t\tactiveInference = undefined;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new Error(`unknown message type: ${type}`);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tsend(websocket, {\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t\t\t: \"unknown websocket error\",\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\twebsocket.addEventListener(\"close\", async () => {\n\t\t\tremoveDebugSocket();\n\t\t\tawait recordDebugEvent(c, {\n\t\t\t\tname: \"webSocketClose\",\n\t\t\t\tconnectionId,\n\t\t\t});\n\t\t});\n\t},\n\tactions: {\n\t\tverify: async (c, requestId: string, expectedSeconds: number) => {\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries WHERE request_id = ? ORDER BY idx ASC\",\n\t\t\t\t\trequestId,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\trequestId,\n\t\t\t\texpectedSeconds,\n\t\t\t\tcount: rows.length,\n\t\t\t\tindexes: rows.map((row) => row.idx),\n\t\t\t};\n\t\t},\n\t\tverifyAll: async (c, expectedRequests: ExpectedRequest[]) => {\n\t\t\tif (!Array.isArray(expectedRequests)) {\n\t\t\t\tthrow new Error(\"expectedRequests must be an array\");\n\t\t\t}\n\n\t\t\tfor (const request of expectedRequests) {\n\t\t\t\tstringValue(request.requestId, \"requestId\");\n\t\t\t\tpositiveInteger(request.seconds, \"seconds\");\n\t\t\t}\n\n\t\t\tconst rows = typedRows(\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"SELECT request_id, idx, created_at FROM mock_agentic_entries ORDER BY request_id ASC, idx ASC\",\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn verifyAllRows(rows, expectedRequests);\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\ntype WorkloadMode =\n\t| \"balanced\"\n\t| \"hot\"\n\t| \"transactions\"\n\t| \"payloads\"\n\t| \"edge\"\n\t| \"fragmentation\"\n\t| \"schema\"\n\t| \"index\"\n\t| \"relational\"\n\t| \"constraints\"\n\t| \"savepoints\"\n\t| \"pragma\"\n\t| \"prepared\"\n\t| \"growth\"\n\t| \"readwrite\"\n\t| \"truncate\"\n\t| \"boundary-keys\"\n\t| \"shadow\"\n\t| \"actual-nul\"\n\t| \"nasty-script\"\n\t| \"nasty\"\n\t| \"kitchen-sink\";\n\ninterface RunPhaseInput {\n\tseed: string;\n\tphase: number;\n\titerations: number;\n\tmode?: WorkloadMode;\n\tmaxPayloadBytes?: number;\n\tgrowthTargetBytes?: number;\n\tkeySpace?: number;\n}\n\ninterface ValidationSummary {\n\ttotalEvents: number;\n\tactiveRows: number;\n\texpectedRows: number;\n\tmissingRows: number;\n\textraRows: number;\n\tmismatchedRows: number;\n\tduplicateKeys: number;\n\tactualVersionSum: number;\n\texpectedVersionSum: number;\n\tactualPayloadChecksumSum: number;\n\texpectedPayloadChecksumSum: number;\n\taccountCount: number;\n\taccountBalanceSum: number;\n\texpectedAccountBalanceSum: number;\n\taccountBalanceMismatch: number;\n\tintegrityCheck: string;\n\tquickCheck: string;\n\tedgeRows: number;\n\tedgeExpectedRows: number;\n\tedgeMismatches: number;\n\tindexRows: number;\n\tindexMismatches: number;\n\trelationalOrders: number;\n\trelationalMismatches: number;\n\tconstraintAttempts: number;\n\tconstraintLeaks: number;\n\tsavepointRows: number;\n\tsavepointMismatches: number;\n\tidempotentOps: number;\n\tidempotentMismatches: number;\n\tschemaObjects: number;\n\tschemaMissingObjects: number;\n\tprobeRows: number;\n\tprobeMismatches: number;\n\tpreparedRows: number;\n\tpreparedMismatches: number;\n\tshadowRows: number;\n\tshadowMismatches: number;\n}\n\ninterface ItemMismatchDebugRow {\n\titemKey: string;\n\tactualValue: string | null;\n\texpectedValue: string | null;\n\tactualVersion: number | null;\n\texpectedVersion: number | null;\n\tactualUpdateCount: number | null;\n\texpectedUpdateCount: number | null;\n\tactualPayloadChecksum: number | null;\n\texpectedPayloadChecksum: number | null;\n\tactualPayloadBytes: number | null;\n\texpectedPayloadBytes: number | null;\n}\n\ninterface ItemEventDebugRow {\n\tseq: number;\n\tphase: number;\n\tlocalIndex: number;\n\tkind: string;\n\tpresent: number;\n\tvalue: string | null;\n\tversion: number;\n\tupdateCount: number;\n\tpayloadChecksum: number;\n\tpayloadBytes: number;\n\tapplied: number;\n}\n\ninterface PhaseResult {\n\tseed: string;\n\tphase: number;\n\tmode: WorkloadMode;\n\titerations: number;\n\tops: Record;\n\tvalidation: ValidationSummary;\n}\n\ninterface ItemRow {\n\titem_key: string;\n\tvalue: string;\n\tversion: number;\n\tupdate_count: number;\n\tpayload?: string;\n\tpayload_checksum: number;\n\tpayload_bytes: number;\n}\n\nconst ACCOUNT_COUNT = 8;\nconst ACCOUNT_INITIAL_BALANCE = 100_000;\nconst DEFAULT_KEY_SPACE = 64;\nconst DEFAULT_MAX_PAYLOAD_BYTES = 8 * 1024;\nconst DEFAULT_GROWTH_TARGET_BYTES = 1024 * 1024;\nconst LARGE_WRITE_CHUNK_BYTES = 96 * 1024;\nconst PAGE_BOUNDARY_SIZES = [\n\t1, 4095, 4096, 4097, 8191, 8192, 8193, 32768, 65535, 65536, 98304, 131072,\n];\n\nfunction hashSeed(input: string): number {\n\tlet hash = 2166136261;\n\tfor (let i = 0; i < input.length; i += 1) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction makeRng(seed: string): () => number {\n\tlet state = hashSeed(seed) || 0x9e3779b9;\n\treturn () => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = state;\n\t\tt = Math.imul(t ^ (t >>> 15), t | 1);\n\t\tt ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\nfunction intBetween(rng: () => number, min: number, max: number): number {\n\treturn min + Math.floor(rng() * (max - min + 1));\n}\n\nfunction checksum(input: string): number {\n\tlet hash = 2166136261;\n\tfor (let i = 0; i < input.length; i += 1) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 16777619);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction payloadFor(\n\tseed: string,\n\tphase: number,\n\tindex: number,\n\tbytes: number,\n): string {\n\tconst prefix = `${seed}:${phase}:${index}:`;\n\tif (bytes <= prefix.length) return prefix.slice(0, bytes);\n\treturn prefix + \"x\".repeat(bytes - prefix.length);\n}\n\nasync function queryOne(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tsql: string,\n\t...args: unknown[]\n): Promise {\n\tconst rows = await database.execute(sql, ...args);\n\treturn rows[0] as T | undefined;\n}\n\nasync function transaction(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tfn: () => Promise,\n): Promise {\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tconst result = await fn();\n\t\tawait database.execute(\"COMMIT\");\n\t\treturn result;\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n}\n\nasync function recordProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n\tscenario: string,\n\tname: string,\n\texpected: string | number,\n\tactual: string | number,\n\tmismatch: boolean,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_probe_results (\n\t\t\tphase, scenario, name, expected, actual, mismatch, created_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n\t\tphase,\n\t\tscenario,\n\t\tname,\n\t\tString(expected),\n\t\tString(actual),\n\t\tmismatch ? 1 : 0,\n\t\tDate.now(),\n\t);\n}\n\nfunction firstColumn(row: unknown): unknown {\n\tif (!row || typeof row !== \"object\") return undefined;\n\tconst values = Object.values(row as Record);\n\treturn values[0];\n}\n\nasync function ensureAccounts(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}): Promise {\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < ACCOUNT_COUNT; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_accounts (id, balance) VALUES (?, ?)\",\n\t\t\t\t`acct-${i}`,\n\t\t\t\tACCOUNT_INITIAL_BALANCE,\n\t\t\t);\n\t\t}\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n}\n\nasync function recordItemEvent(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n\tlocalIndex: number,\n\tkind: string,\n\titemKey: string,\n\tpresent: boolean,\n\tvalue: string | null,\n\tversion: number,\n\tupdateCount: number,\n\tpayload: string,\n\tapplied: boolean,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_item_events (\n\t\t\tphase, local_index, kind, item_key, present, value, version,\n\t\t\tupdate_count, payload_checksum, payload_bytes, applied, created_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\tphase,\n\t\tlocalIndex,\n\t\tkind,\n\t\titemKey,\n\t\tpresent ? 1 : 0,\n\t\tvalue,\n\t\tversion,\n\t\tupdateCount,\n\t\tchecksum(payload),\n\t\tpayload.length,\n\t\tapplied ? 1 : 0,\n\t\tDate.now(),\n\t);\n}\n\nasync function upsertLiveItem(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\trow: ItemRow,\n\tpayload: string,\n): Promise {\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_items (\n\t\t\titem_key, value, version, update_count, payload, payload_checksum,\n\t\t\tpayload_bytes, updated_at\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n\t\tON CONFLICT(item_key) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\tversion = excluded.version,\n\t\t\tupdate_count = excluded.update_count,\n\t\t\tpayload = excluded.payload,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\tupdated_at = excluded.updated_at`,\n\t\trow.item_key,\n\t\trow.value,\n\t\trow.version,\n\t\trow.update_count,\n\t\tpayload,\n\t\trow.payload_checksum,\n\t\trow.payload_bytes,\n\t\tDate.now(),\n\t);\n}\n\nasync function applyItemOperation(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\tkind: \"insert\" | \"update\" | \"delete\" | \"upsert\";\n\t\titemKey: string;\n\t\tpayloadBytes: number;\n\t},\n): Promise {\n\tlet current: ItemRow | undefined;\n\ttry {\n\t\tcurrent = await queryOne(\n\t\t\tdatabase,\n\t\t\t\"SELECT item_key, value, version, update_count, payload, payload_checksum, payload_bytes FROM fuzz_items WHERE item_key = ?\",\n\t\t\topts.itemKey,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation select failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\tconst payload = payloadFor(\n\t\topts.seed,\n\t\topts.phase,\n\t\topts.localIndex,\n\t\topts.payloadBytes,\n\t);\n\tconst nextVersion = (current?.version ?? 0) + 1;\n\tconst nextUpdateCount = (current?.update_count ?? 0) + 1;\n\tconst nextValue = `${opts.kind}:${opts.phase}:${opts.localIndex}:${nextVersion}`;\n\n\tif (opts.kind === \"delete\") {\n\t\ttry {\n\t\t\tawait recordItemEvent(\n\t\t\t\tdatabase,\n\t\t\t\topts.phase,\n\t\t\t\topts.localIndex,\n\t\t\t\topts.kind,\n\t\t\t\topts.itemKey,\n\t\t\t\tfalse,\n\t\t\t\tnull,\n\t\t\t\tnextVersion,\n\t\t\t\tnextUpdateCount,\n\t\t\t\t\"\",\n\t\t\t\tcurrent !== undefined,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`item operation event insert failed for delete key ${JSON.stringify(opts.itemKey)}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t\"DELETE FROM fuzz_items WHERE item_key = ?\",\n\t\t\t\topts.itemKey,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`item operation delete failed for key ${JSON.stringify(opts.itemKey)}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (opts.kind === \"insert\" && current) {\n\t\ttry {\n\t\t\tawait recordItemEvent(\n\t\t\t\tdatabase,\n\t\t\t\topts.phase,\n\t\t\t\topts.localIndex,\n\t\t\t\topts.kind,\n\t\t\t\topts.itemKey,\n\t\t\t\ttrue,\n\t\t\t\tcurrent.value,\n\t\t\t\tcurrent.version,\n\t\t\t\tcurrent.update_count,\n\t\t\t\tcurrent.payload ?? \"\",\n\t\t\t\tfalse,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`item operation event insert failed for noop insert key ${JSON.stringify(opts.itemKey)}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\treturn;\n\t}\n\n\tconst row: ItemRow = {\n\t\titem_key: opts.itemKey,\n\t\tvalue: nextValue,\n\t\tversion: nextVersion,\n\t\tupdate_count: nextUpdateCount,\n\t\tpayload_checksum: checksum(payload),\n\t\tpayload_bytes: payload.length,\n\t};\n\n\ttry {\n\t\tawait recordItemEvent(\n\t\t\tdatabase,\n\t\t\topts.phase,\n\t\t\topts.localIndex,\n\t\t\topts.kind,\n\t\t\topts.itemKey,\n\t\t\ttrue,\n\t\t\trow.value,\n\t\t\trow.version,\n\t\t\trow.update_count,\n\t\t\tpayload,\n\t\t\ttrue,\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation event insert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\ttry {\n\t\tawait upsertLiveItem(database, row, payload);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`item operation live-row upsert failed for kind ${opts.kind} key ${JSON.stringify(opts.itemKey)} payloadBytes ${opts.payloadBytes}`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n\nasync function applyHotUpdates(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\titemKey: string;\n\t\tupdates: number;\n\t\tpayloadBytes: number;\n\t},\n): Promise {\n\tfor (let i = 0; i < opts.updates; i += 1) {\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: opts.localIndex * 1000 + i,\n\t\t\t\tkind: \"update\",\n\t\t\t\titemKey: opts.itemKey,\n\t\t\t\tpayloadBytes: opts.payloadBytes,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`hot update failed for ${opts.itemKey} at sub-update ${i + 1}/${opts.updates} with payloadBytes ${opts.payloadBytes}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function applyTransfer(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\tfromAccount: string;\n\t\ttoAccount: string;\n\t\tamount: number;\n\t},\n): Promise {\n\tawait transaction(database, async () => {\n\t\tconst before = await queryOne<{ total: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_accounts SET balance = balance - ? WHERE id = ?\",\n\t\t\topts.amount,\n\t\t\topts.fromAccount,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_accounts SET balance = balance + ? WHERE id = ?\",\n\t\t\topts.amount,\n\t\t\topts.toAccount,\n\t\t);\n\t\tconst after = await queryOne<{ total: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COALESCE(SUM(balance), 0) AS total FROM fuzz_accounts\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_transfer_events (\n\t\t\t\tphase, local_index, from_account, to_account, amount,\n\t\t\t\tbalance_sum_before, balance_sum_after, created_at\n\t\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\topts.phase,\n\t\t\topts.localIndex,\n\t\t\topts.fromAccount,\n\t\t\topts.toAccount,\n\t\t\topts.amount,\n\t\t\tbefore?.total ?? 0,\n\t\t\tafter?.total ?? 0,\n\t\t\tDate.now(),\n\t\t);\n\t});\n}\n\nasync function applyEdgePayloads(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst writeEdgePayload = async (\n\t\tid: string,\n\t\tkind: string,\n\t\tpayload: string,\n\t\tsizeLabel: string,\n\t): Promise => {\n\t\tconst payloadChecksum = checksum(payload);\n\t\tconst payloadBytes = payload.length;\n\t\ttry {\n\t\t\tawait database.execute(\"BEGIN\");\n\t\t} catch (error) {\n\t\t\tthrow new Error(`edge payload begin failed for ${sizeLabel}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\ttry {\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t\t) VALUES (?, ?, ?, ?, ?, ?)\n\t\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\t\tkind = excluded.kind,\n\t\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\t\tid,\n\t\t\t\t\tkind,\n\t\t\t\t\tpayload,\n\t\t\t\t\tpayloadChecksum,\n\t\t\t\t\tpayloadBytes,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`edge payload row upsert failed for ${sizeLabel}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tcause: error,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\t\tpresent = excluded.present,\n\t\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\t\t\tid,\n\t\t\t\t\tpayloadChecksum,\n\t\t\t\t\tpayloadBytes,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`edge payload expectation upsert failed for ${sizeLabel}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tcause: error,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait database.execute(\"COMMIT\");\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`edge payload commit failed for ${sizeLabel}`, {\n\t\t\t\t\tcause: error,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst sizes = PAGE_BOUNDARY_SIZES.filter(\n\t\t(size) => size <= opts.maxPayloadBytes,\n\t);\n\tif (!sizes.includes(opts.maxPayloadBytes)) sizes.push(opts.maxPayloadBytes);\n\n\tlet ops = 0;\n\tfor (const size of sizes) {\n\t\tconst id = `edge-${opts.phase}-${size}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, size, size);\n\t\ttry {\n\t\t\tawait writeEdgePayload(id, \"boundary\", payload, `size ${size}`);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`edge payload write failed for size ${size}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tops += 1;\n\t}\n\n\tconst unicodePayload = `escaped-nul:\\\\0 unicode:☃️ phase:${opts.phase} seed:${opts.seed}`;\n\tconst unicodeId = `edge-${opts.phase}-unicode-nul`;\n\ttry {\n\t\tawait writeEdgePayload(\n\t\t\tunicodeId,\n\t\t\t\"unicode-nul\",\n\t\t\tunicodePayload,\n\t\t\t\"unicode escaped-nul payload\",\n\t\t);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t\"edge payload write failed for unicode escaped-nul payload\",\n\t\t\t{\n\t\t\t\tcause: error,\n\t\t\t},\n\t\t);\n\t}\n\n\treturn ops + 1;\n}\n\nasync function applyActualNulPayload(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t},\n): Promise {\n\tconst payload = `actual-nul:\\0 phase:${opts.phase} seed:${opts.seed}`;\n\tconst id = `actual-nul-${opts.phase}`;\n\tawait transaction(database, async () => {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t) VALUES (?, 'actual-nul', ?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\tid,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t\tDate.now(),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tid,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t);\n\t});\n\treturn 1;\n}\n\nasync function applyFragmentationChurn(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst rows = Math.max(12, Math.floor(opts.iterations / 2));\n\tlet ops = 0;\n\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst size = intBetween(\n\t\t\topts.rng,\n\t\t\t32,\n\t\t\tMath.max(32, opts.maxPayloadBytes),\n\t\t);\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 10_000 + i, size);\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t) VALUES (?, 'fragment', ?, ?, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\tid,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation payload upsert failed for ${id} at size ${size}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpresent = 1,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\t\tid,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation expectation upsert failed for ${id} at size ${size}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\n\tfor (let i = 0; i < rows; i += 3) {\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t\"DELETE FROM fuzz_edge_payloads WHERE id = ?\",\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`fragmentation delete failed for ${id}`, {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t\t) VALUES (?, 0, 0, 0)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpresent = 0,\n\t\t\t\t\tpayload_checksum = 0,\n\t\t\t\t\tpayload_bytes = 0`,\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation tombstone expectation failed for ${id}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\n\tfor (let i = 1; i < rows; i += 4) {\n\t\tconst size = intBetween(opts.rng, 1, Math.max(1, opts.maxPayloadBytes));\n\t\tconst id = `frag-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 20_000 + i, size);\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_edge_payloads\n\t\t\t\tSET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ?\n\t\t\t\tWHERE id = ?`,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation payload rewrite failed for ${id} at size ${size}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_edge_expectations\n\t\t\t\tSET payload_checksum = ?, payload_bytes = ?\n\t\t\t\tWHERE id = ? AND present = 1`,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tid,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation expectation rewrite failed for ${id} at size ${size}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\n\tif (opts.phase % 2 === 1) {\n\t\ttry {\n\t\t\tawait database.execute(\"VACUUM\");\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`fragmentation vacuum failed for phase ${opts.phase}`,\n\t\t\t\t{\n\t\t\t\t\tcause: error,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\n\treturn ops;\n}\n\nasync function applySchemaChurn(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tconst table = `fuzz_schema_phase_${phase}`;\n\tconst index = `idx_fuzz_schema_phase_${phase}_name`;\n\tconst view = `view_fuzz_schema_phase_${phase}`;\n\tconst dropIndex = `idx_fuzz_schema_drop_probe_${phase}`;\n\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS ${table} (\n\t\t\tid INTEGER PRIMARY KEY,\n\t\t\tname TEXT NOT NULL UNIQUE,\n\t\t\tvalue INTEGER NOT NULL DEFAULT 0,\n\t\t\textra TEXT\n\t\t)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS ${index} ON ${table}(name, value)`,\n\t);\n\ttry {\n\t\tawait database.execute(\n\t\t\t`ALTER TABLE ${table} ADD COLUMN altered_${phase} TEXT DEFAULT 'altered'`,\n\t\t);\n\t} catch {\n\t\tconst column = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t`SELECT COUNT(*) AS count FROM pragma_table_info('${table}') WHERE name = ?`,\n\t\t\t`altered_${phase}`,\n\t\t);\n\t\tif ((column?.count ?? 0) !== 1)\n\t\t\tthrow new Error(`failed to add altered_${phase}`);\n\t}\n\tawait database.execute(\n\t\t`CREATE VIEW IF NOT EXISTS ${view} AS SELECT id, name, value FROM ${table}`,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO ${table} (name, value, extra)\n\t\tVALUES (?, ?, ?)\n\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\textra = excluded.extra`,\n\t\t`schema-${phase}`,\n\t\tphase,\n\t\t`extra-${phase}`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS fuzz_without_rowid (\n\t\t\tid TEXT PRIMARY KEY,\n\t\t\tvalue INTEGER NOT NULL\n\t\t) WITHOUT ROWID`,\n\t);\n\tawait database.execute(`\n\t\tCREATE TRIGGER IF NOT EXISTS trg_fuzz_edge_payload_update\n\t\tAFTER UPDATE ON fuzz_edge_payloads\n\t\tBEGIN\n\t\t\tINSERT INTO fuzz_trigger_audit (\n\t\t\t\tpayload_id, old_checksum, new_checksum, created_at\n\t\t\t) VALUES (\n\t\t\t\tnew.id, old.payload_checksum, new.payload_checksum, strftime('%s', 'now') * 1000\n\t\t\t);\n\t\tEND\n\t`);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_without_rowid (id, value)\n\t\tVALUES (?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET value = excluded.value`,\n\t\t`phase-${phase}`,\n\t\tphase,\n\t);\n\n\tfor (const [name, type] of [\n\t\t[table, \"table\"],\n\t\t[index, \"index\"],\n\t\t[view, \"view\"],\n\t\t[\"trg_fuzz_edge_payload_update\", \"trigger\"],\n\t\t[\"fuzz_without_rowid\", \"table\"],\n\t] as const) {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_schema_registry (name, type)\n\t\t\tVALUES (?, ?)\n\t\t\tON CONFLICT(name) DO UPDATE SET type = excluded.type`,\n\t\t\tname,\n\t\t\ttype,\n\t\t);\n\t}\n\n\tawait database.execute(\n\t\t\"CREATE TEMP TABLE IF NOT EXISTS fuzz_temp_probe (id INTEGER PRIMARY KEY, value TEXT)\",\n\t);\n\tawait database.execute(\n\t\t\"INSERT INTO fuzz_temp_probe (value) VALUES (?)\",\n\t\t`temp-${phase}`,\n\t);\n\tawait database.execute(\"DROP TABLE fuzz_temp_probe\");\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS ${dropIndex} ON fuzz_schema_registry(type)`,\n\t);\n\tawait database.execute(`DROP INDEX IF EXISTS ${dropIndex}`);\n\tconst dropped = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = ?\",\n\t\tdropIndex,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"schema\",\n\t\t\"drop-index\",\n\t\t0,\n\t\tdropped?.count ?? -1,\n\t\t(dropped?.count ?? -1) !== 0,\n\t);\n\n\treturn 13;\n}\n\nasync function applyIndexProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t},\n): Promise {\n\tconst rows = Math.max(20, opts.iterations);\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < rows; i += 1) {\n\t\t\tconst tenant = `tenant-${intBetween(opts.rng, 0, 5)}`;\n\t\t\tconst bucket = intBetween(opts.rng, 0, 12);\n\t\t\tconst score = intBetween(opts.rng, -500, 500);\n\t\t\tconst label = `${opts.seed}:${opts.phase}:${i}`;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_indexed (tenant, bucket, score, label, payload)\n\t\t\t\tVALUES (?, ?, ?, ?, ?)`,\n\t\t\t\ttenant,\n\t\t\t\tbucket,\n\t\t\t\tscore,\n\t\t\t\tlabel,\n\t\t\t\tpayloadFor(\n\t\t\t\t\topts.seed,\n\t\t\t\t\topts.phase,\n\t\t\t\t\t30_000 + i,\n\t\t\t\t\tintBetween(opts.rng, 8, 256),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t});\n\treturn rows;\n}\n\nasync function applyPreparedChurn(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\titerations: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst rows = Math.max(32, opts.iterations);\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst id = `prep-${opts.phase}-${i}`;\n\t\tconst payload = payloadFor(\n\t\t\topts.seed,\n\t\t\topts.phase,\n\t\t\t70_000 + i,\n\t\t\tMath.min(opts.maxPayloadBytes, 64 + (i % 257)),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum)\n\t\t\tVALUES (?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tvalue = excluded.value,\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum\n\t\t\t/* unique-prepared-${opts.phase}-${i} */`,\n\t\t\tid,\n\t\t\ti,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum)\n\t\t\tVALUES (?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tvalue = excluded.value,\n\t\t\t\tpayload_checksum = excluded.payload_checksum`,\n\t\t\tid,\n\t\t\ti,\n\t\t\tchecksum(payload),\n\t\t);\n\t}\n\n\tconst repeatedId = `prep-repeat-${opts.phase}`;\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_prepared_churn (id, value, payload, payload_checksum)\n\t\tVALUES (?, 0, '', 0)\n\t\tON CONFLICT(id) DO UPDATE SET value = 0, payload = '', payload_checksum = 0`,\n\t\trepeatedId,\n\t);\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst payload = payloadFor(\n\t\t\topts.seed,\n\t\t\topts.phase,\n\t\t\t80_000 + i,\n\t\t\tMath.min(512, opts.maxPayloadBytes),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`UPDATE fuzz_prepared_churn\n\t\t\tSET value = value + ?, payload = ?, payload_checksum = ?\n\t\t\tWHERE id = ?`,\n\t\t\t1,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\trepeatedId,\n\t\t);\n\t}\n\tconst finalPayload = payloadFor(\n\t\topts.seed,\n\t\topts.phase,\n\t\t80_000 + rows - 1,\n\t\tMath.min(512, opts.maxPayloadBytes),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_prepared_expectations (id, value, payload_checksum)\n\t\tVALUES (?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tvalue = excluded.value,\n\t\t\tpayload_checksum = excluded.payload_checksum`,\n\t\trepeatedId,\n\t\trows,\n\t\tchecksum(finalPayload),\n\t);\n\n\treturn rows * 2 + 1;\n}\n\nasync function applyReadWriteProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\trng: () => number;\n\t\titerations: number;\n\t},\n): Promise {\n\tif (\n\t\t(\n\t\t\tawait queryOne<{ count: number }>(\n\t\t\t\tdatabase,\n\t\t\t\t\"SELECT COUNT(*) AS count FROM fuzz_indexed\",\n\t\t\t)\n\t\t)?.count === 0\n\t) {\n\t\tawait applyIndexProbe(database, opts);\n\t}\n\n\tconst read = database.execute(\n\t\t`SELECT\n\t\t\tCOUNT(*) AS joined_rows,\n\t\t\tCOALESCE(SUM(a.score + b.score), 0) AS score_sum\n\t\tFROM fuzz_indexed a\n\t\tJOIN fuzz_indexed b ON b.bucket = a.bucket\n\t\tWHERE a.tenant <= 'tenant-3'`,\n\t);\n\tconst write = applyIndexProbe(database, {\n\t\tseed: opts.seed,\n\t\tphase: opts.phase,\n\t\trng: opts.rng,\n\t\titerations: Math.max(10, Math.floor(opts.iterations / 2)),\n\t});\n\tconst [readRows, writeOps] = await Promise.all([read, write]);\n\tconst row = readRows[0] as\n\t\t| { joined_rows?: number; score_sum?: number }\n\t\t| undefined;\n\tconst joinedRows = Number(row?.joined_rows ?? -1);\n\tconst scoreSum = Number(row?.score_sum ?? Number.NaN);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"readwrite\",\n\t\t\"long-read-while-write\",\n\t\t\"nonnegative-finite\",\n\t\t`${joinedRows}:${scoreSum}`,\n\t\tjoinedRows < 0 || !Number.isFinite(scoreSum),\n\t);\n\treturn writeOps + 1;\n}\n\nasync function applyBoundaryKeys(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst keys = [\n\t\t\"\",\n\t\t\" \",\n\t\t`long-${\"k\".repeat(2048)}`,\n\t\t\"slash/key\",\n\t\t\"comma,key\",\n\t\t\"percent%key\",\n\t\t\"CaseKey\",\n\t\t\"casekey\",\n\t];\n\tlet ops = 0;\n\tfor (const [index, key] of keys.entries()) {\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 90_000 + index,\n\t\t\t\tkind: \"upsert\",\n\t\t\t\titemKey: key,\n\t\t\t\tpayloadBytes: Math.min(opts.maxPayloadBytes, 128 + index),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`boundary key write failed for literal key ${JSON.stringify(key)} at index ${index}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\tfor (let i = 0; i < 128; i += 1) {\n\t\tconst itemKey = `seq-${opts.phase}-${i.toString().padStart(4, \"0\")}`;\n\t\ttry {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 91_000 + i,\n\t\t\t\tkind: i % 4 === 0 ? \"delete\" : \"upsert\",\n\t\t\t\titemKey,\n\t\t\t\tpayloadBytes: Math.min(opts.maxPayloadBytes, 32 + (i % 97)),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`boundary key write failed for sequential key ${JSON.stringify(itemKey)} at index ${i}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"boundary-keys\",\n\t\t\"keys-written\",\n\t\t136,\n\t\tops,\n\t\tops !== 136,\n\t);\n\treturn ops;\n}\n\nasync function applyGrowthProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t\tgrowthTargetBytes: number;\n\t},\n): Promise {\n\tconst chunkBytes = Math.max(\n\t\t1,\n\t\tMath.min(LARGE_WRITE_CHUNK_BYTES, opts.maxPayloadBytes),\n\t);\n\tconst rows = Math.max(1, Math.ceil(opts.growthTargetBytes / chunkBytes));\n\tlet written = 0;\n\tfor (let i = 0; i < rows; i += 1) {\n\t\tconst size = Math.min(chunkBytes, opts.growthTargetBytes - written);\n\t\tconst id = `growth-${opts.phase}-${opts.growthTargetBytes}-${i}`;\n\t\tconst payload = payloadFor(opts.seed, opts.phase, 100_000 + i, size);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t) VALUES (?, 'growth', ?, ?, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpayload = excluded.payload,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\tid,\n\t\t\tpayload,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t\tDate.now(),\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tid,\n\t\t\tchecksum(payload),\n\t\t\tpayload.length,\n\t\t);\n\t\twritten += size;\n\t}\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"growth\",\n\t\t\"target-bytes-written\",\n\t\topts.growthTargetBytes,\n\t\twritten,\n\t\twritten !== opts.growthTargetBytes,\n\t);\n\treturn rows;\n}\n\nasync function applyTruncateRecreateProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tconst id = `truncate-${opts.phase}`;\n\tconst largeSize = Math.max(1, Math.min(opts.maxPayloadBytes, 131072));\n\tconst largePayload = payloadFor(opts.seed, opts.phase, 110_000, largeSize);\n\tconst tinyPayload = payloadFor(opts.seed, opts.phase, 110_001, 1);\n\tconst recreatedPayload = payloadFor(\n\t\topts.seed,\n\t\topts.phase,\n\t\t110_002,\n\t\tMath.min(4096, largeSize),\n\t);\n\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t) VALUES (?, 'truncate', ?, ?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tpayload = excluded.payload,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\tupdated_at = excluded.updated_at`,\n\t\tid,\n\t\tlargePayload,\n\t\tchecksum(largePayload),\n\t\tlargePayload.length,\n\t\tDate.now(),\n\t);\n\tawait database.execute(\n\t\t\"UPDATE fuzz_edge_payloads SET payload = ?, payload_checksum = ?, payload_bytes = ?, updated_at = ? WHERE id = ?\",\n\t\ttinyPayload,\n\t\tchecksum(tinyPayload),\n\t\ttinyPayload.length,\n\t\tDate.now(),\n\t\tid,\n\t);\n\tawait database.execute(\"DELETE FROM fuzz_edge_payloads WHERE id = ?\", id);\n\tawait database.execute(\"VACUUM\");\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t) VALUES (?, 'truncate', ?, ?, ?, ?)`,\n\t\tid,\n\t\trecreatedPayload,\n\t\tchecksum(recreatedPayload),\n\t\trecreatedPayload.length,\n\t\tDate.now(),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\tid, present, payload_checksum, payload_bytes\n\t\t) VALUES (?, 1, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tpresent = 1,\n\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\tid,\n\t\tchecksum(recreatedPayload),\n\t\trecreatedPayload.length,\n\t);\n\treturn 5;\n}\n\nasync function updateShadowChecksums(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tconst item = await queryOne<{ rows: number; value: number }>(\n\t\tdatabase,\n\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + version + update_count), 0) AS value\n\t\tFROM fuzz_items`,\n\t);\n\tconst edge = await queryOne<{ rows: number; value: number }>(\n\t\tdatabase,\n\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(payload_checksum + payload_bytes), 0) AS value\n\t\tFROM fuzz_edge_payloads`,\n\t);\n\tawait transaction(database, async () => {\n\t\tfor (const [name, rows, value] of [\n\t\t\t[\"items\", item?.rows ?? 0, item?.value ?? 0],\n\t\t\t[\"edge\", edge?.rows ?? 0, edge?.value ?? 0],\n\t\t] as const) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_shadow_checksums (name, value, row_count)\n\t\t\t\tVALUES (?, ?, ?)\n\t\t\t\tON CONFLICT(name) DO UPDATE SET\n\t\t\t\t\tvalue = excluded.value,\n\t\t\t\t\trow_count = excluded.row_count`,\n\t\t\t\tname,\n\t\t\t\tvalue,\n\t\t\t\trows,\n\t\t\t);\n\t\t}\n\t});\n\tawait recordProbe(database, phase, \"shadow\", \"shadow-updated\", 2, 2, false);\n\treturn 2;\n}\n\nasync function applyConstraintChaos(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tawait database.execute(\"PRAGMA foreign_keys = ON\");\n\tconst validPrefix = `valid-${phase}`;\n\tconst existingValidRows = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints WHERE id LIKE ?\",\n\t\t`${validPrefix}-%`,\n\t);\n\tconst runSeq = existingValidRows?.count ?? 0;\n\tconst validId = `${validPrefix}-${runSeq}`;\n\tconst uniqValue = `uniq-${phase}-${runSeq}`;\n\tconst before = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq)\n\t\tVALUES (?, ?, ?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\tmust_not_null = excluded.must_not_null,\n\t\t\tqty = excluded.qty,\n\t\t\tuniq = excluded.uniq`,\n\t\tvalidId,\n\t\t\"ok\",\n\t\tphase,\n\t\tuniqValue,\n\t);\n\n\tconst attempts: Array<{\n\t\tname: string;\n\t\tsql: string;\n\t\targs: unknown[];\n\t}> = [\n\t\t{\n\t\t\tname: `not-null-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [\n\t\t\t\t`bad-null-${phase}-${runSeq}`,\n\t\t\t\tnull,\n\t\t\t\t1,\n\t\t\t\t`bad-null-${phase}-${runSeq}`,\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: `check-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [\n\t\t\t\t`bad-check-${phase}-${runSeq}`,\n\t\t\t\t\"ok\",\n\t\t\t\t-1,\n\t\t\t\t`bad-check-${phase}-${runSeq}`,\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: `unique-${phase}-${runSeq}`,\n\t\t\tsql: \"INSERT INTO fuzz_constraints (id, must_not_null, qty, uniq) VALUES (?, ?, ?, ?)\",\n\t\t\targs: [`bad-unique-${phase}-${runSeq}`, \"ok\", 1, uniqValue],\n\t\t},\n\t];\n\n\tfor (const attempt of attempts) {\n\t\tconst attemptBefore = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t\t);\n\t\tlet failed = false;\n\t\ttry {\n\t\t\tawait database.execute(attempt.sql, ...attempt.args);\n\t\t} catch {\n\t\t\tfailed = true;\n\t\t}\n\t\tconst attemptAfter = await queryOne<{ count: number }>(\n\t\t\tdatabase,\n\t\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t\t);\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t\t) VALUES (?, 1, ?, ?, ?)`,\n\t\t\tattempt.name,\n\t\t\tfailed ? 1 : 0,\n\t\t\tattemptBefore?.count ?? 0,\n\t\t\tattemptAfter?.count ?? 0,\n\t\t);\n\t}\n\n\tconst after = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_constraints\",\n\t);\n\tif ((after?.count ?? 0) !== (before?.count ?? 0) + 1) {\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t\t) VALUES (?, 0, 0, ?, ?)`,\n\t\t\t`valid-count-${phase}-${runSeq}`,\n\t\t\tbefore?.count ?? 0,\n\t\t\tafter?.count ?? 0,\n\t\t);\n\t}\n\n\tconst parentId = `fk-parent-${phase}-${runSeq}`;\n\tconst childId = `fk-child-${phase}-${runSeq}`;\n\tawait database.execute(\n\t\t\"INSERT INTO fuzz_fk_parent (id) VALUES (?) ON CONFLICT(id) DO NOTHING\",\n\t\tparentId,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_fk_child (id, parent_id)\n\t\tVALUES (?, ?)\n\t\tON CONFLICT(id) DO UPDATE SET parent_id = excluded.parent_id`,\n\t\tchildId,\n\t\tparentId,\n\t);\n\tconst childBeforeDelete = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?\",\n\t\tparentId,\n\t);\n\tawait database.execute(\"DELETE FROM fuzz_fk_parent WHERE id = ?\", parentId);\n\tconst childAfterDelete = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child WHERE parent_id = ?\",\n\t\tparentId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"constraints\",\n\t\t\"fk-cascade-delete\",\n\t\t0,\n\t\tchildAfterDelete?.count ?? -1,\n\t\t(childBeforeDelete?.count ?? 0) !== 1 ||\n\t\t\t(childAfterDelete?.count ?? -1) !== 0,\n\t);\n\n\tconst fkBefore = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child\",\n\t);\n\tlet fkFailed = false;\n\ttry {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_fk_child (id, parent_id) VALUES (?, ?)\",\n\t\t\t`fk-orphan-${phase}-${runSeq}`,\n\t\t\t`missing-parent-${phase}-${runSeq}`,\n\t\t);\n\t} catch {\n\t\tfkFailed = true;\n\t}\n\tconst fkAfter = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_fk_child\",\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\tphase,\n\t\t\"constraints\",\n\t\t\"fk-failure-isolation\",\n\t\t`${fkBefore?.count ?? 0}:failed`,\n\t\t`${fkAfter?.count ?? 0}:${fkFailed ? \"failed\" : \"inserted\"}`,\n\t\t!fkFailed || (fkAfter?.count ?? 0) !== (fkBefore?.count ?? 0),\n\t);\n\n\treturn attempts.length + 3;\n}\n\nasync function applyPragmaProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tlet ops = 0;\n\tfor (const [name, setupSql, checkSql, expected] of [\n\t\t[\n\t\t\t\"journal_mode\",\n\t\t\t\"PRAGMA journal_mode = DELETE\",\n\t\t\t\"PRAGMA journal_mode\",\n\t\t\t\"nonempty\",\n\t\t],\n\t\t[\n\t\t\t\"synchronous\",\n\t\t\t\"PRAGMA synchronous = NORMAL\",\n\t\t\t\"PRAGMA synchronous\",\n\t\t\t\"nonempty\",\n\t\t],\n\t\t[\n\t\t\t\"cache_size\",\n\t\t\t\"PRAGMA cache_size = -2000\",\n\t\t\t\"PRAGMA cache_size\",\n\t\t\t\"-2000\",\n\t\t],\n\t\t[\n\t\t\t\"foreign_keys\",\n\t\t\t\"PRAGMA foreign_keys = ON\",\n\t\t\t\"PRAGMA foreign_keys\",\n\t\t\t\"1\",\n\t\t],\n\t\t[\"auto_vacuum\", \"PRAGMA auto_vacuum\", \"PRAGMA auto_vacuum\", \"nonempty\"],\n\t] as const) {\n\t\ttry {\n\t\t\tawait database.execute(setupSql);\n\t\t\tconst rows = await database.execute(checkSql);\n\t\t\tconst actual = String(firstColumn(rows[0]) ?? \"\");\n\t\t\tawait recordProbe(\n\t\t\t\tdatabase,\n\t\t\t\tphase,\n\t\t\t\t\"pragma\",\n\t\t\t\tname,\n\t\t\t\texpected,\n\t\t\t\tactual,\n\t\t\t\texpected === \"nonempty\"\n\t\t\t\t\t? actual.length === 0\n\t\t\t\t\t: actual !== expected,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tawait recordProbe(\n\t\t\t\tdatabase,\n\t\t\t\tphase,\n\t\t\t\t\"pragma\",\n\t\t\t\tname,\n\t\t\t\texpected,\n\t\t\t\terr instanceof Error ? err.message : \"unknown error\",\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\t\tops += 1;\n\t}\n\treturn ops;\n}\n\nasync function applySavepointScenario(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tconst keepId = `save-keep-${phase}`;\n\tconst rolledBackId = `save-rolled-back-${phase}`;\n\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value\",\n\t\t\tkeepId,\n\t\t\tphase,\n\t\t);\n\t\tawait database.execute(\"SAVEPOINT sp_rollback_probe\");\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_savepoints (id, value) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET value = excluded.value\",\n\t\t\trolledBackId,\n\t\t\t999_000 + phase,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_savepoints SET value = value + 1000 WHERE id = ?\",\n\t\t\tkeepId,\n\t\t);\n\t\tawait database.execute(\"ROLLBACK TO sp_rollback_probe\");\n\t\tawait database.execute(\"RELEASE sp_rollback_probe\");\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_savepoint_expectations (id, present, value)\n\t\tVALUES (?, 1, ?)\n\t\tON CONFLICT(id) DO UPDATE SET present = 1, value = excluded.value`,\n\t\tkeepId,\n\t\tphase,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_savepoint_expectations (id, present, value)\n\t\tVALUES (?, 0, 0)\n\t\tON CONFLICT(id) DO UPDATE SET present = 0, value = 0`,\n\t\trolledBackId,\n\t);\n\n\treturn 5;\n}\n\nasync function applyIdempotentReplay(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n): Promise {\n\tconst targetId = `idem-target-${phase % 3}`;\n\tawait database.execute(\n\t\t\"INSERT OR IGNORE INTO fuzz_idempotent_targets (id, value) VALUES (?, 0)\",\n\t\ttargetId,\n\t);\n\n\tfor (let i = 0; i < 8; i += 1) {\n\t\tconst opId = `idem-${phase}-${i}`;\n\t\tconst amount = phase + i + 1;\n\t\tfor (let attempt = 0; attempt < 3; attempt += 1) {\n\t\t\tawait transaction(database, async () => {\n\t\t\t\tconst existing = await queryOne<{ op_id: string }>(\n\t\t\t\t\tdatabase,\n\t\t\t\t\t\"SELECT op_id FROM fuzz_idempotent_ops WHERE op_id = ?\",\n\t\t\t\t\topId,\n\t\t\t\t);\n\t\t\t\tif (!existing) {\n\t\t\t\t\tawait database.execute(\n\t\t\t\t\t\t\"INSERT INTO fuzz_idempotent_ops (op_id, target_id, amount) VALUES (?, ?, ?)\",\n\t\t\t\t\t\topId,\n\t\t\t\t\t\ttargetId,\n\t\t\t\t\t\tamount,\n\t\t\t\t\t);\n\t\t\t\t\tawait database.execute(\n\t\t\t\t\t\t\"UPDATE fuzz_idempotent_targets SET value = value + ? WHERE id = ?\",\n\t\t\t\t\t\tamount,\n\t\t\t\t\t\ttargetId,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\treturn 24;\n}\n\nasync function ensureRelationalSeed(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}): Promise {\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 8; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_rel_users (id, name) VALUES (?, ?)\",\n\t\t\t\t`user-${i}`,\n\t\t\t\t`User ${i}`,\n\t\t\t);\n\t\t}\n\t\tfor (let i = 0; i < 12; i += 1) {\n\t\t\tconst productId = `product-${i}`;\n\t\t\tconst initialQty = 10_000;\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO fuzz_rel_products (id, price) VALUES (?, ?)\",\n\t\t\t\tproductId,\n\t\t\t\t(i + 1) * 7,\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT OR IGNORE INTO fuzz_inventory (\n\t\t\t\t\tproduct_id, initial_qty, sold_qty, stock_qty\n\t\t\t\t) VALUES (?, ?, 0, ?)`,\n\t\t\t\tproductId,\n\t\t\t\tinitialQty,\n\t\t\t\tinitialQty,\n\t\t\t);\n\t\t}\n\t});\n}\n\nasync function applyRelationalOrder(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tphase: number;\n\t\tlocalIndex: number;\n\t\trng: () => number;\n\t},\n): Promise {\n\tawait ensureRelationalSeed(database);\n\tconst orderPrefix = `order-${opts.phase}-${opts.localIndex}`;\n\tconst existingOrders = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_orders WHERE id LIKE ?\",\n\t\t`${orderPrefix}-%`,\n\t);\n\tconst orderId = `${orderPrefix}-${existingOrders?.count ?? 0}`;\n\tconst userId = `user-${intBetween(opts.rng, 0, 7)}`;\n\tconst itemCount = intBetween(opts.rng, 1, 4);\n\tlet total = 0;\n\n\tawait transaction(database, async () => {\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_orders (id, user_id, total, status) VALUES (?, ?, 0, 'open')\",\n\t\t\torderId,\n\t\t\tuserId,\n\t\t);\n\t\tfor (let i = 0; i < itemCount; i += 1) {\n\t\t\tconst productId = `product-${intBetween(opts.rng, 0, 11)}`;\n\t\t\tconst product = await queryOne<{ price: number }>(\n\t\t\t\tdatabase,\n\t\t\t\t\"SELECT price FROM fuzz_rel_products WHERE id = ?\",\n\t\t\t\tproductId,\n\t\t\t);\n\t\t\tconst quantity = intBetween(opts.rng, 1, 5);\n\t\t\tconst price = product?.price ?? 0;\n\t\t\ttotal += price * quantity;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_order_items (\n\t\t\t\t\torder_id, product_id, quantity, price\n\t\t\t\t) VALUES (?, ?, ?, ?)`,\n\t\t\t\torderId,\n\t\t\t\tproductId,\n\t\t\t\tquantity,\n\t\t\t\tprice,\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t`UPDATE fuzz_inventory\n\t\t\t\tSET sold_qty = sold_qty + ?, stock_qty = stock_qty - ?\n\t\t\t\tWHERE product_id = ?`,\n\t\t\t\tquantity,\n\t\t\t\tquantity,\n\t\t\t\tproductId,\n\t\t\t);\n\t\t}\n\t\tawait database.execute(\n\t\t\t\"UPDATE fuzz_orders SET total = ?, status = 'paid' WHERE id = ?\",\n\t\t\ttotal,\n\t\t\torderId,\n\t\t);\n\t\tawait database.execute(\n\t\t\t\"INSERT INTO fuzz_payments (order_id, amount, status) VALUES (?, ?, 'captured')\",\n\t\t\torderId,\n\t\t\ttotal,\n\t\t);\n\t});\n\n\treturn itemCount + 4;\n}\n\nasync function applyRollbackProbe(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tphase: number,\n\trowCount = 20,\n): Promise {\n\tconst before = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?\",\n\t\t`rollback-${phase}-%`,\n\t);\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < rowCount; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_items (\n\t\t\t\t\titem_key, value, version, update_count, payload, payload_checksum,\n\t\t\t\t\tpayload_bytes, updated_at\n\t\t\t\t) VALUES (?, ?, 1, 1, ?, ?, ?, ?)`,\n\t\t\t\t`rollback-${phase}-${i}`,\n\t\t\t\t\"should-not-survive\",\n\t\t\t\t\"rollback-payload\",\n\t\t\t\tchecksum(\"rollback-payload\"),\n\t\t\t\t\"rollback-payload\".length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t}\n\t\tthrow new Error(\"intentional rollback probe\");\n\t} catch {\n\t\tawait database.execute(\"ROLLBACK\");\n\t}\n\tconst after = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_items WHERE item_key LIKE ?\",\n\t\t`rollback-${phase}-%`,\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO fuzz_constraint_attempts (\n\t\t\tname, expected_failed, actually_failed, before_count, after_count\n\t\t) VALUES (?, 1, 1, ?, ?)`,\n\t\t`rollback-probe-${phase}`,\n\t\tbefore?.count ?? 0,\n\t\tafter?.count ?? 0,\n\t);\n\treturn rowCount;\n}\n\nasync function applyNastyScript(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t\tintense?: boolean;\n\t},\n): Promise {\n\tconst growKey = `nasty-grow-${opts.phase}`;\n\tlet ops = 0;\n\tconst growMax = Math.min(opts.maxPayloadBytes, 131072);\n\tconst growSizes = PAGE_BOUNDARY_SIZES.filter((size) => size <= growMax);\n\tif (!growSizes.includes(1)) growSizes.unshift(1);\n\tif (!growSizes.includes(growMax)) growSizes.push(growMax);\n\tfor (const size of growSizes) {\n\t\tawait applyItemOperation(database, {\n\t\t\tseed: opts.seed,\n\t\t\tphase: opts.phase,\n\t\t\tlocalIndex: 50_000 + size,\n\t\t\tkind: \"upsert\",\n\t\t\titemKey: growKey,\n\t\t\tpayloadBytes: Math.min(size, opts.maxPayloadBytes),\n\t\t});\n\t\tops += 1;\n\t}\n\n\tconst hotUpdates = opts.intense ? 10_000 : 250;\n\tawait applyHotUpdates(database, {\n\t\tseed: opts.seed,\n\t\tphase: opts.phase,\n\t\tlocalIndex: 60_000,\n\t\titemKey: `nasty-hot-${opts.phase}`,\n\t\tupdates: hotUpdates,\n\t\tpayloadBytes: Math.min(1024, opts.maxPayloadBytes),\n\t});\n\tops += hotUpdates;\n\n\tif (opts.intense) {\n\t\tawait database.execute(\n\t\t\t\"CREATE INDEX IF NOT EXISTS idx_nasty_heavy_write ON fuzz_items(value, version)\",\n\t\t);\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 120_000 + i,\n\t\t\t\tkind: \"upsert\",\n\t\t\t\titemKey: `nasty-bulk-${opts.phase}-${i}`,\n\t\t\t\tpayloadBytes: Math.min(256, opts.maxPayloadBytes),\n\t\t\t});\n\t\t\tops += 1;\n\t\t}\n\t\tfor (let i = 0; i < 10_000; i += 2) {\n\t\t\tawait applyItemOperation(database, {\n\t\t\t\tseed: opts.seed,\n\t\t\t\tphase: opts.phase,\n\t\t\t\tlocalIndex: 140_000 + i,\n\t\t\t\tkind: \"delete\",\n\t\t\t\titemKey: `nasty-bulk-${opts.phase}-${i}`,\n\t\t\t\tpayloadBytes: 1,\n\t\t\t});\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\"DROP INDEX IF EXISTS idx_nasty_heavy_write\");\n\t}\n\n\tconst rollbackRows = opts.intense ? 1000 : 20;\n\tawait applyRollbackProbe(database, opts.phase, rollbackRows);\n\tops += rollbackRows;\n\n\treturn ops;\n}\n\nasync function applyDeterministicNastyScript(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmaxPayloadBytes: number;\n\t},\n): Promise {\n\tlet ops = 0;\n\tconst growId = `nasty-script-grow-${opts.phase}`;\n\tconst maxGrowBytes = Math.min(opts.maxPayloadBytes, 131072);\n\tlet finalGrowPayload = \"\";\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 256; i += 1) {\n\t\t\tconst size = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(1 + ((maxGrowBytes - 1) * i) / 255),\n\t\t\t);\n\t\t\tconst payload = payloadFor(\n\t\t\t\topts.seed,\n\t\t\t\topts.phase,\n\t\t\t\t160_000 + i,\n\t\t\t\tsize,\n\t\t\t);\n\t\t\tfinalGrowPayload = payload;\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_edge_payloads (\n\t\t\t\t\tid, kind, payload, payload_checksum, payload_bytes, updated_at\n\t\t\t\t) VALUES (?, 'nasty-grow', ?, ?, ?, ?)\n\t\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\t\tpayload = excluded.payload,\n\t\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\t\tpayload_bytes = excluded.payload_bytes,\n\t\t\t\t\tupdated_at = excluded.updated_at`,\n\t\t\t\tgrowId,\n\t\t\t\tpayload,\n\t\t\t\tchecksum(payload),\n\t\t\t\tpayload.length,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\n\t\t\t`INSERT INTO fuzz_edge_expectations (\n\t\t\t\tid, present, payload_checksum, payload_bytes\n\t\t\t) VALUES (?, 1, ?, ?)\n\t\t\tON CONFLICT(id) DO UPDATE SET\n\t\t\t\tpresent = 1,\n\t\t\t\tpayload_checksum = excluded.payload_checksum,\n\t\t\t\tpayload_bytes = excluded.payload_bytes`,\n\t\t\tgrowId,\n\t\t\tchecksum(finalGrowPayload),\n\t\t\tfinalGrowPayload.length,\n\t\t);\n\t});\n\n\tconst counterId = `nasty-counter-${opts.phase}`;\n\tawait database.execute(\n\t\t\"INSERT INTO fuzz_nasty_counter (id, value) VALUES (?, 0) ON CONFLICT(id) DO UPDATE SET value = 0\",\n\t\tcounterId,\n\t);\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"UPDATE fuzz_nasty_counter SET value = value + 1 WHERE id = ?\",\n\t\t\t\tcounterId,\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t});\n\tconst counter = await queryOne<{ value: number }>(\n\t\tdatabase,\n\t\t\"SELECT value FROM fuzz_nasty_counter WHERE id = ?\",\n\t\tcounterId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"same-row-10k-updates\",\n\t\t10_000,\n\t\tcounter?.value ?? -1,\n\t\t(counter?.value ?? -1) !== 10_000,\n\t);\n\n\tconst groupId = `nasty-bulk-${opts.phase}`;\n\tawait database.execute(\n\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_nasty_rows_group_n ON fuzz_nasty_rows(group_id, n)\",\n\t);\n\tawait transaction(database, async () => {\n\t\tfor (let i = 0; i < 10_000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO fuzz_nasty_rows (group_id, n, payload)\n\t\t\t\tVALUES (?, ?, ?)\n\t\t\t\tON CONFLICT(group_id, n) DO UPDATE SET payload = excluded.payload`,\n\t\t\t\tgroupId,\n\t\t\t\ti,\n\t\t\t\tpayloadFor(opts.seed, opts.phase, 170_000 + i, 64),\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\n\t\t\t\"DELETE FROM fuzz_nasty_rows WHERE group_id = ? AND n % 2 = 0\",\n\t\t\tgroupId,\n\t\t);\n\t\tops += 1;\n\t});\n\tawait database.execute(\"DROP INDEX IF EXISTS idx_fuzz_nasty_rows_group_n\");\n\tconst remaining = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\tgroupId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"insert-10k-delete-every-other\",\n\t\t5000,\n\t\tremaining?.count ?? -1,\n\t\t(remaining?.count ?? -1) !== 5000,\n\t);\n\tconst indexLeft = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'index' AND name = 'idx_fuzz_nasty_rows_group_n'\",\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"create-drop-index-around-heavy-writes\",\n\t\t0,\n\t\tindexLeft?.count ?? -1,\n\t\t(indexLeft?.count ?? -1) !== 0,\n\t);\n\n\tconst rollbackGroupId = `nasty-rollback-${opts.phase}`;\n\tconst beforeRollback = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\trollbackGroupId,\n\t);\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\tfor (let i = 0; i < 1000; i += 1) {\n\t\t\tawait database.execute(\n\t\t\t\t\"INSERT INTO fuzz_nasty_rows (group_id, n, payload) VALUES (?, ?, ?)\",\n\t\t\t\trollbackGroupId,\n\t\t\t\ti,\n\t\t\t\t\"rollback\",\n\t\t\t);\n\t\t\tops += 1;\n\t\t}\n\t\tawait database.execute(\"ROLLBACK\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\tthrow err;\n\t}\n\tconst afterRollback = await queryOne<{ count: number }>(\n\t\tdatabase,\n\t\t\"SELECT COUNT(*) AS count FROM fuzz_nasty_rows WHERE group_id = ?\",\n\t\trollbackGroupId,\n\t);\n\tawait recordProbe(\n\t\tdatabase,\n\t\topts.phase,\n\t\t\"nasty-script\",\n\t\t\"rollback-1k-inserts\",\n\t\tbeforeRollback?.count ?? 0,\n\t\tafterRollback?.count ?? -1,\n\t\t(afterRollback?.count ?? -1) !== (beforeRollback?.count ?? 0),\n\t);\n\n\treturn ops;\n}\n\nfunction shouldRunDeepScenario(\n\tmode: WorkloadMode,\n\tscenario: WorkloadMode,\n): boolean {\n\treturn mode === scenario || mode === \"kitchen-sink\" || mode === \"nasty\";\n}\n\nasync function applyDeepScenarios(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\topts: {\n\t\tseed: string;\n\t\tphase: number;\n\t\tmode: WorkloadMode;\n\t\titerations: number;\n\t\trng: () => number;\n\t\tmaxPayloadBytes: number;\n\t\tgrowthTargetBytes: number;\n\t\tops: Record;\n\t},\n): Promise {\n\tconst runScenario = async (\n\t\tname: string,\n\t\tfn: () => Promise,\n\t): Promise => {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\tconst detail =\n\t\t\t\terror instanceof Error\n\t\t\t\t\t? error.message\n\t\t\t\t\t: typeof error === \"string\"\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: String(error);\n\t\t\tthrow new Error(\n\t\t\t\t`deep scenario ${name} failed in mode ${opts.mode} during phase ${opts.phase}: ${detail}`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t};\n\n\tif (shouldRunDeepScenario(opts.mode, \"edge\") || opts.mode === \"payloads\") {\n\t\topts.ops.edgePayload =\n\t\t\t(opts.ops.edgePayload ?? 0) +\n\t\t\t(await runScenario(\"edge\", () =>\n\t\t\t\tapplyEdgePayloads(database, opts),\n\t\t\t));\n\t}\n\tif (opts.mode === \"actual-nul\") {\n\t\topts.ops.actualNul =\n\t\t\t(opts.ops.actualNul ?? 0) +\n\t\t\t(await runScenario(\"actual-nul\", () =>\n\t\t\t\tapplyActualNulPayload(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"fragmentation\")) {\n\t\topts.ops.fragmentation =\n\t\t\t(opts.ops.fragmentation ?? 0) +\n\t\t\t(await runScenario(\"fragmentation\", () =>\n\t\t\t\tapplyFragmentationChurn(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"schema\")) {\n\t\topts.ops.schema =\n\t\t\t(opts.ops.schema ?? 0) +\n\t\t\t(await runScenario(\"schema\", () =>\n\t\t\t\tapplySchemaChurn(database, opts.phase),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"index\")) {\n\t\topts.ops.index =\n\t\t\t(opts.ops.index ?? 0) +\n\t\t\t(await runScenario(\"index\", () => applyIndexProbe(database, opts)));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"constraints\")) {\n\t\topts.ops.constraints =\n\t\t\t(opts.ops.constraints ?? 0) +\n\t\t\t(await runScenario(\"constraints\", () =>\n\t\t\t\tapplyConstraintChaos(database, opts.phase),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"savepoints\")) {\n\t\topts.ops.savepoints =\n\t\t\t(opts.ops.savepoints ?? 0) +\n\t\t\t(await runScenario(\"savepoints\", () =>\n\t\t\t\tapplySavepointScenario(database, opts.phase),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"pragma\")) {\n\t\topts.ops.pragma =\n\t\t\t(opts.ops.pragma ?? 0) +\n\t\t\t(await runScenario(\"pragma\", () =>\n\t\t\t\tapplyPragmaProbe(database, opts.phase),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"prepared\")) {\n\t\topts.ops.prepared =\n\t\t\t(opts.ops.prepared ?? 0) +\n\t\t\t(await runScenario(\"prepared\", () =>\n\t\t\t\tapplyPreparedChurn(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"growth\")) {\n\t\topts.ops.growth =\n\t\t\t(opts.ops.growth ?? 0) +\n\t\t\t(await runScenario(\"growth\", () =>\n\t\t\t\tapplyGrowthProbe(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"readwrite\")) {\n\t\topts.ops.readwrite =\n\t\t\t(opts.ops.readwrite ?? 0) +\n\t\t\t(await runScenario(\"readwrite\", () =>\n\t\t\t\tapplyReadWriteProbe(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"truncate\")) {\n\t\topts.ops.truncate =\n\t\t\t(opts.ops.truncate ?? 0) +\n\t\t\t(await runScenario(\"truncate\", () =>\n\t\t\t\tapplyTruncateRecreateProbe(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"boundary-keys\")) {\n\t\topts.ops.boundaryKeys =\n\t\t\t(opts.ops.boundaryKeys ?? 0) +\n\t\t\t(await runScenario(\"boundary-keys\", () =>\n\t\t\t\tapplyBoundaryKeys(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"relational\")) {\n\t\tconst orders = Math.max(1, Math.floor(opts.iterations / 20));\n\t\tfor (let i = 0; i < orders; i += 1) {\n\t\t\topts.ops.relational =\n\t\t\t\t(opts.ops.relational ?? 0) +\n\t\t\t\t(await runScenario(\"relational\", () =>\n\t\t\t\t\tapplyRelationalOrder(database, {\n\t\t\t\t\t\tphase: opts.phase,\n\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\trng: opts.rng,\n\t\t\t\t\t}),\n\t\t\t\t));\n\t\t}\n\t}\n\tif (opts.mode === \"kitchen-sink\" || opts.mode === \"nasty\") {\n\t\topts.ops.idempotent =\n\t\t\t(opts.ops.idempotent ?? 0) +\n\t\t\t(await runScenario(\"idempotent\", () =>\n\t\t\t\tapplyIdempotentReplay(database, opts.phase),\n\t\t\t));\n\t\topts.ops.nasty =\n\t\t\t(opts.ops.nasty ?? 0) +\n\t\t\t(await runScenario(\"nasty\", () =>\n\t\t\t\tapplyNastyScript(database, {\n\t\t\t\t\t...opts,\n\t\t\t\t\tintense: opts.mode === \"nasty\",\n\t\t\t\t}),\n\t\t\t));\n\t}\n\tif (opts.mode === \"nasty-script\") {\n\t\topts.ops.nasty =\n\t\t\t(opts.ops.nasty ?? 0) +\n\t\t\t(await runScenario(\"nasty-script\", () =>\n\t\t\t\tapplyDeterministicNastyScript(database, opts),\n\t\t\t));\n\t}\n\tif (shouldRunDeepScenario(opts.mode, \"shadow\")) {\n\t\topts.ops.shadow =\n\t\t\t(opts.ops.shadow ?? 0) +\n\t\t\t(await runScenario(\"shadow\", () =>\n\t\t\t\tupdateShadowChecksums(database, opts.phase),\n\t\t\t));\n\t}\n}\n\nfunction chooseKind(\n\tmode: WorkloadMode,\n\trng: () => number,\n): \"insert\" | \"update\" | \"delete\" | \"upsert\" | \"hot\" | \"transfer\" {\n\tconst roll = rng();\n\tif (mode === \"transactions\") {\n\t\tif (roll < 0.55) return \"transfer\";\n\t\tif (roll < 0.75) return \"upsert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (mode === \"hot\") {\n\t\tif (roll < 0.6) return \"hot\";\n\t\tif (roll < 0.75) return \"upsert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (mode === \"payloads\") {\n\t\tif (roll < 0.4) return \"upsert\";\n\t\tif (roll < 0.7) return \"insert\";\n\t\tif (roll < 0.9) return \"update\";\n\t\treturn \"delete\";\n\t}\n\tif (roll < 0.2) return \"insert\";\n\tif (roll < 0.45) return \"update\";\n\tif (roll < 0.65) return \"delete\";\n\tif (roll < 0.85) return \"upsert\";\n\tif (roll < 0.95) return \"hot\";\n\treturn \"transfer\";\n}\n\nasync function validate(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}): Promise {\n\tconst integrity = await queryOne<{ integrity_check: string }>(\n\t\tdatabase,\n\t\t\"PRAGMA integrity_check\",\n\t);\n\tconst quick = await queryOne<{ quick_check: string }>(\n\t\tdatabase,\n\t\t\"PRAGMA quick_check\",\n\t);\n\tconst totals = await queryOne<{\n\t\ttotal_events: number;\n\t\tactive_rows: number;\n\t\texpected_rows: number;\n\t\tactual_version_sum: number;\n\t\texpected_version_sum: number;\n\t\tactual_payload_checksum_sum: number;\n\t\texpected_payload_checksum_sum: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_item_events) AS total_events,\n\t\t\t(SELECT COUNT(*) FROM fuzz_items) AS active_rows,\n\t\t\t(SELECT COUNT(*) FROM latest WHERE present = 1) AS expected_rows,\n\t\t\t(SELECT COALESCE(SUM(version), 0) FROM fuzz_items) AS actual_version_sum,\n\t\t\t(SELECT COALESCE(SUM(version), 0) FROM latest WHERE present = 1) AS expected_version_sum,\n\t\t\t(SELECT COALESCE(SUM(payload_checksum), 0) FROM fuzz_items) AS actual_payload_checksum_sum,\n\t\t\t(SELECT COALESCE(SUM(payload_checksum), 0) FROM latest WHERE present = 1) AS expected_payload_checksum_sum`,\n\t);\n\tconst mismatches = await queryOne<{\n\t\tmissing_rows: number;\n\t\textra_rows: number;\n\t\tmismatched_rows: number;\n\t\tduplicate_keys: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM latest l\n\t\t\t\tLEFT JOIN fuzz_items i ON i.item_key = l.item_key\n\t\t\t\tWHERE l.present = 1 AND i.item_key IS NULL\n\t\t\t) AS missing_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_items i\n\t\t\t\tLEFT JOIN latest l ON l.item_key = i.item_key\n\t\t\t\tWHERE l.item_key IS NULL OR l.present = 0\n\t\t\t) AS extra_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM latest l\n\t\t\t\tJOIN fuzz_items i ON i.item_key = l.item_key\n\t\t\t\tWHERE l.present = 1\n\t\t\t\t\tAND (\n\t\t\t\t\t\ti.value != l.value OR\n\t\t\t\t\t\ti.version != l.version OR\n\t\t\t\t\t\ti.update_count != l.update_count OR\n\t\t\t\t\t\ti.payload_checksum != l.payload_checksum OR\n\t\t\t\t\t\ti.payload_bytes != l.payload_bytes\n\t\t\t\t\t)\n\t\t\t) AS mismatched_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT item_key\n\t\t\t\t\tFROM fuzz_items\n\t\t\t\t\tGROUP BY item_key\n\t\t\t\t\tHAVING COUNT(*) > 1\n\t\t\t\t)\n\t\t\t) AS duplicate_keys`,\n\t);\n\tconst accounts = await queryOne<{\n\t\taccount_count: number;\n\t\taccount_balance_sum: number;\n\t\taccount_balance_mismatch: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS account_count,\n\t\t\tCOALESCE(SUM(balance), 0) AS account_balance_sum,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_transfer_events\n\t\t\t\tWHERE balance_sum_before != ? OR balance_sum_after != ?\n\t\t\t) AS account_balance_mismatch\n\t\tFROM fuzz_accounts`,\n\t\tACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t\tACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t);\n\tconst edge = await queryOne<{\n\t\tedge_rows: number;\n\t\tedge_expected_rows: number;\n\t\tedge_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_edge_payloads) AS edge_rows,\n\t\t\t(SELECT COUNT(*) FROM fuzz_edge_expectations WHERE present = 1) AS edge_expected_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_edge_expectations e\n\t\t\t\tLEFT JOIN fuzz_edge_payloads p ON p.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\t(e.present = 1 AND p.id IS NULL) OR\n\t\t\t\t\t(e.present = 0 AND p.id IS NOT NULL) OR\n\t\t\t\t\t(e.present = 1 AND (\n\t\t\t\t\t\tp.payload_checksum != e.payload_checksum OR\n\t\t\t\t\t\tp.payload_bytes != e.payload_bytes\n\t\t\t\t\t))\n\t\t\t) AS edge_mismatches`,\n\t);\n\tconst indexProbe = await queryOne<{\n\t\tindex_rows: number;\n\t\tindex_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_indexed) AS index_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT id FROM fuzz_indexed\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t\tEXCEPT\n\t\t\t\t\tSELECT id FROM fuzz_indexed NOT INDEXED\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM (\n\t\t\t\t\tSELECT id FROM fuzz_indexed NOT INDEXED\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t\tEXCEPT\n\t\t\t\t\tSELECT id FROM fuzz_indexed\n\t\t\t\t\tWHERE tenant = 'tenant-1' AND bucket BETWEEN 2 AND 8 AND score BETWEEN -250 AND 250\n\t\t\t\t)\n\t\t\t) AS index_mismatches`,\n\t);\n\tconst relational = await queryOne<{\n\t\trelational_orders: number;\n\t\trelational_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_orders) AS relational_orders,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_orders o\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT order_id, COALESCE(SUM(quantity * price), 0) AS item_total\n\t\t\t\t\tFROM fuzz_order_items\n\t\t\t\t\tGROUP BY order_id\n\t\t\t\t) i ON i.order_id = o.id\n\t\t\t\tWHERE o.total != COALESCE(i.item_total, 0)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_orders o\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT order_id, COALESCE(SUM(amount), 0) AS payment_total\n\t\t\t\t\tFROM fuzz_payments\n\t\t\t\t\tWHERE status = 'captured'\n\t\t\t\t\tGROUP BY order_id\n\t\t\t\t) p ON p.order_id = o.id\n\t\t\t\tWHERE o.status = 'paid' AND o.total != COALESCE(p.payment_total, 0)\n\t\t\t) + (\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_inventory\n\t\t\t\tWHERE initial_qty != sold_qty + stock_qty OR stock_qty < 0\n\t\t\t) AS relational_mismatches`,\n\t);\n\tconst constraints = await queryOne<{\n\t\tconstraint_attempts: number;\n\t\tconstraint_leaks: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS constraint_attempts,\n\t\t\tCOALESCE(SUM(\n\t\t\t\tCASE\n\t\t\t\t\tWHEN expected_failed = 1 AND (actually_failed != 1 OR before_count != after_count) THEN 1\n\t\t\t\t\tWHEN expected_failed = 0 AND after_count != before_count + 1 THEN 1\n\t\t\t\t\tELSE 0\n\t\t\t\tEND\n\t\t\t), 0) AS constraint_leaks\n\t\tFROM fuzz_constraint_attempts`,\n\t);\n\tconst savepoints = await queryOne<{\n\t\tsavepoint_rows: number;\n\t\tsavepoint_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_savepoints) AS savepoint_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_savepoint_expectations e\n\t\t\t\tLEFT JOIN fuzz_savepoints s ON s.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\t(e.present = 1 AND s.id IS NULL) OR\n\t\t\t\t\t(e.present = 0 AND s.id IS NOT NULL) OR\n\t\t\t\t\t(e.present = 1 AND s.value != e.value)\n\t\t\t) AS savepoint_mismatches`,\n\t);\n\tconst idempotency = await queryOne<{\n\t\tidempotent_ops: number;\n\t\tidempotent_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_idempotent_ops) AS idempotent_ops,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_idempotent_targets t\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT target_id, COALESCE(SUM(amount), 0) AS expected\n\t\t\t\t\tFROM fuzz_idempotent_ops\n\t\t\t\t\tGROUP BY target_id\n\t\t\t\t) o ON o.target_id = t.id\n\t\t\t\tWHERE t.value != COALESCE(o.expected, 0)\n\t\t\t) AS idempotent_mismatches`,\n\t);\n\tconst schema = await queryOne<{\n\t\tschema_objects: number;\n\t\tschema_missing_objects: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS schema_objects,\n\t\t\tCOALESCE(SUM(CASE WHEN m.name IS NULL THEN 1 ELSE 0 END), 0) AS schema_missing_objects\n\t\tFROM fuzz_schema_registry r\n\t\tLEFT JOIN sqlite_master m ON m.name = r.name AND m.type = r.type`,\n\t);\n\tconst probes = await queryOne<{\n\t\tprobe_rows: number;\n\t\tprobe_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\tCOUNT(*) AS probe_rows,\n\t\t\tCOALESCE(SUM(mismatch), 0) AS probe_mismatches\n\t\tFROM fuzz_probe_results`,\n\t);\n\tconst prepared = await queryOne<{\n\t\tprepared_rows: number;\n\t\tprepared_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`SELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_prepared_churn) AS prepared_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_prepared_expectations e\n\t\t\t\tLEFT JOIN fuzz_prepared_churn p ON p.id = e.id\n\t\t\t\tWHERE\n\t\t\t\t\tp.id IS NULL OR\n\t\t\t\t\tp.value != e.value OR\n\t\t\t\t\tp.payload_checksum != e.payload_checksum\n\t\t\t) AS prepared_mismatches`,\n\t);\n\tconst shadow = await queryOne<{\n\t\tshadow_rows: number;\n\t\tshadow_mismatches: number;\n\t}>(\n\t\tdatabase,\n\t\t`WITH recomputed AS (\n\t\t\tSELECT 'items' AS name,\n\t\t\t\tCOUNT(*) AS row_count,\n\t\t\t\tCOALESCE(SUM(payload_checksum + version + update_count), 0) AS value\n\t\t\tFROM fuzz_items\n\t\t\tUNION ALL\n\t\t\tSELECT 'edge' AS name,\n\t\t\t\tCOUNT(*) AS row_count,\n\t\t\t\tCOALESCE(SUM(payload_checksum + payload_bytes), 0) AS value\n\t\t\tFROM fuzz_edge_payloads\n\t\t)\n\t\tSELECT\n\t\t\t(SELECT COUNT(*) FROM fuzz_shadow_checksums) AS shadow_rows,\n\t\t\t(\n\t\t\t\tSELECT COUNT(*)\n\t\t\t\tFROM fuzz_shadow_checksums s\n\t\t\t\tJOIN recomputed r ON r.name = s.name\n\t\t\t\tWHERE s.value != r.value OR s.row_count != r.row_count\n\t\t\t) AS shadow_mismatches`,\n\t);\n\n\tconst summary: ValidationSummary = {\n\t\ttotalEvents: totals?.total_events ?? 0,\n\t\tactiveRows: totals?.active_rows ?? 0,\n\t\texpectedRows: totals?.expected_rows ?? 0,\n\t\tmissingRows: mismatches?.missing_rows ?? 0,\n\t\textraRows: mismatches?.extra_rows ?? 0,\n\t\tmismatchedRows: mismatches?.mismatched_rows ?? 0,\n\t\tduplicateKeys: mismatches?.duplicate_keys ?? 0,\n\t\tactualVersionSum: totals?.actual_version_sum ?? 0,\n\t\texpectedVersionSum: totals?.expected_version_sum ?? 0,\n\t\tactualPayloadChecksumSum: totals?.actual_payload_checksum_sum ?? 0,\n\t\texpectedPayloadChecksumSum: totals?.expected_payload_checksum_sum ?? 0,\n\t\taccountCount: accounts?.account_count ?? 0,\n\t\taccountBalanceSum: accounts?.account_balance_sum ?? 0,\n\t\texpectedAccountBalanceSum: ACCOUNT_COUNT * ACCOUNT_INITIAL_BALANCE,\n\t\taccountBalanceMismatch: accounts?.account_balance_mismatch ?? 0,\n\t\tintegrityCheck: integrity?.integrity_check ?? \"missing\",\n\t\tquickCheck: quick?.quick_check ?? \"missing\",\n\t\tedgeRows: edge?.edge_rows ?? 0,\n\t\tedgeExpectedRows: edge?.edge_expected_rows ?? 0,\n\t\tedgeMismatches: edge?.edge_mismatches ?? 0,\n\t\tindexRows: indexProbe?.index_rows ?? 0,\n\t\tindexMismatches: indexProbe?.index_mismatches ?? 0,\n\t\trelationalOrders: relational?.relational_orders ?? 0,\n\t\trelationalMismatches: relational?.relational_mismatches ?? 0,\n\t\tconstraintAttempts: constraints?.constraint_attempts ?? 0,\n\t\tconstraintLeaks: constraints?.constraint_leaks ?? 0,\n\t\tsavepointRows: savepoints?.savepoint_rows ?? 0,\n\t\tsavepointMismatches: savepoints?.savepoint_mismatches ?? 0,\n\t\tidempotentOps: idempotency?.idempotent_ops ?? 0,\n\t\tidempotentMismatches: idempotency?.idempotent_mismatches ?? 0,\n\t\tschemaObjects: schema?.schema_objects ?? 0,\n\t\tschemaMissingObjects: schema?.schema_missing_objects ?? 0,\n\t\tprobeRows: probes?.probe_rows ?? 0,\n\t\tprobeMismatches: probes?.probe_mismatches ?? 0,\n\t\tpreparedRows: prepared?.prepared_rows ?? 0,\n\t\tpreparedMismatches: prepared?.prepared_mismatches ?? 0,\n\t\tshadowRows: shadow?.shadow_rows ?? 0,\n\t\tshadowMismatches: shadow?.shadow_mismatches ?? 0,\n\t};\n\n\treturn summary;\n}\n\nasync function debugItemMismatches(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tlimit = 5,\n): Promise<{\n\titemMismatches: ItemMismatchDebugRow[];\n\trecentEventsByKey: Record;\n}> {\n\tconst rows = (await database.execute(\n\t\t`WITH latest AS (\n\t\t\tSELECT e.*\n\t\t\tFROM fuzz_item_events e\n\t\t\tJOIN (\n\t\t\t\tSELECT item_key, MAX(seq) AS seq\n\t\t\t\tFROM fuzz_item_events\n\t\t\t\tGROUP BY item_key\n\t\t\t) m ON m.item_key = e.item_key AND m.seq = e.seq\n\t\t)\n\t\tSELECT\n\t\t\tCOALESCE(l.item_key, i.item_key) AS item_key,\n\t\t\ti.value AS actual_value,\n\t\t\tl.value AS expected_value,\n\t\t\ti.version AS actual_version,\n\t\t\tl.version AS expected_version,\n\t\t\ti.update_count AS actual_update_count,\n\t\t\tl.update_count AS expected_update_count,\n\t\t\ti.payload_checksum AS actual_payload_checksum,\n\t\t\tl.payload_checksum AS expected_payload_checksum,\n\t\t\ti.payload_bytes AS actual_payload_bytes,\n\t\t\tl.payload_bytes AS expected_payload_bytes\n\t\tFROM latest l\n\t\tFULL OUTER JOIN fuzz_items i ON i.item_key = l.item_key\n\t\tWHERE\n\t\t\t(l.present = 1 AND i.item_key IS NULL) OR\n\t\t\t((l.item_key IS NULL OR l.present = 0) AND i.item_key IS NOT NULL) OR\n\t\t\t(\n\t\t\t\tl.present = 1 AND i.item_key IS NOT NULL AND (\n\t\t\t\t\ti.value != l.value OR\n\t\t\t\t\ti.version != l.version OR\n\t\t\t\t\ti.update_count != l.update_count OR\n\t\t\t\t\ti.payload_checksum != l.payload_checksum OR\n\t\t\t\t\ti.payload_bytes != l.payload_bytes\n\t\t\t\t)\n\t\t\t)\n\t\tORDER BY COALESCE(l.item_key, i.item_key)\n\t\tLIMIT ?`,\n\t\tlimit,\n\t)) as Array<{\n\t\titem_key: string;\n\t\tactual_value: string | null;\n\t\texpected_value: string | null;\n\t\tactual_version: number | null;\n\t\texpected_version: number | null;\n\t\tactual_update_count: number | null;\n\t\texpected_update_count: number | null;\n\t\tactual_payload_checksum: number | null;\n\t\texpected_payload_checksum: number | null;\n\t\tactual_payload_bytes: number | null;\n\t\texpected_payload_bytes: number | null;\n\t}>;\n\n\tconst itemMismatches = rows.map((row) => ({\n\t\titemKey: row.item_key,\n\t\tactualValue: row.actual_value,\n\t\texpectedValue: row.expected_value,\n\t\tactualVersion: row.actual_version,\n\t\texpectedVersion: row.expected_version,\n\t\tactualUpdateCount: row.actual_update_count,\n\t\texpectedUpdateCount: row.expected_update_count,\n\t\tactualPayloadChecksum: row.actual_payload_checksum,\n\t\texpectedPayloadChecksum: row.expected_payload_checksum,\n\t\tactualPayloadBytes: row.actual_payload_bytes,\n\t\texpectedPayloadBytes: row.expected_payload_bytes,\n\t}));\n\n\tconst recentEventsByKey: Record = {};\n\tfor (const row of itemMismatches) {\n\t\tconst events = (await database.execute(\n\t\t\t`SELECT\n\t\t\t\tseq,\n\t\t\t\tphase,\n\t\t\t\tlocal_index,\n\t\t\t\tkind,\n\t\t\t\tpresent,\n\t\t\t\tvalue,\n\t\t\t\tversion,\n\t\t\t\tupdate_count,\n\t\t\t\tpayload_checksum,\n\t\t\t\tpayload_bytes,\n\t\t\t\tapplied\n\t\t\tFROM fuzz_item_events\n\t\t\tWHERE item_key = ?\n\t\t\tORDER BY seq DESC\n\t\t\tLIMIT 10`,\n\t\t\trow.itemKey,\n\t\t)) as Array<{\n\t\t\tseq: number;\n\t\t\tphase: number;\n\t\t\tlocal_index: number;\n\t\t\tkind: string;\n\t\t\tpresent: number;\n\t\t\tvalue: string | null;\n\t\t\tversion: number;\n\t\t\tupdate_count: number;\n\t\t\tpayload_checksum: number;\n\t\t\tpayload_bytes: number;\n\t\t\tapplied: number;\n\t\t}>;\n\t\trecentEventsByKey[row.itemKey] = events.map((event) => ({\n\t\t\tseq: event.seq,\n\t\t\tphase: event.phase,\n\t\t\tlocalIndex: event.local_index,\n\t\t\tkind: event.kind,\n\t\t\tpresent: event.present,\n\t\t\tvalue: event.value,\n\t\t\tversion: event.version,\n\t\t\tupdateCount: event.update_count,\n\t\t\tpayloadChecksum: event.payload_checksum,\n\t\t\tpayloadBytes: event.payload_bytes,\n\t\t\tapplied: event.applied,\n\t\t}));\n\t}\n\n\treturn { itemMismatches, recentEventsByKey };\n}\n\nexport const rawSqliteFuzzer = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_items (\n\t\t\t\t\titem_key TEXT PRIMARY KEY,\n\t\t\t\t\tvalue TEXT NOT NULL,\n\t\t\t\t\tversion INTEGER NOT NULL,\n\t\t\t\t\tupdate_count INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_item_events (\n\t\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tlocal_index INTEGER NOT NULL,\n\t\t\t\t\tkind TEXT NOT NULL,\n\t\t\t\t\titem_key TEXT NOT NULL,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tvalue TEXT,\n\t\t\t\t\tversion INTEGER NOT NULL,\n\t\t\t\t\tupdate_count INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tapplied INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_item_events_key_seq ON fuzz_item_events(item_key, seq)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_accounts (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tbalance INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_transfer_events (\n\t\t\t\t\tseq INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tlocal_index INTEGER NOT NULL,\n\t\t\t\t\tfrom_account TEXT NOT NULL,\n\t\t\t\t\tto_account TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL,\n\t\t\t\t\tbalance_sum_before INTEGER NOT NULL,\n\t\t\t\t\tbalance_sum_after INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_edge_payloads (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tkind TEXT NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_edge_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_trigger_audit (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tpayload_id TEXT NOT NULL,\n\t\t\t\t\told_checksum INTEGER NOT NULL,\n\t\t\t\t\tnew_checksum INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_edge_kind_size ON fuzz_edge_payloads(kind, payload_bytes)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_indexed (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\ttenant TEXT NOT NULL,\n\t\t\t\t\tbucket INTEGER NOT NULL,\n\t\t\t\t\tscore INTEGER NOT NULL,\n\t\t\t\t\tlabel TEXT NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_tenant_bucket_score ON fuzz_indexed(tenant, bucket, score)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_fuzz_indexed_score_label ON fuzz_indexed(score, label)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_rel_users (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tname TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_rel_products (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tprice INTEGER NOT NULL CHECK (price >= 0)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_orders (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tuser_id TEXT NOT NULL,\n\t\t\t\t\ttotal INTEGER NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES fuzz_rel_users(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_order_items (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id TEXT NOT NULL,\n\t\t\t\t\tproduct_id TEXT NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL CHECK (quantity > 0),\n\t\t\t\t\tprice INTEGER NOT NULL CHECK (price >= 0),\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES fuzz_orders(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_payments (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES fuzz_orders(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_inventory (\n\t\t\t\t\tproduct_id TEXT PRIMARY KEY,\n\t\t\t\t\tinitial_qty INTEGER NOT NULL,\n\t\t\t\t\tsold_qty INTEGER NOT NULL,\n\t\t\t\t\tstock_qty INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES fuzz_rel_products(id)\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_constraints (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tmust_not_null TEXT NOT NULL,\n\t\t\t\t\tqty INTEGER NOT NULL CHECK (qty >= 0),\n\t\t\t\t\tuniq TEXT NOT NULL UNIQUE\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_constraint_attempts (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\texpected_failed INTEGER NOT NULL,\n\t\t\t\t\tactually_failed INTEGER NOT NULL,\n\t\t\t\t\tbefore_count INTEGER NOT NULL,\n\t\t\t\t\tafter_count INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_fk_parent (\n\t\t\t\t\tid TEXT PRIMARY KEY\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_fk_child (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tparent_id TEXT NOT NULL,\n\t\t\t\t\tFOREIGN KEY (parent_id) REFERENCES fuzz_fk_parent(id) ON DELETE CASCADE\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_savepoints (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_savepoint_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tpresent INTEGER NOT NULL,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_idempotent_ops (\n\t\t\t\t\top_id TEXT PRIMARY KEY,\n\t\t\t\t\ttarget_id TEXT NOT NULL,\n\t\t\t\t\tamount INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_idempotent_targets (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_schema_registry (\n\t\t\t\t\tname TEXT PRIMARY KEY,\n\t\t\t\t\ttype TEXT NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_probe_results (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tphase INTEGER NOT NULL,\n\t\t\t\t\tscenario TEXT NOT NULL,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\texpected TEXT NOT NULL,\n\t\t\t\t\tactual TEXT NOT NULL,\n\t\t\t\t\tmismatch INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_prepared_churn (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_prepared_expectations (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\tpayload_checksum INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_shadow_checksums (\n\t\t\t\t\tname TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\t\trow_count INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_nasty_counter (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS fuzz_nasty_rows (\n\t\t\t\t\tgroup_id TEXT NOT NULL,\n\t\t\t\t\tn INTEGER NOT NULL,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tPRIMARY KEY (group_id, n)\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_nasty_rows\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_nasty_counter\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_shadow_checksums\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_prepared_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_prepared_churn\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_probe_results\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_schema_registry\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_idempotent_targets\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_idempotent_ops\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_savepoint_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_savepoints\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_fk_child\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_fk_parent\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_constraint_attempts\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_constraints\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_payments\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_order_items\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_orders\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_inventory\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_rel_products\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_rel_users\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_indexed\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_trigger_audit\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_edge_expectations\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_edge_payloads\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_transfer_events\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_accounts\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_item_events\");\n\t\t\tawait c.db.execute(\"DELETE FROM fuzz_items\");\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await validate(c.db);\n\t\t},\n\n\t\trunPhase: async (c, input: RunPhaseInput): Promise => {\n\t\t\tconst mode = input.mode ?? \"balanced\";\n\t\t\tconst iterations = Math.max(1, Math.floor(input.iterations));\n\t\t\tconst keySpace = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(input.keySpace ?? DEFAULT_KEY_SPACE),\n\t\t\t);\n\t\t\tconst maxPayloadBytes = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(input.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES),\n\t\t\t);\n\t\t\tconst growthTargetBytes = Math.max(\n\t\t\t\t1,\n\t\t\t\tMath.floor(\n\t\t\t\t\tinput.growthTargetBytes ?? DEFAULT_GROWTH_TARGET_BYTES,\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst rng = makeRng(`${input.seed}:${input.phase}:${mode}`);\n\t\t\tconst ops: Record = {};\n\t\t\tlet stage = \"ensureAccounts\";\n\n\t\t\ttry {\n\t\t\t\tawait ensureAccounts(c.db);\n\n\t\t\t\tfor (let i = 0; i < iterations; i += 1) {\n\t\t\t\t\tconst kind = chooseKind(mode, rng);\n\t\t\t\t\tops[kind] = (ops[kind] ?? 0) + 1;\n\t\t\t\t\tstage = `base:${kind}:iteration:${i}`;\n\n\t\t\t\t\tif (kind === \"transfer\") {\n\t\t\t\t\t\tconst fromIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1);\n\t\t\t\t\t\tlet toIndex = intBetween(rng, 0, ACCOUNT_COUNT - 1);\n\t\t\t\t\t\tif (toIndex === fromIndex)\n\t\t\t\t\t\t\ttoIndex = (toIndex + 1) % ACCOUNT_COUNT;\n\t\t\t\t\t\tconst fromAccount = `acct-${fromIndex}`;\n\t\t\t\t\t\tconst toAccount = `acct-${toIndex}`;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyTransfer(c.db, {\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\tfromAccount,\n\t\t\t\t\t\t\t\ttoAccount,\n\t\t\t\t\t\t\t\tamount: intBetween(rng, 1, 500),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation transfer failed at iteration ${i} from ${fromAccount} to ${toAccount}`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (kind === \"hot\") {\n\t\t\t\t\t\tconst itemKey = `hot-${intBetween(rng, 0, 3)}`;\n\t\t\t\t\t\tconst updates = intBetween(\n\t\t\t\t\t\t\trng,\n\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\tmode === \"hot\" ? 12 : 5,\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyHotUpdates(c.db, {\n\t\t\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\titemKey,\n\t\t\t\t\t\t\t\tupdates,\n\t\t\t\t\t\t\t\tpayloadBytes: intBetween(\n\t\t\t\t\t\t\t\t\trng,\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tmaxPayloadBytes,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation hot failed at iteration ${i} for ${itemKey} with ${updates} updates`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst itemKey =\n\t\t\t\t\t\t\tmode === \"hot\" && rng() < 0.6\n\t\t\t\t\t\t\t\t? `hot-${intBetween(rng, 0, 3)}`\n\t\t\t\t\t\t\t\t: `item-${intBetween(rng, 0, keySpace - 1)}`;\n\t\t\t\t\t\tconst payloadBytes =\n\t\t\t\t\t\t\tmode === \"payloads\"\n\t\t\t\t\t\t\t\t? intBetween(\n\t\t\t\t\t\t\t\t\t\trng,\n\t\t\t\t\t\t\t\t\t\tMath.min(256, maxPayloadBytes),\n\t\t\t\t\t\t\t\t\t\tmaxPayloadBytes,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: intBetween(rng, 1, maxPayloadBytes);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait applyItemOperation(c.db, {\n\t\t\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\t\t\tphase: input.phase,\n\t\t\t\t\t\t\t\tlocalIndex: i,\n\t\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\titemKey,\n\t\t\t\t\t\t\t\tpayloadBytes,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`base operation ${kind} failed at iteration ${i} for ${JSON.stringify(itemKey)} with payloadBytes ${payloadBytes}`,\n\t\t\t\t\t\t\t\t{ cause: error },\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstage = \"deep-scenarios\";\n\t\t\t\tawait applyDeepScenarios(c.db, {\n\t\t\t\t\tseed: input.seed,\n\t\t\t\t\tphase: input.phase,\n\t\t\t\t\tmode,\n\t\t\t\t\titerations,\n\t\t\t\t\trng,\n\t\t\t\t\tmaxPayloadBytes,\n\t\t\t\t\tgrowthTargetBytes,\n\t\t\t\t\tops,\n\t\t\t\t});\n\n\t\t\t\tstage = \"validate\";\n\t\t\t\treturn {\n\t\t\t\t\tseed: input.seed,\n\t\t\t\t\tphase: input.phase,\n\t\t\t\t\tmode,\n\t\t\t\t\titerations,\n\t\t\t\t\tops,\n\t\t\t\t\tvalidation: await validate(c.db),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconst detail =\n\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t: typeof error === \"string\"\n\t\t\t\t\t\t\t? error\n\t\t\t\t\t\t\t: String(error);\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`runPhase failed during ${stage} for mode ${mode} phase ${input.phase} seed ${input.seed}: ${detail}`,\n\t\t\t\t\t{ cause: error },\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tvalidate: async (c) => {\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await validate(c.db);\n\t\t},\n\n\t\tdebugItemMismatches: async (c, limit?: number) => {\n\t\t\tawait ensureAccounts(c.db);\n\t\t\treturn await debugItemMismatches(c.db, limit ?? 5);\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const DEFAULT_ON_SLEEP_DURATION_MS = 5_000;\nexport const DEFAULT_ON_SLEEP_TICK_MS = 1_000;\nconst SLEEP_TIMEOUT_MS = 10 * 60 * 1000;\nconst SLEEP_GRACE_PERIOD_MS = 30 * 60 * 1000;\nconst ACTOR_STOPPED_CLOSE_CODE = 1000;\nconst ACTOR_STOPPED_CLOSE_REASON = \"actor stopped\";\n\nfunction sleep(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction formatError(error: unknown): string {\n\tif (error instanceof Error) return error.stack ?? error.message;\n\treturn String(error);\n}\n\nexport const sigtermSleepProbe = actor({\n\tstate: {\n\t\tlabel: \"unprepared\",\n\t\twakeCount: 0,\n\t\tsleepCount: 0,\n\t\tonSleepDurationMs: DEFAULT_ON_SLEEP_DURATION_MS,\n\t\tonSleepTickMs: DEFAULT_ON_SLEEP_TICK_MS,\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t\tonSleepStartedAt: null as number | null,\n\t\tonSleepAsyncFinishedAt: null as number | null,\n\t\tonSleepFinishedAt: null as number | null,\n\t\tonSleepLastError: null as string | null,\n\t},\n\tcreateVars: () => ({\n\t\twebsockets: new Set(),\n\t}),\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS sigterm_sleep_log (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tevent TEXT NOT NULL,\n\t\t\t\t\tsleep_count INTEGER NOT NULL,\n\t\t\t\t\tdetail TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tonWebSocket: (c, websocket: UniversalWebSocket) => {\n\t\tc.vars.websockets.add(websocket);\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe websocket connected\",\n\t\t\tlabel: c.state.label,\n\t\t\tconnectionId,\n\t\t\tconnectionCount: c.state.connectionCount,\n\t\t});\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionId,\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.messageCount += 1;\n\t\t\tconst data = event.data;\n\t\t\tif (typeof data !== \"string\") return;\n\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(data);\n\t\t\t\tif (parsed.type === \"ping\") {\n\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {}\n\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"echo\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\treceived: data,\n\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", (event) => {\n\t\t\tc.vars.websockets.delete(websocket);\n\t\t\tc.state.connectionCount -= 1;\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"sigterm sleep probe websocket closed\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tcode: event.code,\n\t\t\t\treason: event.reason,\n\t\t\t});\n\t\t});\n\t},\n\tonWake: async (c) => {\n\t\tc.state.wakeCount += 1;\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe onWake\",\n\t\t\tlabel: c.state.label,\n\t\t\twakeCount: c.state.wakeCount,\n\t\t\tsleepCount: c.state.sleepCount,\n\t\t});\n\t\tawait c.db.execute(\n\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\"wake\",\n\t\t\tc.state.sleepCount,\n\t\t\t`wake-${c.state.wakeCount}`,\n\t\t\tDate.now(),\n\t\t);\n\t},\n\tonSleep: async (c) => {\n\t\tconst sleepCount = c.state.sleepCount + 1;\n\t\tconst startedAt = Date.now();\n\t\tc.state.sleepCount = sleepCount;\n\t\tc.state.onSleepStartedAt = startedAt;\n\t\tc.state.onSleepAsyncFinishedAt = null;\n\t\tc.state.onSleepFinishedAt = null;\n\t\tc.state.onSleepLastError = null;\n\n\t\tc.log.info({\n\t\t\tmsg: \"sigterm sleep probe onSleep start\",\n\t\t\tlabel: c.state.label,\n\t\t\tsleepCount,\n\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t});\n\n\t\ttry {\n\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"onSleepStarted\",\n\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\t\t\ttimestamp: startedAt,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-start\",\n\t\t\t\tsleepCount,\n\t\t\t\tc.state.label,\n\t\t\t\tstartedAt,\n\t\t\t);\n\n\t\t\tconst deadline = startedAt + c.state.onSleepDurationMs;\n\t\t\tlet tickIndex = 0;\n\t\t\twhile (Date.now() < deadline) {\n\t\t\t\tconst waitMs = Math.min(\n\t\t\t\t\tc.state.onSleepTickMs,\n\t\t\t\t\tMath.max(0, deadline - Date.now()),\n\t\t\t\t);\n\t\t\t\tif (waitMs > 0) await sleep(waitMs);\n\n\t\t\t\ttickIndex += 1;\n\t\t\t\tconst tickAt = Date.now();\n\t\t\t\tconst detail = `tick=${tickIndex} elapsed-ms=${tickAt - startedAt}`;\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t\"on-sleep-tick\",\n\t\t\t\t\tsleepCount,\n\t\t\t\t\tdetail,\n\t\t\t\t\ttickAt,\n\t\t\t\t);\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"sigterm sleep probe onSleep tick\",\n\t\t\t\t\tlabel: c.state.label,\n\t\t\t\t\tsleepCount,\n\t\t\t\t\ttickIndex,\n\t\t\t\t\telapsedMs: tickAt - startedAt,\n\t\t\t\t});\n\n\t\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\t\twebsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: \"onSleepTick\",\n\t\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\t\ttickIndex,\n\t\t\t\t\t\t\telapsedMs: tickAt - startedAt,\n\t\t\t\t\t\t\ttimestamp: tickAt,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst asyncFinishedAt = Date.now();\n\t\t\tc.state.onSleepAsyncFinishedAt = asyncFinishedAt;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-after-await\",\n\t\t\t\tsleepCount,\n\t\t\t\t`delay-ms=${asyncFinishedAt - startedAt}`,\n\t\t\t\tasyncFinishedAt,\n\t\t\t);\n\n\t\t\tconst finishedAt = Date.now();\n\t\t\tc.state.onSleepFinishedAt = finishedAt;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"on-sleep-finish\",\n\t\t\t\tsleepCount,\n\t\t\t\tc.state.label,\n\t\t\t\tfinishedAt,\n\t\t\t);\n\n\t\t\tfor (const websocket of c.vars.websockets) {\n\t\t\t\tif (websocket.readyState !== 1) continue;\n\t\t\t\twebsocket.send(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\ttype: \"onSleepFinished\",\n\t\t\t\t\t\tsleepCount,\n\t\t\t\t\t\telapsedMs: finishedAt - startedAt,\n\t\t\t\t\t\ttimestamp: finishedAt,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\twebsocket.close(\n\t\t\t\t\tACTOR_STOPPED_CLOSE_CODE,\n\t\t\t\t\tACTOR_STOPPED_CLOSE_REASON,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tc.log.info({\n\t\t\t\tmsg: \"sigterm sleep probe onSleep finish\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tsleepCount,\n\t\t\t\telapsedMs: finishedAt - startedAt,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = formatError(error);\n\t\t\tc.state.onSleepLastError = message;\n\t\t\tc.log.error({\n\t\t\t\tmsg: \"sigterm sleep probe onSleep error\",\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tsleepCount,\n\t\t\t\terror: message,\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t},\n\tactions: {\n\t\tprepare: async (\n\t\t\tc,\n\t\t\tlabel = `sigterm-sleep-probe-${Date.now()}`,\n\t\t\tonSleepDurationMs = DEFAULT_ON_SLEEP_DURATION_MS,\n\t\t\tonSleepTickMs = DEFAULT_ON_SLEEP_TICK_MS,\n\t\t) => {\n\t\t\tif (!Number.isFinite(onSleepDurationMs) || onSleepDurationMs < 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"onSleepDurationMs must be a finite non-negative number\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!Number.isFinite(onSleepTickMs) || onSleepTickMs <= 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"onSleepTickMs must be a finite positive number\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tc.state.label = label;\n\t\t\tc.state.onSleepDurationMs = onSleepDurationMs;\n\t\t\tc.state.onSleepTickMs = onSleepTickMs;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO sigterm_sleep_log (event, sleep_count, detail, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"prepared\",\n\t\t\t\tc.state.sleepCount,\n\t\t\t\tlabel,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tlabel: c.state.label,\n\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t};\n\t\t},\n\t\tgetProof: async (c) => {\n\t\t\tconst rows = await c.db.execute<{\n\t\t\t\tid: number;\n\t\t\t\tevent: string;\n\t\t\t\tsleep_count: number;\n\t\t\t\tdetail: string | null;\n\t\t\t\tcreated_at: number;\n\t\t\t}>(\"SELECT * FROM sigterm_sleep_log ORDER BY id\");\n\t\t\treturn {\n\t\t\t\tstate: {\n\t\t\t\t\tlabel: c.state.label,\n\t\t\t\t\twakeCount: c.state.wakeCount,\n\t\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\t\tonSleepDurationMs: c.state.onSleepDurationMs,\n\t\t\t\t\tonSleepTickMs: c.state.onSleepTickMs,\n\t\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t\t\tonSleepStartedAt: c.state.onSleepStartedAt,\n\t\t\t\t\tonSleepAsyncFinishedAt: c.state.onSleepAsyncFinishedAt,\n\t\t\t\t\tonSleepFinishedAt: c.state.onSleepFinishedAt,\n\t\t\t\t\tonSleepLastError: c.state.onSleepLastError,\n\t\t\t\t},\n\t\t\t\trows,\n\t\t\t};\n\t\t},\n\t},\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t\tsleepTimeout: SLEEP_TIMEOUT_MS,\n\t\tsleepGracePeriod: SLEEP_GRACE_PERIOD_MS,\n\t},\n});\n","import {\n\tactor,\n\ttype RivetMessageEvent,\n\ttype UniversalWebSocket,\n} from \"rivetkit\";\n\n// Minimal non-hibernatable WebSocket actor for fuzz-testing the\n// force-sleep → gateway close path. Keeps state intentionally tiny so the\n// only thing being exercised is the close lifecycle, not user code.\nexport const sleepCloseFuzz = actor({\n\toptions: {\n\t\tcanHibernateWebSocket: false,\n\t},\n\tstate: {\n\t\tconnectionCount: 0,\n\t\tmessageCount: 0,\n\t},\n\tonWebSocket(c, websocket: UniversalWebSocket) {\n\t\tc.state.connectionCount += 1;\n\t\tconst connectionId = crypto.randomUUID();\n\n\t\twebsocket.send(\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"welcome\",\n\t\t\t\tconnectionId,\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t}),\n\t\t);\n\n\t\tconst interval = setInterval(() => {\n\t\t\tif (websocket.readyState !== 1) return;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"tick\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t}),\n\t\t\t);\n\t\t}, 500);\n\n\t\twebsocket.addEventListener(\"message\", (event: RivetMessageEvent) => {\n\t\t\tc.state.messageCount += 1;\n\t\t\twebsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\ttype: \"echo\",\n\t\t\t\t\tconnectionId,\n\t\t\t\t\treceived: event.data,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\n\t\twebsocket.addEventListener(\"close\", () => {\n\t\t\tclearInterval(interval);\n\t\t\tc.state.connectionCount -= 1;\n\t\t});\n\t},\n\tactions: {\n\t\tgetStats(c) {\n\t\t\treturn {\n\t\t\t\tconnectionCount: c.state.connectionCount,\n\t\t\t\tmessageCount: c.state.messageCount,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor, setup } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport type SlowReconnectRequest =\n\t| { type: \"client_resume\"; version: number }\n\t| {\n\t\t\ttype: \"executor_connect\";\n\t\t\tclientId: string;\n\t\t\texecutorType?: \"local-client\" | \"sandbox\" | \"virtual\";\n\t }\n\t| {\n\t\t\ttype: \"repro_reconnect\";\n\t\t\tclientId?: string;\n\t\t\tstaggerHandleMs?: number;\n\t };\n\nexport interface SlowReconnectStep {\n\tname: string;\n\tdurationMs: number;\n\trowCount: number;\n}\n\nexport interface SlowReconnectWorkloadResult {\n\tname: string;\n\ttotalMs: number;\n\tsteps: SlowReconnectStep[];\n}\n\nexport interface SlowReconnectResultMessage {\n\ttype: \"slow_reconnect_result\";\n\ttrigger: SlowReconnectRequest[\"type\"];\n\ttotalMs: number;\n\tresults: SlowReconnectWorkloadResult[];\n}\n\nexport interface SlowReconnectErrorMessage {\n\ttype: \"slow_reconnect_error\";\n\ttrigger: SlowReconnectRequest[\"type\"] | \"unknown\";\n\terror: string;\n}\n\nexport interface SlowReconnectVars {\n\tsql: Db | null;\n}\n\ninterface RawRivetDB {\n\texecute: (\n\t\tquery: string,\n\t\t...args: unknown[]\n\t) => Promise[]>;\n}\n\ntype SQLPrimitive = string | number | boolean | null;\n\ntype Db = (>(\n\tquery: string,\n\t...values: SQLPrimitive[]\n) => Promise) & {\n\twithTransaction(fn: (tx: Db) => Promise): Promise;\n};\n\nclass AsyncMutex {\n\tprivate locked = false;\n\tprivate waiters: Array<() => void> = [];\n\n\tasync acquire(): Promise {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true;\n\t\t\treturn;\n\t\t}\n\t\tawait new Promise((resolve) => this.waiters.push(resolve));\n\t\tthis.locked = true;\n\t}\n\n\trelease(): void {\n\t\tconst next = this.waiters.shift();\n\t\tif (next) {\n\t\t\tnext();\n\t\t\treturn;\n\t\t}\n\t\tthis.locked = false;\n\t}\n}\n\nfunction createDb(\n\texecute: >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t) => Promise,\n): Db {\n\tconst mutex = new AsyncMutex();\n\tlet activeTransaction: Db | null = null;\n\n\tconst createTransactionDb = (): Db => {\n\t\tconst tx = Object.assign(\n\t\t\t>(\n\t\t\t\tquery: string,\n\t\t\t\t...values: SQLPrimitive[]\n\t\t\t) => execute(query, ...values),\n\t\t\t{\n\t\t\t\twithTransaction: async (\n\t\t\t\t\tfn: (tx: Db) => Promise,\n\t\t\t\t): Promise => fn(tx),\n\t\t\t},\n\t\t);\n\t\treturn tx;\n\t};\n\n\tconst queryWithMutex = async >(\n\t\tquery: string,\n\t\t...values: SQLPrimitive[]\n\t): Promise => {\n\t\tif (activeTransaction) {\n\t\t\treturn activeTransaction(query, ...values);\n\t\t}\n\t\tawait mutex.acquire();\n\t\ttry {\n\t\t\treturn await execute(query, ...values);\n\t\t} finally {\n\t\t\tmutex.release();\n\t\t}\n\t};\n\n\tconst sql = Object.assign(queryWithMutex, {\n\t\twithTransaction: async (fn: (tx: Db) => Promise): Promise => {\n\t\t\tif (activeTransaction) {\n\t\t\t\treturn fn(activeTransaction);\n\t\t\t}\n\t\t\tawait mutex.acquire();\n\t\t\tconst tx = createTransactionDb();\n\t\t\ttry {\n\t\t\t\tawait execute(\"BEGIN\");\n\t\t\t\tactiveTransaction = tx;\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fn(tx);\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait execute(\"COMMIT\");\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tactiveTransaction = null;\n\t\t\t\t\tawait execute(\"ROLLBACK\");\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tactiveTransaction = null;\n\t\t\t\tmutex.release();\n\t\t\t}\n\t\t},\n\t});\n\treturn sql;\n}\n\nconst MESSAGE_COUNT = 84;\nconst MESSAGE_TOOL_REF_COUNT = 122;\nconst TOOL_CALL_COUNT = 61;\nconst EXECUTOR_TOOL_COUNT = 42;\nconst THREAD_EVENT_COUNT = 233;\n\nconst MESSAGE_CONTENT_BYTES = 10_620;\nconst THREAD_EVENT_PAYLOAD_BYTES = 4_036;\nconst TOOL_CALL_RESULT_BYTES = 10_975;\nconst EXECUTOR_TOOL_SCHEMA_BYTES = 2_235;\n\nexport const slowReconnectActor = actor({\n\tstate: { runCount: 0 },\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait createSlowReconnectSchema(database);\n\t\t},\n\t}),\n\tvars: { sql: null } as SlowReconnectVars,\n\tonWebSocket: (c, ws) => {\n\t\tconst sock = ws as unknown as WebSocket;\n\t\tif (sock.readyState === WebSocket.OPEN) {\n\t\t\tsock.send(\"pong\");\n\t\t}\n\n\t\tws.addEventListener(\"message\", (event) => {\n\t\t\tconst promise = handleSlowReconnectWebSocketMessage(\n\t\t\t\tc,\n\t\t\t\tsock,\n\t\t\t\tevent.data,\n\t\t\t);\n\t\t\tvoid c.keepAwake(promise);\n\t\t});\n\t},\n\tactions: {\n\t\tprepare: async (c) => {\n\t\t\tawait createSlowReconnectSchema(c.db);\n\t\t\treturn await seedSlowReconnectData(c.db);\n\t\t},\n\t\treproReconnect: async (c, clientId?: string) => {\n\t\t\tc.vars.sql ??= createSlowReconnectDb(c.db);\n\t\t\tc.state.runCount++;\n\t\t\treturn await runReconnectRepro(\n\t\t\t\tc.vars.sql,\n\t\t\t\tclientId ?? `action-${c.state.runCount}`,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\tgetRunCount: (c) => c.state.runCount,\n\t\tsleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn true;\n\t\t},\n\t},\n});\n\nasync function handleSlowReconnectWebSocketMessage(\n\tc: { db: RawRivetDB; vars: SlowReconnectVars; state: { runCount: number } },\n\tsock: WebSocket,\n\tdata: unknown,\n): Promise {\n\tif (data === \"ping\") {\n\t\tif (sock.readyState === WebSocket.OPEN) {\n\t\t\tsock.send(\"pong\");\n\t\t}\n\t\treturn;\n\t}\n\n\tlet trigger: SlowReconnectRequest[\"type\"] | \"unknown\" = \"unknown\";\n\ttry {\n\t\tconst request = parseSlowReconnectRequest(data);\n\t\ttrigger = request.type;\n\t\tc.vars.sql ??= createSlowReconnectDb(c.db);\n\t\tc.state.runCount++;\n\n\t\tif (request.type === \"client_resume\") {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst result = await runCatchupSnapshot(\n\t\t\t\tc.vars.sql,\n\t\t\t\trequest.version,\n\t\t\t);\n\t\t\tsendJSON(sock, {\n\t\t\t\ttype: \"slow_reconnect_result\",\n\t\t\t\ttrigger: request.type,\n\t\t\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\t\t\tresults: [result],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientId =\n\t\t\trequest.type === \"executor_connect\"\n\t\t\t\t? request.clientId\n\t\t\t\t: (request.clientId ?? `slow-reconnect-${c.state.runCount}`);\n\t\tconst staggerHandleMs =\n\t\t\trequest.type === \"repro_reconnect\"\n\t\t\t\t? (request.staggerHandleMs ?? 0)\n\t\t\t\t: 0;\n\t\tconst result = await runReconnectRepro(\n\t\t\tc.vars.sql,\n\t\t\tclientId,\n\t\t\tstaggerHandleMs,\n\t\t);\n\n\t\tif (request.type === \"executor_connect\") {\n\t\t\tsendJSON(sock, {\n\t\t\t\ttype: \"executor_connected\",\n\t\t\t\texecutorId: clientId,\n\t\t\t\tregisteredToolCount: EXECUTOR_TOOL_COUNT,\n\t\t\t\tguidanceInventory: [],\n\t\t\t\tresumeBootstrap: true,\n\t\t\t});\n\t\t}\n\n\t\tsendJSON(sock, {\n\t\t\ttype: \"slow_reconnect_result\",\n\t\t\ttrigger: request.type,\n\t\t\t...result,\n\t\t});\n\t} catch (error) {\n\t\tsendJSON(sock, {\n\t\t\ttype: \"slow_reconnect_error\",\n\t\t\ttrigger,\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t});\n\t}\n}\n\nfunction parseSlowReconnectRequest(data: unknown): SlowReconnectRequest {\n\tif (typeof data !== \"string\") {\n\t\tthrow new Error(\"slowReconnectActor request must be a string\");\n\t}\n\tconst parsed = JSON.parse(data) as unknown;\n\tif (!parsed || typeof parsed !== \"object\") {\n\t\tthrow new Error(\"slowReconnectActor request must be an object\");\n\t}\n\tconst request = parsed as Record;\n\tif (request.type === \"client_resume\") {\n\t\treturn {\n\t\t\ttype: \"client_resume\",\n\t\t\tversion: numberField(request, \"version\"),\n\t\t};\n\t}\n\tif (request.type === \"executor_connect\") {\n\t\tconst executorType = request.executorType;\n\t\treturn {\n\t\t\ttype: \"executor_connect\",\n\t\t\tclientId: stringField(request, \"clientId\"),\n\t\t\t...(executorType === \"local-client\" ||\n\t\t\texecutorType === \"sandbox\" ||\n\t\t\texecutorType === \"virtual\"\n\t\t\t\t? { executorType }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tif (request.type === \"repro_reconnect\") {\n\t\treturn {\n\t\t\ttype: \"repro_reconnect\",\n\t\t\t...(typeof request.clientId === \"string\"\n\t\t\t\t? { clientId: request.clientId }\n\t\t\t\t: {}),\n\t\t\t...(typeof request.staggerHandleMs === \"number\"\n\t\t\t\t? { staggerHandleMs: request.staggerHandleMs }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\tthrow new Error(\n\t\t`Unknown slowReconnectActor request type: ${String(request.type)}`,\n\t);\n}\n\nfunction stringField(record: Record, field: string): string {\n\tconst value = record[field];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(\n\t\t\t`slowReconnectActor request ${field} must be a non-empty string`,\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction numberField(record: Record, field: string): number {\n\tconst value = record[field];\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(\n\t\t\t`slowReconnectActor request ${field} must be a finite number`,\n\t\t);\n\t}\n\treturn value;\n}\n\nfunction sendJSON(\n\tsock: WebSocket,\n\tmessage: SlowReconnectResultMessage | SlowReconnectErrorMessage | object,\n): void {\n\tif (sock.readyState === WebSocket.OPEN) {\n\t\tsock.send(JSON.stringify(message));\n\t}\n}\n\nfunction createSlowReconnectDb(db: RawRivetDB): Db {\n\treturn createDb(\n\t\tasync >(\n\t\t\tquery: string,\n\t\t\t...values: SQLPrimitive[]\n\t\t): Promise => {\n\t\t\tconst converted = values.map((value) =>\n\t\t\t\ttypeof value === \"boolean\" ? (value ? 1 : 0) : value,\n\t\t\t);\n\t\t\treturn (await db.execute(query, ...converted)) as T[];\n\t\t},\n\t);\n}\n\nasync function runReconnectRepro(\n\tsql: Db,\n\tclientId: string,\n\tstaggerHandleMs: number,\n): Promise> {\n\tconst startedAt = performance.now();\n\tconst buildToolPlanContext = await runBuildToolPlanContext(sql);\n\tconst catchupSnapshot = await runCatchupSnapshot(sql, 0);\n\tconst recoverToolCalls = await runRecoverToolCalls(sql);\n\tconst handleExecutorConnect = await delay(staggerHandleMs).then(() =>\n\t\trunHandleExecutorConnect(sql, clientId),\n\t);\n\n\tconst results = [\n\t\thandleExecutorConnect,\n\t\tbuildToolPlanContext,\n\t\tcatchupSnapshot,\n\t\trecoverToolCalls,\n\t];\n\n\treturn {\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tresults,\n\t};\n}\n\nasync function runHandleExecutorConnect(\n\tsql: Db,\n\tclientId: string,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: SlowReconnectStep[] = [];\n\tconst nextSeq = await sql.withTransaction(async (tx) => {\n\t\tconst latestExecutor = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"load-latest-executor-id\",\n\t\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t\t);\n\t\tconst latestExecutorId = String(\n\t\t\tlatestExecutor[0]?.executor_id ?? \"seed-executor\",\n\t\t);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"select-cached-executor-tools\",\n\t\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\t\tlatestExecutorId,\n\t\t);\n\t\tconst executorType = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"select-executor-type\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_type'`,\n\t\t);\n\t\tif (!executorType[0]?.value) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tsteps,\n\t\t\t\t\"set-executor-type\",\n\t\t\t\t`INSERT OR REPLACE INTO thread_meta_kv (key, value, updated_at) VALUES ('executor_type', ?, ?)`,\n\t\t\t\t\"local-client\",\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst sandboxIntent = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"select-sandbox-intent\",\n\t\t\t`SELECT value FROM thread_meta_kv WHERE key = 'sandbox_intent'`,\n\t\t);\n\t\tif (hasPendingLaunch(sandboxIntent[0]?.value)) {\n\t\t\tawait timedQuery(\n\t\t\t\ttx,\n\t\t\t\tsteps,\n\t\t\t\t\"clear-pending-launch\",\n\t\t\t\t`UPDATE thread_meta_kv SET value = ?, updated_at = ? WHERE key = 'sandbox_intent'`,\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tnew Date().toISOString(),\n\t\t\t);\n\t\t}\n\t\tconst seqRows = await timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"select-next-thread-event-seq\",\n\t\t\t`SELECT COALESCE(MAX(seq), 0) + 1 AS seq FROM thread_events`,\n\t\t);\n\t\tconst seq = Number(seqRows[0]?.seq ?? 1);\n\t\tawait timedQuery(\n\t\t\ttx,\n\t\t\tsteps,\n\t\t\t\"insert-executor-connected-event\",\n\t\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at) VALUES (?, ?, ?, ?)`,\n\t\t\tseq,\n\t\t\t\"executor_connected\",\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"executor_connected\",\n\t\t\t\texecutorId: clientId,\n\t\t\t}),\n\t\t\tnew Date().toISOString(),\n\t\t);\n\t\treturn seq;\n\t});\n\tsteps.push({\n\t\tname: \"transaction-total\",\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: nextSeq,\n\t});\n\treturn {\n\t\tname: \"handle-executor-connect\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runBuildToolPlanContext(\n\tsql: Db,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: SlowReconnectStep[] = [];\n\tconst latestExecutor = await timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"load-latest-executor-id\",\n\t\t`SELECT executor_id FROM executor_tools ORDER BY updated_at DESC LIMIT 1`,\n\t);\n\tconst latestExecutorId = String(\n\t\tlatestExecutor[0]?.executor_id ?? \"seed-executor\",\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"select-executor-tools\",\n\t\t`SELECT tool_name, schema FROM executor_tools WHERE executor_id = ? ORDER BY tool_name ASC`,\n\t\tlatestExecutorId,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"count-uncancelled-top-level\",\n\t\t`SELECT COUNT(*) as count FROM messages WHERE cancelled = 0 AND parent_tool_use_id IS NULL`,\n\t);\n\tconst unresolvedRows = await timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"find-unresolved-assistant-message\",\n\t\t`SELECT m.*\n\t\t\tFROM message_tool_refs AS tool_use\n\t\t\tJOIN messages AS m\n\t\t\t\tON m.message_id = tool_use.assistant_message_id\n\t\t\tWHERE tool_use.block_type = 'tool_use'\n\t\t\t\tAND tool_use.cancelled = 0\n\t\t\t\tAND m.cancelled = 0\n\t\t\t\tAND m.role = 'assistant'\n\t\t\t\tAND m.parent_tool_use_id IS NULL\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\t\tWHERE tool_result.assistant_message_id = tool_use.assistant_message_id\n\t\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\t\tAND tool_result.tool_use_id = tool_use.tool_use_id\n\t\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL\n\t\t\t\t)\n\t\t\tGROUP BY m.message_id\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tconst unresolvedMessageId = unresolvedRows[0]?.message_id;\n\tif (typeof unresolvedMessageId === \"string\") {\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tsteps,\n\t\t\t\"get-persisted-tool-result-ids\",\n\t\t\t`SELECT tool_result.tool_use_id\n\t\t\t\tFROM message_tool_refs AS tool_result\n\t\t\t\tJOIN messages AS tool_result_message\n\t\t\t\t\tON tool_result_message.message_id = tool_result.source_message_id\n\t\t\t\tWHERE tool_result.assistant_message_id = ?\n\t\t\t\t\tAND tool_result.block_type = 'tool_result'\n\t\t\t\t\tAND tool_result.cancelled = 0\n\t\t\t\t\tAND tool_result_message.parent_tool_use_id IS NULL`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t\tawait timedQuery(\n\t\t\tsql,\n\t\t\tsteps,\n\t\t\t\"get-tool-calls-by-message-id\",\n\t\t\t`SELECT * FROM tool_calls WHERE message_id = ?`,\n\t\t\tunresolvedMessageId,\n\t\t);\n\t}\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"is-last-message-cancelled-assistant\",\n\t\t`SELECT role, cancelled FROM messages\n\t\t\tWHERE parent_tool_use_id IS NULL\n\t\t\tORDER BY created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"get-last-uncancelled\",\n\t\t`SELECT m.* FROM messages m\n\t\t\tWHERE m.cancelled = 0 AND m.parent_tool_use_id IS NULL\n\t\t\tORDER BY m.created_at DESC\n\t\t\tLIMIT 1`,\n\t);\n\n\treturn {\n\t\tname: \"build-tool-plan-context\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runCatchupSnapshot(\n\tsql: Db,\n\tversion: number,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: SlowReconnectStep[] = [];\n\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"thread-events-list-since-version\",\n\t\t`SELECT seq, event_type, payload, created_at FROM thread_events WHERE seq > ? ORDER BY seq ASC`,\n\t\tversion,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"environment-snapshot\",\n\t\t`SELECT snapshot FROM environment_snapshot WHERE id = 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"thread-settings-snapshot\",\n\t\t`SELECT settings FROM thread_settings_snapshot WHERE id = 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"retry-state\",\n\t\t`SELECT * FROM retry_state WHERE id = 1`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"queued-messages\",\n\t\t`SELECT * FROM queued_messages ORDER BY created_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"executor-artifacts\",\n\t\t`SELECT artifact_key, data_type, length(content_base64) AS bytes, tool_call_id, updated_at FROM executor_artifacts ORDER BY updated_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"tool-approvals\",\n\t\t`SELECT * FROM tool_approvals ORDER BY timestamp ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"compaction-summaries\",\n\t\t`SELECT cut_message_id, created_at FROM compaction_summaries ORDER BY created_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"executor-status\",\n\t\t`SELECT value FROM thread_meta_kv WHERE key = 'executor_status'`,\n\t);\n\n\tsteps.sort((a, b) => b.durationMs - a.durationMs);\n\treturn {\n\t\tname: \"catchup-snapshot\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function runRecoverToolCalls(\n\tsql: Db,\n): Promise {\n\tconst startedAt = performance.now();\n\tconst steps: SlowReconnectStep[] = [];\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"hydrate-tool-progress\",\n\t\t`SELECT id, progress\n\t\t\tFROM tool_calls\n\t\t\tWHERE progress IS NOT NULL\n\t\t\t\tAND state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"get-pending-tool-calls\",\n\t\t`SELECT * FROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')\n\t\t\tORDER BY issued_at ASC`,\n\t);\n\tawait timedQuery(\n\t\tsql,\n\t\tsteps,\n\t\t\"get-next-tool-expiry\",\n\t\t`SELECT MIN(expires_at) AS expires_at\n\t\t\tFROM tool_calls\n\t\t\tWHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\treturn {\n\t\tname: \"recover-tool-calls\",\n\t\ttotalMs: Math.round(performance.now() - startedAt),\n\t\tsteps,\n\t};\n}\n\nasync function timedQuery>(\n\tsql: Db,\n\tsteps: SlowReconnectStep[],\n\tname: string,\n\tquery: string,\n\t...values: SQLPrimitive[]\n): Promise {\n\tconst startedAt = performance.now();\n\tconst rows = await sql(query, ...values);\n\tsteps.push({\n\t\tname,\n\t\tdurationMs: Math.round(performance.now() - startedAt),\n\t\trowCount: rows.length,\n\t});\n\treturn rows;\n}\n\nfunction hasPendingLaunch(value: unknown): boolean {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn false;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(value) as { pendingLaunch?: unknown };\n\t\treturn (\n\t\t\tparsed.pendingLaunch !== null && parsed.pendingLaunch !== undefined\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));\n}\n\nasync function createSlowReconnectSchema(database: RawRivetDB): Promise {\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS executor_tools (\n\t\texecutor_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\tschema TEXT NOT NULL,\n\t\tupdated_at TEXT NOT NULL,\n\t\tPRIMARY KEY (executor_id, tool_name)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_executor_tools_executor ON executor_tools(executor_id)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_meta_kv (\n\t\tkey TEXT PRIMARY KEY,\n\t\tvalue TEXT,\n\t\tupdated_at TEXT NOT NULL\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS thread_events (\n\t\tseq INTEGER PRIMARY KEY,\n\t\tevent_type TEXT NOT NULL,\n\t\tpayload TEXT NOT NULL,\n\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_thread_events_seq ON thread_events(seq)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_added_events (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\tseq INTEGER NOT NULL UNIQUE\n\t)`);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS messages (\n\t\tmessage_id TEXT PRIMARY KEY,\n\t\trole TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'info')),\n\t\tcontent TEXT NOT NULL,\n\t\tmeta TEXT,\n\t\tuser_state TEXT,\n\t\tcreated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tread_at TEXT,\n\t\tparent_tool_use_id TEXT,\n\t\ttool_result_for_message_id TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_role_cancelled_created_at ON messages(parent_tool_use_id, role, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_cancelled_created_at ON messages(parent_tool_use_id, cancelled, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_parent_created_at ON messages(parent_tool_use_id, created_at DESC)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_messages_role_created_at ON messages(role, created_at)`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS message_tool_refs (\n\t\tsource_message_id TEXT NOT NULL,\n\t\tassistant_message_id TEXT NOT NULL,\n\t\ttool_use_id TEXT NOT NULL,\n\t\tblock_type TEXT NOT NULL CHECK(block_type IN ('tool_use', 'tool_result')),\n\t\tcancelled INTEGER NOT NULL DEFAULT 0,\n\t\tPRIMARY KEY (source_message_id, block_type, tool_use_id)\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_assistant_lookup ON message_tool_refs(assistant_message_id, block_type, cancelled, tool_use_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE UNIQUE INDEX IF NOT EXISTS idx_message_tool_refs_live_tool_result ON message_tool_refs(assistant_message_id, tool_use_id) WHERE block_type = 'tool_result' AND cancelled = 0`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_source_message ON message_tool_refs(source_message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_message_tool_refs_tool_use_lookup ON message_tool_refs(tool_use_id, assistant_message_id) WHERE block_type = 'tool_use' AND cancelled = 0`,\n\t);\n\tawait database.execute(`CREATE TABLE IF NOT EXISTS tool_calls (\n\t\tid TEXT PRIMARY KEY,\n\t\tprovider_tool_use_id TEXT NOT NULL,\n\t\ttool_name TEXT NOT NULL,\n\t\targs TEXT NOT NULL,\n\t\texecutor_id TEXT,\n\t\tmessage_id TEXT NOT NULL,\n\t\tissued_at TEXT NOT NULL,\n\t\texpires_at TEXT,\n\t\tstate TEXT NOT NULL CHECK(state IN ('queued', 'pending_reconnect', 'pending_ack', 'running', 'completed', 'expired', 'revoked')),\n\t\tresult TEXT,\n\t\tprogress TEXT,\n\t\tcompleted_at TEXT\n\t)`);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_message_id ON tool_calls(message_id)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_state ON tool_calls(state)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_calls_expires_at ON tool_calls(expires_at) WHERE state IN ('queued', 'pending_reconnect', 'pending_ack', 'running')`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS environment_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), snapshot TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS thread_settings_snapshot (id INTEGER PRIMARY KEY CHECK (id = 1), settings TEXT NOT NULL, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS retry_state (id INTEGER PRIMARY KEY CHECK (id = 1), attempt INTEGER NOT NULL DEFAULT 0, scheduled_at INTEGER NOT NULL, reason TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS queued_messages (message_id TEXT PRIMARY KEY, content TEXT NOT NULL, user_state TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), steer INTEGER NOT NULL DEFAULT 0, user_meta TEXT)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS executor_artifacts (artifact_key TEXT PRIMARY KEY, data_type TEXT NOT NULL, content_base64 TEXT NOT NULL, tool_call_id TEXT, updated_at TEXT NOT NULL)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS tool_approvals (id TEXT PRIMARY KEY, tool_call_id TEXT NOT NULL UNIQUE, tool_name TEXT NOT NULL, args TEXT NOT NULL, reason TEXT, to_allow TEXT, context TEXT NOT NULL CHECK(context IN ('thread', 'subagent')), subagent_tool_name TEXT, parent_tool_call_id TEXT, timestamp INTEGER NOT NULL, matched_rule TEXT, rule_source TEXT CHECK(rule_source IN ('user', 'built-in')))`,\n\t);\n\tawait database.execute(\n\t\t`CREATE INDEX IF NOT EXISTS idx_tool_approvals_timestamp ON tool_approvals(timestamp)`,\n\t);\n\tawait database.execute(\n\t\t`CREATE TABLE IF NOT EXISTS compaction_summaries (summary_id TEXT PRIMARY KEY, summary_text TEXT NOT NULL, cut_message_id TEXT NOT NULL, created_at TEXT NOT NULL)`,\n\t);\n}\n\nasync function seedSlowReconnectData(database: RawRivetDB): Promise<{\n\tseeded: boolean;\n\tmessages: number;\n\ttoolCalls: number;\n\tthreadEvents: number;\n}> {\n\tconst existing = await database.execute(\n\t\t`SELECT COUNT(*) AS count FROM messages`,\n\t);\n\tif (Number(existing[0]?.count ?? 0) > 0) {\n\t\tconst [toolCalls] = await database.execute(\n\t\t\t`SELECT COUNT(*) AS count FROM tool_calls`,\n\t\t);\n\t\tconst [threadEvents] = await database.execute(\n\t\t\t`SELECT COUNT(*) AS count FROM thread_events`,\n\t\t);\n\t\treturn {\n\t\t\tseeded: false,\n\t\t\tmessages: Number(existing[0]?.count ?? 0),\n\t\t\ttoolCalls: Number(toolCalls?.count ?? 0),\n\t\t\tthreadEvents: Number(threadEvents?.count ?? 0),\n\t\t};\n\t}\n\n\t// Randomize the seeded data volume so each actor's database differs\n\t// substantially in size. This exercises the reconnect repro across a wide\n\t// range of catch-up payload sizes instead of a single fixed shape.\n\tconst vary = (base: number, min = 1) =>\n\t\tMath.max(min, Math.round(base * (0.25 + Math.random() * 3.75)));\n\tconst messageCount = vary(MESSAGE_COUNT, 2);\n\tconst messageToolRefCount = vary(MESSAGE_TOOL_REF_COUNT, 2);\n\tconst toolCallCount = vary(TOOL_CALL_COUNT);\n\tconst executorToolCount = vary(EXECUTOR_TOOL_COUNT);\n\tconst threadEventCount = vary(THREAD_EVENT_COUNT);\n\tconst assistantSpan = Math.max(1, Math.floor(messageCount / 2));\n\n\tconst now = new Date(\"2026-05-16T03:58:18.661Z\").getTime();\n\tconst text = (size: number) => \"x\".repeat(size);\n\tconst isoAt = (index: number) =>\n\t\tnew Date(now + index * 1_000).toISOString();\n\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_meta_kv (key, value, updated_at)`,\n\t\t[\n\t\t\t[\"executor_type\", \"local-client\", isoAt(0)],\n\t\t\t[\n\t\t\t\t\"sandbox_intent\",\n\t\t\t\tJSON.stringify({ spec: null, pendingLaunch: null }),\n\t\t\t\tisoAt(0),\n\t\t\t],\n\t\t\t[\n\t\t\t\t\"executor_status\",\n\t\t\t\tJSON.stringify({ available: true, message: \"ready\" }),\n\t\t\t\tisoAt(0),\n\t\t\t],\n\t\t],\n\t);\n\n\tconst messageRows: unknown[][] = [];\n\tfor (let index = 1; index <= messageCount; index++) {\n\t\tconst role = index % 2 === 0 ? \"assistant\" : \"user\";\n\n\t\tmessageRows.push([\n\t\t\tmessageId(index),\n\t\t\trole,\n\t\t\ttext(MESSAGE_CONTENT_BYTES),\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tisoAt(index),\n\t\t\t0,\n\t\t\tnull,\n\t\t\tnull,\n\t\t\tnull,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO messages (message_id, role, content, meta, user_state, created_at, cancelled, read_at, parent_tool_use_id, tool_result_for_message_id)`,\n\t\tmessageRows,\n\t\t20,\n\t);\n\n\tconst messageToolRefRows: unknown[][] = [];\n\tfor (let index = 0; index < messageToolRefCount / 2; index++) {\n\t\tconst assistantIndex = 2 + (index % assistantSpan) * 2;\n\t\tconst sourceIndex = Math.max(1, assistantIndex - 1);\n\t\tconst resultIndex = Math.min(messageCount, assistantIndex + 1);\n\t\tconst toolUseId = toolUseID(index + 1);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(sourceIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_use\",\n\t\t\t0,\n\t\t]);\n\t\tmessageToolRefRows.push([\n\t\t\tmessageId(resultIndex),\n\t\t\tmessageId(assistantIndex),\n\t\t\ttoolUseId,\n\t\t\t\"tool_result\",\n\t\t\t0,\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_tool_refs (source_message_id, assistant_message_id, tool_use_id, block_type, cancelled)`,\n\t\tmessageToolRefRows,\n\t\t50,\n\t);\n\n\tconst toolCallRows: unknown[][] = [];\n\tfor (let index = 1; index <= toolCallCount; index++) {\n\t\tconst assistantIndex = 2 + ((index - 1) % assistantSpan) * 2;\n\t\ttoolCallRows.push([\n\t\t\ttoolUseID(index),\n\t\t\t`provider-${index}`,\n\t\t\t`tool_${index % 21}`,\n\t\t\tJSON.stringify({ path: `/tmp/file-${index}` }),\n\t\t\t\"seed-executor\",\n\t\t\tmessageId(assistantIndex),\n\t\t\tisoAt(index),\n\t\t\tnull,\n\t\t\t\"completed\",\n\t\t\tJSON.stringify({\n\t\t\t\tok: true,\n\t\t\t\trun: { status: \"done\", result: text(TOOL_CALL_RESULT_BYTES) },\n\t\t\t}),\n\t\t\tnull,\n\t\t\tisoAt(index + 100),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO tool_calls (id, provider_tool_use_id, tool_name, args, executor_id, message_id, issued_at, expires_at, state, result, progress, completed_at)`,\n\t\ttoolCallRows,\n\t\t20,\n\t);\n\n\tconst executorToolRows: unknown[][] = [];\n\tfor (let index = 1; index <= executorToolCount; index++) {\n\t\tconst schema = JSON.stringify({\n\t\t\tname: `tool_${index}`,\n\t\t\tdescription: text(EXECUTOR_TOOL_SCHEMA_BYTES),\n\t\t\tinput_schema: { type: \"object\", properties: {} },\n\t\t});\n\t\texecutorToolRows.push([\n\t\t\t\"seed-executor\",\n\t\t\t`tool_${index}`,\n\t\t\tschema,\n\t\t\tisoAt(index),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO executor_tools (executor_id, tool_name, schema, updated_at)`,\n\t\texecutorToolRows,\n\t\t42,\n\t);\n\n\tconst threadEventRows: unknown[][] = [];\n\tfor (let index = 1; index <= threadEventCount; index++) {\n\t\tthreadEventRows.push([\n\t\t\tindex,\n\t\t\tindex % 3 === 0 ? \"message_added\" : \"agent_state_changed\",\n\t\t\tJSON.stringify({\n\t\t\t\ttype: \"seed_event\",\n\t\t\t\tbody: text(THREAD_EVENT_PAYLOAD_BYTES),\n\t\t\t}),\n\t\t\tisoAt(index),\n\t\t]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO thread_events (seq, event_type, payload, created_at)`,\n\t\tthreadEventRows,\n\t\t25,\n\t);\n\n\tconst messageAddedRows: unknown[][] = [];\n\tfor (let index = 1; index <= messageCount; index++) {\n\t\tmessageAddedRows.push([messageId(index), index]);\n\t}\n\tawait batchInsert(\n\t\tdatabase,\n\t\t`INSERT INTO message_added_events (message_id, seq)`,\n\t\tmessageAddedRows,\n\t\t50,\n\t);\n\n\tawait database.execute(\n\t\t`INSERT INTO environment_snapshot (id, snapshot, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ cwd: \"/workspace\", body: text(3_620) }),\n\t\tisoAt(0),\n\t);\n\tawait database.execute(\n\t\t`INSERT INTO thread_settings_snapshot (id, settings, updated_at) VALUES (1, ?, ?)`,\n\t\tJSON.stringify({ maxTokens: 20_000, body: text(55) }),\n\t\tisoAt(0),\n\t);\n\treturn {\n\t\tseeded: true,\n\t\tmessages: messageCount,\n\t\ttoolCalls: toolCallCount,\n\t\tthreadEvents: threadEventCount,\n\t};\n}\n\n// The engine rejects a remote SQLite statement whose bind params exceed 128\n// KiB. Keep each chunk under a budget below that so wide rows (large message\n// content) never trip the limit, regardless of the row-count batch size.\nconst MAX_BATCH_BIND_BYTES = 120 * 1024;\n\nfunction bindParamBytes(value: unknown): number {\n\tif (value === null || value === undefined) {\n\t\treturn 0;\n\t}\n\tif (typeof value === \"string\") {\n\t\treturn Buffer.byteLength(value, \"utf8\");\n\t}\n\tif (typeof value === \"number\") {\n\t\treturn 8;\n\t}\n\tif (value instanceof Uint8Array) {\n\t\treturn value.byteLength;\n\t}\n\treturn Buffer.byteLength(String(value), \"utf8\");\n}\n\nasync function batchInsert(\n\tdatabase: RawRivetDB,\n\tinsertPrefix: string,\n\trows: unknown[][],\n\tbatchSize = 100,\n): Promise {\n\tif (rows.length === 0) {\n\t\treturn;\n\t}\n\tconst columnCount = rows[0]?.length ?? 0;\n\tif (columnCount === 0) {\n\t\treturn;\n\t}\n\tconst rowPlaceholder = `(${\"?,\".repeat(columnCount).slice(0, -1)})`;\n\tconst rowBytes = rows.map((row) =>\n\t\trow.reduce((sum: number, value) => sum + bindParamBytes(value), 0),\n\t);\n\n\tlet index = 0;\n\twhile (index < rows.length) {\n\t\tconst chunk: unknown[][] = [];\n\t\tlet chunkBytes = 0;\n\t\twhile (\n\t\t\tindex < rows.length &&\n\t\t\tchunk.length < batchSize &&\n\t\t\t// Always take at least one row so an oversized single row still runs\n\t\t\t// (and surfaces the engine error) rather than looping forever.\n\t\t\t(chunk.length === 0 ||\n\t\t\t\tchunkBytes + rowBytes[index]! <= MAX_BATCH_BIND_BYTES)\n\t\t) {\n\t\t\tchunk.push(rows[index]!);\n\t\t\tchunkBytes += rowBytes[index]!;\n\t\t\tindex++;\n\t\t}\n\t\tconst values = chunk.map(() => rowPlaceholder).join(\",\");\n\t\tconst bindings = chunk.flat();\n\t\tawait database.execute(`${insertPrefix} VALUES ${values}`, ...bindings);\n\t}\n}\n\nfunction messageId(index: number): string {\n\treturn `M-${String(index).padStart(22, \"0\")}`;\n}\n\nfunction toolUseID(index: number): string {\n\treturn `toolu_${String(index).padStart(22, \"0\")}`;\n}\n\nexport const registry = setup({\n\tuse: { slowReconnectActor },\n\tmaxIncomingMessageSize: 5 * 1024 * 1024,\n\tmaxOutgoingMessageSize: 5 * 1024 * 1024,\n});\n\n// NOTE: do not call registry.start() at module top-level here, even guarded\n// by `import.meta.main`. When tsup bundles this file into the kitchen-sink\n// server entry, `import.meta.main` evaluates to true for the bundle as a\n// whole, so this would spawn a second runner that only knows about\n// `slowReconnectActor` and rejects every other actor on the shared serverful\n// pool with `not_registered`. If you want to run this actor standalone, do\n// it from a separate entrypoint file.\n","import { randomBytes } from \"node:crypto\";\nimport { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_TARGET_BYTES = 50 * 1024 * 1024;\nconst DEFAULT_ROW_BYTES = 16 * 1024;\nconst DEFAULT_BATCH_ROWS = 8;\nconst DEFAULT_TRANSACTION_BYTES = 64 * 1024;\nconst READ_BATCH_ROWS = 64;\nconst REVERSE_PROBE_ROWS = 32 * 1024;\nconst PAYLOAD_TABLE = \"cold_start_payload\";\nconst REVERSE_PROBE_TABLE = \"cold_start_reverse_probe\";\n\ninterface WriteInput {\n\ttargetBytes?: number;\n\trowBytes?: number;\n\tbatchRows?: number;\n\ttransactionBytes?: number;\n}\n\ninterface PayloadRow {\n\tmin_id?: number | null;\n\tmax_id?: number | null;\n\trows: number;\n\tbytes: number;\n\texpected_bytes: number;\n}\n\ninterface PayloadValueRow {\n\tbytes: number;\n\texpected_bytes: number;\n}\n\nfunction positiveInteger(\n\tvalue: number | undefined,\n\tfallback: number,\n\tname: string,\n) {\n\tconst resolved = value ?? fallback;\n\tif (!Number.isInteger(resolved) || resolved < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\treturn resolved;\n}\n\nfunction randomAsciiString(bytes: number): string {\n\treturn randomBytes(Math.ceil(bytes / 2))\n\t\t.toString(\"hex\")\n\t\t.slice(0, bytes);\n}\n\nasync function readPayloads(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tdirection: \"forward\" | \"backward\" = \"forward\",\n) {\n\tconst t0 = performance.now();\n\tconst [bounds] = (await database.execute(\n\t\t`\n\t\t\tSELECT\n\t\t\t\tMIN(id) AS min_id,\n\t\t\t\tMAX(id) AS max_id,\n\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t0 AS bytes,\n\t\t\t\t0 AS expected_bytes\n\t\t\tFROM ${PAYLOAD_TABLE}\n\t\t`,\n\t)) as PayloadRow[];\n\n\tif (!bounds) throw new Error(\"read query returned no rows\");\n\n\tlet rows = 0;\n\tlet bytes = 0;\n\tlet expectedBytes = 0;\n\tlet chunks = 0;\n\tconst minId = bounds.min_id ?? 0;\n\tconst maxId = bounds.max_id ?? 0;\n\n\tif (direction === \"backward\") {\n\t\tconst [probeBounds] = (await database.execute(\n\t\t\t`\n\t\t\t\tSELECT\n\t\t\t\t\tMIN(id) AS min_id,\n\t\t\t\t\tMAX(id) AS max_id,\n\t\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t\t0 AS bytes,\n\t\t\t\t\t0 AS expected_bytes\n\t\t\t\tFROM ${REVERSE_PROBE_TABLE}\n\t\t\t`,\n\t\t)) as PayloadRow[];\n\t\tif (!probeBounds)\n\t\t\tthrow new Error(\"reverse probe query returned no rows\");\n\t\tconst probeMinId = probeBounds.min_id ?? 0;\n\t\tconst probeMaxId = probeBounds.max_id ?? 0;\n\n\t\tfor (\n\t\t\tlet upperId = probeMaxId;\n\t\t\tupperId >= probeMinId && upperId > 0;\n\t\t\tupperId -= READ_BATCH_ROWS\n\t\t) {\n\t\t\tconst lowerId = Math.max(probeMinId, upperId - READ_BATCH_ROWS + 1);\n\t\t\tconst chunkRows = (await database.execute(\n\t\t\t\t`\n\t\t\t\t\tSELECT\n\t\t\t\t\t\tmarker AS bytes,\n\t\t\t\t\t\tmarker AS expected_bytes\n\t\t\t\t\tFROM ${REVERSE_PROBE_TABLE}\n\t\t\t\t\tWHERE id BETWEEN ? AND ?\n\t\t\t\t\tORDER BY id DESC\n\t\t\t\t`,\n\t\t\t\tlowerId,\n\t\t\t\tupperId,\n\t\t\t)) as PayloadValueRow[];\n\n\t\t\tfor (const row of chunkRows) {\n\t\t\t\trows += 1;\n\t\t\t\tbytes += row.bytes;\n\t\t\t\texpectedBytes += row.expected_bytes;\n\t\t\t}\n\t\t\tchunks += 1;\n\t\t}\n\n\t\treturn {\n\t\t\tms: performance.now() - t0,\n\t\t\tops: rows,\n\t\t\trows,\n\t\t\tbytes,\n\t\t\texpectedBytes,\n\t\t\tchunks,\n\t\t\treadBatchRows: READ_BATCH_ROWS,\n\t\t\tdirection,\n\t\t};\n\t}\n\n\tfor (let lowerId = minId; lowerId <= maxId; lowerId += READ_BATCH_ROWS) {\n\t\tconst upperId = lowerId + READ_BATCH_ROWS - 1;\n\t\tconst [chunk] = (await database.execute(\n\t\t\t`\n\t\t\t\tSELECT\n\t\t\t\t\tCOUNT(*) AS rows,\n\t\t\t\t\tCOALESCE(SUM(length(payload)), 0) AS bytes,\n\t\t\t\t\tCOALESCE(SUM(payload_bytes), 0) AS expected_bytes\n\t\t\t\tFROM ${PAYLOAD_TABLE}\n\t\t\t\tWHERE id BETWEEN ? AND ?\n\t\t\t`,\n\t\t\tlowerId,\n\t\t\tupperId,\n\t\t)) as PayloadRow[];\n\t\tif (!chunk) throw new Error(\"chunked read query returned no rows\");\n\n\t\trows += chunk.rows;\n\t\tbytes += chunk.bytes;\n\t\texpectedBytes += chunk.expected_bytes;\n\t\tchunks += 1;\n\t}\n\n\treturn {\n\t\tms: performance.now() - t0,\n\t\tops: rows,\n\t\trows,\n\t\tbytes,\n\t\texpectedBytes,\n\t\tchunks,\n\t\treadBatchRows: READ_BATCH_ROWS,\n\t};\n}\n\nexport const sqliteColdStartBench = actor({\n\toptions: {\n\t\tactionTimeout: 600_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cold_start_payload (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tpayload TEXT NOT NULL,\n\t\t\t\t\tpayload_bytes INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cold_start_reverse_probe (\n\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\tmarker INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(`DELETE FROM ${PAYLOAD_TABLE}`);\n\t\t\tawait c.db.execute(`DELETE FROM ${REVERSE_PROBE_TABLE}`);\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\twriteRandomStrings: async (c, input: WriteInput = {}) => {\n\t\t\tconst targetBytes = positiveInteger(\n\t\t\t\tinput.targetBytes,\n\t\t\t\tDEFAULT_TARGET_BYTES,\n\t\t\t\t\"targetBytes\",\n\t\t\t);\n\t\t\tconst rowBytes = positiveInteger(\n\t\t\t\tinput.rowBytes,\n\t\t\t\tDEFAULT_ROW_BYTES,\n\t\t\t\t\"rowBytes\",\n\t\t\t);\n\t\t\tconst batchRows = positiveInteger(\n\t\t\t\tinput.batchRows,\n\t\t\t\tDEFAULT_BATCH_ROWS,\n\t\t\t\t\"batchRows\",\n\t\t\t);\n\t\t\tconst transactionBytes = positiveInteger(\n\t\t\t\tinput.transactionBytes,\n\t\t\t\tDEFAULT_TRANSACTION_BYTES,\n\t\t\t\t\"transactionBytes\",\n\t\t\t);\n\t\t\tconst createdAt = Date.now();\n\t\t\tlet remainingBytes = targetBytes;\n\t\t\tlet rows = 0;\n\t\t\tlet transactions = 0;\n\t\t\tlet randomStringMs = 0;\n\t\t\tlet sqliteInsertMs = 0;\n\t\t\tlet commitMs = 0;\n\t\t\tlet inTransaction = false;\n\n\t\t\tconst wallT0 = performance.now();\n\t\t\ttry {\n\t\t\t\twhile (remainingBytes > 0) {\n\t\t\t\t\tlet transactionRemainingBytes = Math.min(\n\t\t\t\t\t\ttransactionBytes,\n\t\t\t\t\t\tremainingBytes,\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\t\tinTransaction = true;\n\t\t\t\t\ttransactions += 1;\n\n\t\t\t\t\twhile (transactionRemainingBytes > 0) {\n\t\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\t\tconst generateT0 = performance.now();\n\n\t\t\t\t\t\tfor (\n\t\t\t\t\t\t\tlet batchIndex = 0;\n\t\t\t\t\t\t\tbatchIndex < batchRows &&\n\t\t\t\t\t\t\ttransactionRemainingBytes > 0 &&\n\t\t\t\t\t\t\tremainingBytes > 0;\n\t\t\t\t\t\t\tbatchIndex += 1\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst payloadBytes = Math.min(\n\t\t\t\t\t\t\t\trowBytes,\n\t\t\t\t\t\t\t\ttransactionRemainingBytes,\n\t\t\t\t\t\t\t\tremainingBytes,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?)\");\n\t\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\t\trandomAsciiString(payloadBytes),\n\t\t\t\t\t\t\t\tpayloadBytes,\n\t\t\t\t\t\t\t\tcreatedAt + rows,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\ttransactionRemainingBytes -= payloadBytes;\n\t\t\t\t\t\t\tremainingBytes -= payloadBytes;\n\t\t\t\t\t\t\trows += 1;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trandomStringMs += performance.now() - generateT0;\n\t\t\t\t\t\tconst insertT0 = performance.now();\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`INSERT INTO ${PAYLOAD_TABLE} (payload, payload_bytes, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t\t...args,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsqliteInsertMs += performance.now() - insertT0;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst commitT0 = performance.now();\n\t\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\t\tcommitMs += performance.now() - commitT0;\n\t\t\t\t\tinTransaction = false;\n\t\t\t\t}\n\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tinTransaction = true;\n\t\t\t\tfor (\n\t\t\t\t\tlet lowerId = 1;\n\t\t\t\t\tlowerId <= REVERSE_PROBE_ROWS;\n\t\t\t\t\tlowerId += 256\n\t\t\t\t) {\n\t\t\t\t\tconst upperId = Math.min(REVERSE_PROBE_ROWS, lowerId + 255);\n\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\tfor (let id = lowerId; id <= upperId; id += 1) {\n\t\t\t\t\t\tplaceholders.push(\"(?, ?)\");\n\t\t\t\t\t\targs.push(id, 1);\n\t\t\t\t\t}\n\t\t\t\t\tconst insertT0 = performance.now();\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t`INSERT INTO ${REVERSE_PROBE_TABLE} (id, marker) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t...args,\n\t\t\t\t\t);\n\t\t\t\t\tsqliteInsertMs += performance.now() - insertT0;\n\t\t\t\t}\n\t\t\t\tconst reverseCommitT0 = performance.now();\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\tcommitMs += performance.now() - reverseCommitT0;\n\t\t\t\tinTransaction = false;\n\n\t\t\t\treturn {\n\t\t\t\t\tms: sqliteInsertMs + commitMs,\n\t\t\t\t\twriteWallMs: performance.now() - wallT0,\n\t\t\t\t\trandomStringMs,\n\t\t\t\t\tsqliteInsertMs,\n\t\t\t\t\tcommitMs,\n\t\t\t\t\tops: rows,\n\t\t\t\t\trows,\n\t\t\t\t\ttransactions,\n\t\t\t\t\tbytes: targetBytes,\n\t\t\t\t\trowBytes,\n\t\t\t\t\tbatchRows,\n\t\t\t\t\ttransactionBytes,\n\t\t\t\t\treverseProbeRows: REVERSE_PROBE_ROWS,\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (inTransaction) {\n\t\t\t\t\tawait c.db.execute(\"ROLLBACK\");\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\treadAll: async (c) => {\n\t\t\treturn readPayloads(c.db);\n\t\t},\n\n\t\treadAllReverse: async (c) => {\n\t\t\treturn readPayloads(c.db, \"backward\");\n\t\t},\n\n\t\twakeSqlite: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst [row] = (await c.db.execute(\n\t\t\t\t`SELECT COUNT(*) AS rows FROM ${PAYLOAD_TABLE} WHERE id = -1`,\n\t\t\t)) as Array<{ rows: number }>;\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\trows: row?.rows ?? 0,\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport type { SqliteNativeMetrics } from \"rivetkit/db\";\nimport { db } from \"rivetkit/db\";\n\ninterface RunCycleInput {\n\tseed: string;\n\tcycle: number;\n\tinsertRows?: number;\n\trowBytes?: number;\n\tdeleteRows?: number;\n\tretainRows?: number;\n\tscanRows?: number;\n}\n\ninterface CountRow {\n\tcount: number;\n}\n\ninterface StorageRow {\n\tpage_count: number;\n\tfreelist_count: number;\n\tpage_size: number;\n\tvfs: SqliteNativeMetrics | null;\n}\n\nconst DEFAULT_INSERT_ROWS = 128;\nconst DEFAULT_ROW_BYTES = 16 * 1024;\n// const DEFAULT_DELETE_ROWS = 64;\n// const DEFAULT_RETAIN_ROWS = 1024;\nconst DEFAULT_SCAN_ROWS = 512;\nconst INSERT_BATCH_ROWS = 32;\n\nfunction finiteInt(value: number | undefined, fallback: number): number {\n\tif (value === undefined) return fallback;\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`expected a non-negative finite number, got ${value}`);\n\t}\n\treturn Math.floor(value);\n}\n\nfunction copyNativeMetrics(\n\tmetrics: SqliteNativeMetrics | null | undefined,\n): SqliteNativeMetrics | null {\n\tif (!metrics) return null;\n\tconst raw = metrics as unknown as Record;\n\tconst numberField = (camel: string, snake: string) =>\n\t\tNumber(raw[camel] ?? raw[snake] ?? 0);\n\treturn {\n\t\trequestBuildNs: numberField(\"requestBuildNs\", \"request_build_ns\"),\n\t\tserializeNs: numberField(\"serializeNs\", \"serialize_ns\"),\n\t\ttransportNs: numberField(\"transportNs\", \"transport_ns\"),\n\t\tstateUpdateNs: numberField(\"stateUpdateNs\", \"state_update_ns\"),\n\t\ttotalNs: numberField(\"totalNs\", \"total_ns\"),\n\t\tcommitCount: numberField(\"commitCount\", \"commit_count\"),\n\t\tpageCacheEntries: numberField(\"pageCacheEntries\", \"page_cache_entries\"),\n\t\tpageCacheWeightedSize: numberField(\n\t\t\t\"pageCacheWeightedSize\",\n\t\t\t\"page_cache_weighted_size\",\n\t\t),\n\t\tpageCacheCapacityPages: numberField(\n\t\t\t\"pageCacheCapacityPages\",\n\t\t\t\"page_cache_capacity_pages\",\n\t\t),\n\t\twriteBufferDirtyPages: numberField(\n\t\t\t\"writeBufferDirtyPages\",\n\t\t\t\"write_buffer_dirty_pages\",\n\t\t),\n\t\tdbSizePages: numberField(\"dbSizePages\", \"db_size_pages\"),\n\t};\n}\n\nasync function queryOne(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tsql: string,\n\t...args: unknown[]\n): Promise {\n\tconst rows = await database.execute(sql, ...args);\n\tif (!rows[0]) throw new Error(`query returned no rows: ${sql}`);\n\treturn rows[0] as T;\n}\n\nasync function storageStats(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n\tnativeMetrics?: () =>\n\t\t| SqliteNativeMetrics\n\t\t| Promise\n\t\t| null;\n}): Promise {\n\tconst [pageCount, freelistCount, pageSize] = await Promise.all([\n\t\tqueryOne<{ page_count: number }>(database, \"PRAGMA page_count\"),\n\t\tqueryOne<{ freelist_count: number }>(database, \"PRAGMA freelist_count\"),\n\t\tqueryOne<{ page_size: number }>(database, \"PRAGMA page_size\"),\n\t]);\n\n\tconst nativeMetrics = await database.nativeMetrics?.();\n\tconst copiedMetrics = copyNativeMetrics(nativeMetrics);\n\n\treturn {\n\t\tpage_count: pageCount.page_count,\n\t\tfreelist_count: freelistCount.freelist_count,\n\t\tpage_size: pageSize.page_size,\n\t\tvfs: copiedMetrics,\n\t};\n}\n\nexport const sqliteMemoryPressure = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tstate: {\n\t\tsleepCount: 0,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS pressure_rows (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tseed TEXT NOT NULL,\n\t\t\t\t\tcycle INTEGER NOT NULL,\n\t\t\t\t\tbucket INTEGER NOT NULL,\n\t\t\t\t\tpayload BLOB NOT NULL,\n\t\t\t\t\ttouched_count INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_pressure_rows_seed_cycle ON pressure_rows(seed, cycle)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_pressure_rows_bucket ON pressure_rows(bucket)\",\n\t\t\t);\n\t\t\tawait database.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS pressure_cycles (\n\t\t\t\t\tcycle INTEGER PRIMARY KEY,\n\t\t\t\t\tseed TEXT NOT NULL,\n\t\t\t\t\tinserted_rows INTEGER NOT NULL,\n\t\t\t\t\tdeleted_rows INTEGER NOT NULL,\n\t\t\t\t\tactive_rows INTEGER NOT NULL,\n\t\t\t\t\tactive_bytes INTEGER NOT NULL,\n\t\t\t\t\tduration_ms REAL NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\t\t},\n\t}),\n\tonSleep: (c) => {\n\t\tc.state.sleepCount += 1;\n\t\tconsole.log(\n\t\t\tJSON.stringify({\n\t\t\t\tkind: \"sqlite_memory_pressure_on_sleep\",\n\t\t\t\tactorId: c.actorId,\n\t\t\t\tsleepCount: c.state.sleepCount,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t}),\n\t\t);\n\t},\n\tactions: {\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"DELETE FROM pressure_cycles\");\n\t\t\tawait c.db.execute(\"DELETE FROM pressure_rows\");\n\t\t\tawait c.db.execute(\"VACUUM\");\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tstorage: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\treleaseStorage: async (c) => {\n\t\t\tconst before = await storageStats(c.db);\n\t\t\t// Keep the remote DB large for the sleep reclamation soak.\n\t\t\t// await c.db.execute(\"DELETE FROM pressure_cycles\");\n\t\t\t// await c.db.execute(\"DELETE FROM pressure_rows\");\n\t\t\t// await c.db.execute(\"VACUUM\");\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tbefore,\n\t\t\t\tafter: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\tstats: async (c) => {\n\t\t\tconst rowStats = await queryOne<{\n\t\t\t\tactive_rows: number;\n\t\t\t\tactive_bytes: number | null;\n\t\t\t\ttouched_sum: number | null;\n\t\t\t}>(\n\t\t\t\tc.db,\n\t\t\t\t\"SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes, COALESCE(SUM(touched_count), 0) AS touched_sum FROM pressure_rows\",\n\t\t\t);\n\t\t\tconst cycles = await queryOne(\n\t\t\t\tc.db,\n\t\t\t\t\"SELECT COUNT(*) AS count FROM pressure_cycles\",\n\t\t\t);\n\t\t\tconst integrity = await queryOne<{ integrity_check: string }>(\n\t\t\t\tc.db,\n\t\t\t\t\"PRAGMA integrity_check\",\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\ttouchedCount: rowStats.touched_sum ?? 0,\n\t\t\t\tcycles: cycles.count,\n\t\t\t\tintegrityCheck: integrity.integrity_check,\n\t\t\t\tstorage: await storageStats(c.db),\n\t\t\t};\n\t\t},\n\n\t\trunCycle: async (c, input: RunCycleInput) => {\n\t\t\tconst startedAt = performance.now();\n\t\t\tconst insertRows = finiteInt(input.insertRows, DEFAULT_INSERT_ROWS);\n\t\t\tconst rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES);\n\t\t\t// const deleteRows = finiteInt(input.deleteRows, DEFAULT_DELETE_ROWS);\n\t\t\t// const retainRows = Math.max(\n\t\t\t// \t1,\n\t\t\t// \tfiniteInt(input.retainRows, DEFAULT_RETAIN_ROWS),\n\t\t\t// );\n\t\t\tconst scanRows = Math.max(\n\t\t\t\t1,\n\t\t\t\tfiniteInt(input.scanRows, DEFAULT_SCAN_ROWS),\n\t\t\t);\n\t\t\tconst now = Date.now();\n\t\t\tlet insertedRows = 0;\n\t\t\tconst logStage = (\n\t\t\t\tstage: string,\n\t\t\t\tphase: \"start\" | \"end\" | \"error\",\n\t\t\t\tfields: Record = {},\n\t\t\t) => {\n\t\t\t\tconsole.log(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\tkind: \"sqlite_memory_pressure_run_cycle_stage\",\n\t\t\t\t\t\tactorId: c.actorId,\n\t\t\t\t\t\tseed: input.seed,\n\t\t\t\t\t\tcycle: input.cycle,\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tphase,\n\t\t\t\t\t\telapsedMs: performance.now() - startedAt,\n\t\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\t\t...fields,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst executeTimed = async (\n\t\t\t\tstage: string,\n\t\t\t\tsql: string,\n\t\t\t\t...args: unknown[]\n\t\t\t) => {\n\t\t\t\tconst stageStartedAt = performance.now();\n\t\t\t\tlogStage(stage, \"start\", { argCount: args.length });\n\t\t\t\ttry {\n\t\t\t\t\tconst rows = await c.db.execute(sql, ...args);\n\t\t\t\t\tlogStage(stage, \"end\", {\n\t\t\t\t\t\tdurationMs: performance.now() - stageStartedAt,\n\t\t\t\t\t\trowCount: rows.length,\n\t\t\t\t\t});\n\t\t\t\t\treturn rows;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogStage(stage, \"error\", {\n\t\t\t\t\t\tdurationMs: performance.now() - stageStartedAt,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t};\n\t\t\tlogStage(\"run_cycle\", \"start\", {\n\t\t\t\tinsertRows,\n\t\t\t\trowBytes,\n\t\t\t\tscanRows,\n\t\t\t});\n\n\t\t\tawait executeTimed(\"begin\", \"BEGIN\");\n\t\t\ttry {\n\t\t\t\twhile (insertedRows < insertRows) {\n\t\t\t\t\tconst batchRows = Math.min(\n\t\t\t\t\t\tINSERT_BATCH_ROWS,\n\t\t\t\t\t\tinsertRows - insertedRows,\n\t\t\t\t\t);\n\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\tconst args: unknown[] = [];\n\n\t\t\t\t\tfor (let i = 0; i < batchRows; i += 1) {\n\t\t\t\t\t\tconst rowIndex = insertedRows + i;\n\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?, randomblob(?), 0, ?)\");\n\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\tinput.seed,\n\t\t\t\t\t\t\tinput.cycle,\n\t\t\t\t\t\t\t(input.cycle + rowIndex) % 32,\n\t\t\t\t\t\t\trowBytes,\n\t\t\t\t\t\t\tnow + rowIndex,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait executeTimed(\n\t\t\t\t\t\t\"insert_batch\",\n\t\t\t\t\t\t`INSERT INTO pressure_rows (seed, cycle, bucket, payload, touched_count, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t...args,\n\t\t\t\t\t);\n\t\t\t\t\tinsertedRows += batchRows;\n\t\t\t\t\tlogStage(\"insert_batch_progress\", \"end\", {\n\t\t\t\t\t\tinsertedRows,\n\t\t\t\t\t\tbatchRows,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait executeTimed(\"commit\", \"COMMIT\");\n\t\t\t} catch (err) {\n\t\t\t\tawait executeTimed(\"rollback\", \"ROLLBACK\").catch(\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t\tthrow err;\n\t\t\t}\n\n\t\t\tconst scan = await executeTimed(\n\t\t\t\t\"scan_recent\",\n\t\t\t\t\"SELECT id, length(payload) AS payload_bytes FROM pressure_rows ORDER BY id DESC LIMIT ?\",\n\t\t\t\tscanRows,\n\t\t\t);\n\t\t\tconst bucketAgg = await executeTimed(\n\t\t\t\t\"bucket_agg\",\n\t\t\t\t\"SELECT bucket, COUNT(*) AS rows, SUM(length(payload)) AS bytes FROM pressure_rows WHERE bucket BETWEEN ? AND ? GROUP BY bucket ORDER BY bucket\",\n\t\t\t\tinput.cycle % 16,\n\t\t\t\t(input.cycle % 16) + 15,\n\t\t\t);\n\t\t\tawait executeTimed(\n\t\t\t\t\"touch_recent\",\n\t\t\t\t\"UPDATE pressure_rows SET touched_count = touched_count + 1 WHERE id IN (SELECT id FROM pressure_rows ORDER BY id DESC LIMIT ?)\",\n\t\t\t\tMath.min(scanRows, insertRows),\n\t\t\t);\n\n\t\t\tconst deletedRows = 0;\n\t\t\t// const beforeDelete = await queryOne(\n\t\t\t// \tc.db,\n\t\t\t// \t\"SELECT COUNT(*) AS count FROM pressure_rows\",\n\t\t\t// );\n\t\t\t// const overRetainRows = Math.max(0, beforeDelete.count - retainRows);\n\t\t\t// const deleteLimit = Math.max(deleteRows, overRetainRows);\n\t\t\t// if (deleteLimit > 0) {\n\t\t\t// \tawait c.db.execute(\n\t\t\t// \t\t\"DELETE FROM pressure_rows WHERE id IN (SELECT id FROM pressure_rows ORDER BY id ASC LIMIT ?)\",\n\t\t\t// \t\tdeleteLimit,\n\t\t\t// \t);\n\t\t\t// \tconst afterDelete = await queryOne(\n\t\t\t// \t\tc.db,\n\t\t\t// \t\t\"SELECT changes() AS count\",\n\t\t\t// \t);\n\t\t\t// \tdeletedRows = afterDelete.count;\n\t\t\t// }\n\n\t\t\tconst rowStatsRows = await executeTimed(\n\t\t\t\t\"row_stats\",\n\t\t\t\t\"SELECT COUNT(*) AS active_rows, COALESCE(SUM(length(payload)), 0) AS active_bytes FROM pressure_rows\",\n\t\t\t);\n\t\t\tconst rowStats = rowStatsRows[0] as\n\t\t\t\t| {\n\t\t\t\t\t\tactive_rows: number;\n\t\t\t\t\t\tactive_bytes: number | null;\n\t\t\t\t }\n\t\t\t\t| undefined;\n\t\t\tif (!rowStats) throw new Error(\"query returned no rows: row_stats\");\n\t\t\tconst integrityRows = await executeTimed(\n\t\t\t\t\"integrity_check\",\n\t\t\t\t\"PRAGMA integrity_check\",\n\t\t\t);\n\t\t\tconst integrity = integrityRows[0] as\n\t\t\t\t| { integrity_check: string }\n\t\t\t\t| undefined;\n\t\t\tif (!integrity) {\n\t\t\t\tthrow new Error(\"query returned no rows: integrity_check\");\n\t\t\t}\n\t\t\tconst durationMs = performance.now() - startedAt;\n\n\t\t\tawait executeTimed(\n\t\t\t\t\"record_cycle\",\n\t\t\t\t\"INSERT OR REPLACE INTO pressure_cycles (cycle, seed, inserted_rows, deleted_rows, active_rows, active_bytes, duration_ms, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n\t\t\t\tinput.cycle,\n\t\t\t\tinput.seed,\n\t\t\t\tinsertedRows,\n\t\t\t\tdeletedRows,\n\t\t\t\trowStats.active_rows,\n\t\t\t\trowStats.active_bytes ?? 0,\n\t\t\t\tdurationMs,\n\t\t\t\tnow,\n\t\t\t);\n\n\t\t\tconst storageStartedAt = performance.now();\n\t\t\tlogStage(\"storage_stats\", \"start\");\n\t\t\tconst storage = await storageStats(c.db);\n\t\t\tlogStage(\"storage_stats\", \"end\", {\n\t\t\t\tdurationMs: performance.now() - storageStartedAt,\n\t\t\t\tpageCount: storage.page_count,\n\t\t\t\tdbSizePages: storage.vfs?.dbSizePages ?? null,\n\t\t\t\tpageCacheEntries: storage.vfs?.pageCacheEntries ?? null,\n\t\t\t});\n\t\t\tlogStage(\"run_cycle\", \"end\", {\n\t\t\t\tdurationMs,\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\tpageCount: storage.page_count,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tseed: input.seed,\n\t\t\t\tcycle: input.cycle,\n\t\t\t\tinsertedRows,\n\t\t\t\tdeletedRows,\n\t\t\t\tactiveRows: rowStats.active_rows,\n\t\t\t\tactiveBytes: rowStats.active_bytes ?? 0,\n\t\t\t\tscannedRows: scan.length,\n\t\t\t\tbucketsRead: bucketAgg.length,\n\t\t\t\tintegrityCheck: integrity.integrity_check,\n\t\t\t\tstorage,\n\t\t\t\tdurationMs,\n\t\t\t};\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst DEFAULT_ROW_BYTES = 2 * 1024;\nconst ORDER_BATCH_ROWS = 50;\nconst DOC_BATCH_ROWS = 75;\nconst LEDGER_BATCH_ROWS = 100;\nconst POINT_LOOKUP_OPS = 1_000;\nconst RANGE_CHUNK_ROWS = 512;\nconst SETUP_TRANSACTION_ROWS = 128;\nconst FEED_PAGE_ROWS = 100;\nconst CHAT_LOG_CHUNK_BYTES = 4 * 1024;\nconst CHAT_LOG_INSERT_BATCH_SIZE = 50;\nconst CHAT_THREAD_ID = \"rw-chat-main\";\nconst SQL_RUSH_MSGS_COUNT = 2_500;\nconst SQL_RUSH_TOOL_REFS_COUNT = 240;\nconst SQL_RUSH_EVENTS_COUNT = 700;\nconst SQL_RUSH_KV_COUNT = 40;\nconst SQL_RUSH_TOOLS_COUNT = 41;\nconst SQL_RUSH_META_COUNT = 12;\n\n// Keep this list in sync with the runner's workload catalog. The runner owns\n// the per-workload rationale and expected cache/VFS behavior so benchmark\n// result artifacts can preserve that intent over time.\nconst WORKLOADS = [\n\t\"small-rowid-point\",\n\t\"small-schema-read\",\n\t\"small-range-scan\",\n\t\"rowid-range-forward\",\n\t\"rowid-range-backward\",\n\t\"secondary-index-covering-range\",\n\t\"secondary-index-scattered-table\",\n\t\"aggregate-status\",\n\t\"aggregate-time-bucket\",\n\t\"aggregate-tenant-time-range\",\n\t\"parallel-read-aggregates\",\n\t\"parallel-read-write-transition\",\n\t\"feed-order-by-limit\",\n\t\"feed-pagination-adjacent\",\n\t\"join-order-items\",\n\t\"random-point-lookups\",\n\t\"hot-index-cold-table\",\n\t\"ledger-without-rowid-range\",\n\t\"chat-log-select-limit\",\n\t\"chat-log-select-indexed\",\n\t\"chat-log-count\",\n\t\"chat-log-sum\",\n\t\"chat-tool-read-fanout\",\n\t\"chat-tool-script\",\n\t\"write-batch-after-wake\",\n\t\"update-hot-partition\",\n\t\"delete-churn-range-read\",\n\t\"migration-create-indexes-large\",\n\t\"migration-create-indexes-skewed-large\",\n\t\"migration-table-rebuild-large\",\n\t\"migration-add-column-large\",\n\t\"migration-ddl-small\",\n] as const;\n\ntype WorkloadName = (typeof WORKLOADS)[number];\n\ninterface SetupInput {\n\tworkload: WorkloadName;\n\ttargetBytes?: number;\n\trowBytes?: number;\n}\n\ninterface RunInput {\n\tworkload: WorkloadName;\n\ttargetBytes?: number;\n}\n\ninterface CountRow {\n\trows: number;\n}\n\ninterface PageCountRow {\n\tpage_count: number;\n}\n\ninterface CacheSizeRow {\n\tcache_size: number;\n}\n\ninterface PageSizeRow {\n\tpage_size: number;\n}\n\ninterface BytesRow {\n\tbytes: number;\n\trows?: number;\n}\n\ninterface AggregateRow {\n\trows: number;\n\ttotal: number;\n}\n\nfunction positiveInteger(\n\tvalue: number | undefined,\n\tfallback: number,\n\tname: string,\n) {\n\tconst resolved = value ?? fallback;\n\tif (!Number.isInteger(resolved) || resolved < 1) {\n\t\tthrow new Error(`${name} must be a positive integer`);\n\t}\n\treturn resolved;\n}\n\nfunction assertWorkload(workload: string): asserts workload is WorkloadName {\n\tif (!(WORKLOADS as readonly string[]).includes(workload)) {\n\t\tthrow new Error(`unknown SQLite benchmark workload: ${workload}`);\n\t}\n}\n\nfunction pseudoRandom(value: number) {\n\treturn Math.imul(value ^ 0x9e3779b9, 0x85ebca6b) >>> 0;\n}\n\nfunction paddedHex(value: number) {\n\treturn pseudoRandom(value).toString(16).padStart(8, \"0\");\n}\n\nfunction payload(prefix: string, bytes: number) {\n\treturn prefix + \"x\".repeat(Math.max(0, bytes - prefix.length));\n}\n\nfunction typedRows(rows: unknown[]): T[] {\n\treturn rows as T[];\n}\n\nasync function queryPageCount(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tconst [row] = typedRows(\n\t\tawait database.execute(\"PRAGMA page_count\"),\n\t);\n\treturn row?.page_count ?? 0;\n}\n\nasync function resetCommerce(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_order_items\");\n\tawait database.execute(\"DELETE FROM rw_orders\");\n\tawait database.execute(\"DELETE FROM rw_customers\");\n\tawait database.execute(\"DELETE FROM rw_events\");\n}\n\nasync function resetDocs(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_docs\");\n}\n\nasync function resetLedger(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_ledger\");\n}\n\nasync function resetChatLog(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM rw_chat_log\");\n}\n\nasync function resetSqlRush(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\"DELETE FROM tool_refs\");\n\tawait database.execute(\"DELETE FROM msgs\");\n\tawait database.execute(\"DELETE FROM events\");\n\tawait database.execute(\"DELETE FROM kv\");\n\tawait database.execute(\"DELETE FROM tools\");\n\tawait database.execute(\"DELETE FROM meta\");\n}\n\nasync function resetMigration(database: {\n\texecute: (sql: string, ...args: unknown[]) => Promise;\n}) {\n\tawait database.execute(\n\t\t\"DROP INDEX IF EXISTS idx_rw_migration_source_account\",\n\t);\n\tawait database.execute(\n\t\t\"DROP INDEX IF EXISTS idx_rw_migration_source_created\",\n\t);\n\tawait database.execute(\n\t\t\"DROP INDEX IF EXISTS idx_rw_migration_source_status_total\",\n\t);\n\tawait database.execute(\n\t\t\"DROP INDEX IF EXISTS idx_rw_migration_source_skew_account\",\n\t);\n\tawait database.execute(\n\t\t\"DROP INDEX IF EXISTS idx_rw_migration_source_skew_status\",\n\t);\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_source_rebuilt\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_source\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_audit\");\n\tawait database.execute(\"DROP TABLE IF EXISTS rw_migration_empty\");\n}\n\nasync function withTransaction(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tfn: () => Promise,\n) {\n\tlet inTransaction = false;\n\tawait database.execute(\"BEGIN\");\n\tinTransaction = true;\n\ttry {\n\t\tawait fn();\n\t\tawait database.execute(\"COMMIT\");\n\t\tinTransaction = false;\n\t} catch (err) {\n\t\tif (inTransaction) {\n\t\t\tawait database.execute(\"ROLLBACK\").catch(() => undefined);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function seedCommerce(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetCommerce(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst customerCount = Math.max(32, Math.ceil(rows / 16));\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\tfor (\n\t\t\tlet offset = 0;\n\t\t\toffset < customerCount;\n\t\t\toffset += ORDER_BATCH_ROWS\n\t\t) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\t\t\tconst batchEnd = Math.min(customerCount, offset + ORDER_BATCH_ROWS);\n\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\ti + 1,\n\t\t\t\t\t`acct-${i % 64}`,\n\t\t\t\t\t`user-${paddedHex(i)}@example.test`,\n\t\t\t\t\t[\"free\", \"pro\", \"team\", \"enterprise\"][i % 4],\n\t\t\t\t\t[\"iad\", \"sfo\", \"fra\", \"sin\"][i % 4],\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO rw_customers (id, account_id, email, plan, region) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\t});\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (\n\t\t\t\tlet offset = txStart;\n\t\t\t\toffset < txEnd;\n\t\t\t\toffset += ORDER_BATCH_ROWS\n\t\t\t) {\n\t\t\t\tconst orderPlaceholders: string[] = [];\n\t\t\t\tconst orderArgs: unknown[] = [];\n\t\t\t\tconst itemPlaceholders: string[] = [];\n\t\t\t\tconst itemArgs: unknown[] = [];\n\t\t\t\tconst eventPlaceholders: string[] = [];\n\t\t\t\tconst eventArgs: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS);\n\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst id = i + 1;\n\t\t\t\t\tconst customerId = (pseudoRandom(i) % customerCount) + 1;\n\t\t\t\t\tconst createdAt = 1_700_000_000_000 + i * 1000;\n\t\t\t\t\tconst status = [\"pending\", \"paid\", \"shipped\", \"refunded\"][\n\t\t\t\t\t\ti % 4\n\t\t\t\t\t];\n\t\t\t\t\tconst totalCents = 500 + (pseudoRandom(i + 17) % 25_000);\n\t\t\t\t\tconst note = payload(`order-${id}-${status}:`, rowBytes);\n\n\t\t\t\t\torderPlaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\torderArgs.push(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tcustomerId,\n\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\ttotalCents,\n\t\t\t\t\t\ti % 128,\n\t\t\t\t\t\tnote,\n\t\t\t\t\t);\n\n\t\t\t\t\tfor (let item = 0; item < 2; item += 1) {\n\t\t\t\t\t\titemPlaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\t\titemArgs.push(\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t`sku-${paddedHex(i + item).slice(0, 6)}`,\n\t\t\t\t\t\t\t1 + ((i + item) % 5),\n\t\t\t\t\t\t\t100 + (pseudoRandom(i + item + 31) % 5000),\n\t\t\t\t\t\t\titem,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\teventPlaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\teventArgs.push(\n\t\t\t\t\t\t`acct-${customerId % 64}`,\n\t\t\t\t\t\t[\"click\", \"purchase\", \"refund\", \"shipment\"][i % 4],\n\t\t\t\t\t\tcreatedAt,\n\t\t\t\t\t\t`order:${id}`,\n\t\t\t\t\t\tpayload(`event-${id}:`, Math.min(rowBytes, 512)),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${orderPlaceholders.join(\", \")}`,\n\t\t\t\t\t...orderArgs,\n\t\t\t\t);\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_order_items (order_id, sku, quantity, price_cents, line_no) VALUES ${itemPlaceholders.join(\", \")}`,\n\t\t\t\t\t...itemArgs,\n\t\t\t\t);\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_events (account_id, event_type, created_at, entity_key, properties) VALUES ${eventPlaceholders.join(\", \")}`,\n\t\t\t\t\t...eventArgs,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedDocs(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetDocs(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (\n\t\t\t\tlet offset = txStart;\n\t\t\t\toffset < txEnd;\n\t\t\t\toffset += DOC_BATCH_ROWS\n\t\t\t) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + DOC_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst rank = pseudoRandom(i);\n\t\t\t\t\tconst body = payload(`doc-${i}-${rank}:`, rowBytes);\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\t`doc-${paddedHex(i)}`,\n\t\t\t\t\t\trank,\n\t\t\t\t\t\t`tenant-${rank % 128}`,\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\trowBytes,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_docs (external_key, row_rank, tenant_id, body, body_bytes) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedLedger(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n) {\n\tawait resetLedger(database);\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (\n\t\t\t\tlet offset = txStart;\n\t\t\t\toffset < txEnd;\n\t\t\t\toffset += LEDGER_BATCH_ROWS\n\t\t\t) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + LEDGER_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst accountId = `acct-${String(i % 256).padStart(4, \"0\")}`;\n\t\t\t\t\tconst entryId = Math.floor(i / 256) + 1;\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\taccountId,\n\t\t\t\t\t\tentryId,\n\t\t\t\t\t\t(i % 2 === 0 ? 1 : -1) * (100 + (i % 10_000)),\n\t\t\t\t\t\t1_700_000_000_000 + i * 1000,\n\t\t\t\t\t\tpayload(\n\t\t\t\t\t\t\t`ledger-${accountId}-${entryId}:`,\n\t\t\t\t\t\t\tMath.min(rowBytes, 512),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_ledger (account_id, entry_id, amount_cents, created_at, memo) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nfunction buildChatLogMessage(seq: number, targetBytes: number): string {\n\tconst prefix = `message-${seq}: `;\n\treturn prefix + \"x\".repeat(Math.max(0, targetBytes - prefix.length));\n}\n\nasync function seedChatLog(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tawait resetChatLog(database);\n\tconst createdAtBase = 1_700_000_000_000;\n\tlet remainingBytes = targetBytes;\n\tlet rows = 0;\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\twhile (remainingBytes > 0) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\n\t\t\tfor (\n\t\t\t\tlet batchIndex = 0;\n\t\t\t\tbatchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0;\n\t\t\t\tbatchIndex += 1\n\t\t\t) {\n\t\t\t\tconst contentBytes = Math.min(\n\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\tremainingBytes,\n\t\t\t\t);\n\t\t\t\tconst seq = rows;\n\t\t\t\tconst role = seq % 2 === 0 ? \"user\" : \"assistant\";\n\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\tseq,\n\t\t\t\t\trole,\n\t\t\t\t\tbuildChatLogMessage(seq, contentBytes),\n\t\t\t\t\tcontentBytes,\n\t\t\t\t\tMath.ceil(contentBytes / 4),\n\t\t\t\t\tcreatedAtBase + seq,\n\t\t\t\t);\n\n\t\t\t\tremainingBytes -= contentBytes;\n\t\t\t\trows += 1;\n\t\t\t}\n\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO rw_chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes: CHAT_LOG_CHUNK_BYTES,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function batchInsert(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tsql: string,\n\trows: unknown[][],\n\tbatchSize: number,\n) {\n\tif (rows.length === 0) return;\n\tconst colsPerRow = rows[0]?.length ?? 0;\n\tif (colsPerRow === 0) return;\n\tconst placeholder = `(${\"?,\".repeat(colsPerRow).slice(0, -1)})`;\n\tfor (let i = 0; i < rows.length; i += batchSize) {\n\t\tconst chunk = rows.slice(i, i + batchSize);\n\t\tconst values = new Array(chunk.length).fill(placeholder).join(\",\");\n\t\tconst args: unknown[] = [];\n\t\tfor (const row of chunk) args.push(...row);\n\t\tawait database.execute(`${sql} VALUES ${values}`, ...args);\n\t}\n}\n\nasync function seedSqlRush(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tawait resetSqlRush(database);\n\tconst now = 1_700_000_000_000;\n\tconst startedAt = performance.now();\n\n\tawait withTransaction(database, async () => {\n\t\tconst msgsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_MSGS_COUNT; i += 1) {\n\t\t\tmsgsRows.push([\n\t\t\t\ti === 0 ? null : i,\n\t\t\t\ti % 3 === 0 ? \"user\" : \"assistant\",\n\t\t\t\tpayload(\"msg:\", 512),\n\t\t\t\t0,\n\t\t\t\tnow - (SQL_RUSH_MSGS_COUNT - i) * 1000,\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO msgs (parent, role, content, cancelled, created_at)\",\n\t\t\tmsgsRows,\n\t\t\t50,\n\t\t);\n\n\t\tconst toolRefsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_TOOL_REFS_COUNT; i += 1) {\n\t\t\ttoolRefsRows.push([\n\t\t\t\ti + 1,\n\t\t\t\t`tool_${i % 20}`,\n\t\t\t\t`call_${i}`,\n\t\t\t\ti % 5 === 0 ? \"pending\" : \"done\",\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO tool_refs (msg_id, tool_name, tool_call_id, status)\",\n\t\t\ttoolRefsRows,\n\t\t\t100,\n\t\t);\n\n\t\tconst eventsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_EVENTS_COUNT; i += 1) {\n\t\t\teventsRows.push([\n\t\t\t\ti + 1,\n\t\t\t\t`event_${i % 8}`,\n\t\t\t\tpayload(\"event:\", 256),\n\t\t\t\tnow - (SQL_RUSH_EVENTS_COUNT - i) * 100,\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO events (seq, event_type, payload, created_at)\",\n\t\t\teventsRows,\n\t\t\t100,\n\t\t);\n\n\t\tconst kvRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_KV_COUNT; i += 1) {\n\t\t\tkvRows.push([`kv_${i}`, payload(\"kv:\", 128), now]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO kv (key, value, updated_at)\",\n\t\t\tkvRows,\n\t\t\t40,\n\t\t);\n\n\t\tconst toolsRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_TOOLS_COUNT; i += 1) {\n\t\t\ttoolsRows.push([\n\t\t\t\t\"exec-1\",\n\t\t\t\t`tool_${i}`,\n\t\t\t\tpayload(\"tool:\", 1024),\n\t\t\t\tnow,\n\t\t\t]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO tools (executor_id, name, spec, updated_at)\",\n\t\t\ttoolsRows,\n\t\t\t41,\n\t\t);\n\n\t\tconst metaRows: unknown[][] = [];\n\t\tfor (let i = 0; i < SQL_RUSH_META_COUNT; i += 1) {\n\t\t\tmetaRows.push([`key_${i}`, payload(\"meta:\", 64)]);\n\t\t}\n\t\tawait batchInsert(\n\t\t\tdatabase,\n\t\t\t\"INSERT INTO meta (key, value)\",\n\t\t\tmetaRows,\n\t\t\t12,\n\t\t);\n\t});\n\n\treturn {\n\t\trows:\n\t\t\tSQL_RUSH_MSGS_COUNT +\n\t\t\tSQL_RUSH_TOOL_REFS_COUNT +\n\t\t\tSQL_RUSH_EVENTS_COUNT +\n\t\t\tSQL_RUSH_KV_COUNT +\n\t\t\tSQL_RUSH_TOOLS_COUNT +\n\t\t\tSQL_RUSH_META_COUNT,\n\t\ttargetBytes,\n\t\trowBytes: 0,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function seedMigrationSource(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n\trowBytes: number,\n\tskewed = false,\n) {\n\tawait resetMigration(database);\n\tawait database.execute(`CREATE TABLE rw_migration_source (\n\t\tid INTEGER PRIMARY KEY,\n\t\taccount_id TEXT NOT NULL,\n\t\tstatus TEXT NOT NULL,\n\t\tcreated_at INTEGER NOT NULL,\n\t\ttotal_cents INTEGER NOT NULL,\n\t\tbody TEXT NOT NULL\n\t)`);\n\n\tconst rows = Math.max(1, Math.ceil(targetBytes / rowBytes));\n\tconst startedAt = performance.now();\n\n\tfor (let txStart = 0; txStart < rows; txStart += SETUP_TRANSACTION_ROWS) {\n\t\tconst txEnd = Math.min(rows, txStart + SETUP_TRANSACTION_ROWS);\n\t\tawait withTransaction(database, async () => {\n\t\t\tfor (\n\t\t\t\tlet offset = txStart;\n\t\t\t\toffset < txEnd;\n\t\t\t\toffset += ORDER_BATCH_ROWS\n\t\t\t) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tconst args: unknown[] = [];\n\t\t\t\tconst batchEnd = Math.min(txEnd, offset + ORDER_BATCH_ROWS);\n\t\t\t\tfor (let i = offset; i < batchEnd; i += 1) {\n\t\t\t\t\tconst accountId = skewed\n\t\t\t\t\t\t? `acct-${i % 10 === 0 ? i % 512 : i % 8}`\n\t\t\t\t\t\t: `acct-${pseudoRandom(i) % 512}`;\n\t\t\t\t\tconst status = skewed\n\t\t\t\t\t\t? i % 20 === 0\n\t\t\t\t\t\t\t? \"failed\"\n\t\t\t\t\t\t\t: \"open\"\n\t\t\t\t\t\t: [\"open\", \"closed\", \"failed\", \"pending\"][i % 4];\n\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\targs.push(\n\t\t\t\t\t\ti + 1,\n\t\t\t\t\t\taccountId,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t1_700_000_000_000 + i * 1000,\n\t\t\t\t\t\t100 + (pseudoRandom(i + 41) % 50_000),\n\t\t\t\t\t\tpayload(`migration-${i}:`, rowBytes),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`INSERT INTO rw_migration_source (id, account_id, status, created_at, total_cents, body) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t...args,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t}\n\n\treturn {\n\t\trows,\n\t\ttargetBytes,\n\t\trowBytes,\n\t\tsetupMs: performance.now() - startedAt,\n\t\tpageCount: await queryPageCount(database),\n\t};\n}\n\nasync function readRowidRange(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\tdirection: \"forward\" | \"backward\",\n) {\n\tconst [count] = typedRows(\n\t\tawait database.execute(\"SELECT COUNT(*) AS rows FROM rw_orders\"),\n\t);\n\tconst rows = count?.rows ?? 0;\n\tlet bytes = 0;\n\tlet scannedRows = 0;\n\n\tif (direction === \"backward\") {\n\t\tfor (let upper = rows; upper > 0; upper -= RANGE_CHUNK_ROWS) {\n\t\t\tconst lower = Math.max(1, upper - RANGE_CHUNK_ROWS + 1);\n\t\t\tconst chunk = typedRows(\n\t\t\t\tawait database.execute(\n\t\t\t\t\t`SELECT length(note) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ? ORDER BY id DESC`,\n\t\t\t\t\tlower,\n\t\t\t\t\tupper,\n\t\t\t\t),\n\t\t\t);\n\t\t\tfor (const row of chunk) {\n\t\t\t\tbytes += row.bytes;\n\t\t\t\tscannedRows += 1;\n\t\t\t}\n\t\t}\n\t\treturn { rows: scannedRows, bytes };\n\t}\n\n\tfor (let lower = 1; lower <= rows; lower += RANGE_CHUNK_ROWS) {\n\t\tconst upper = lower + RANGE_CHUNK_ROWS - 1;\n\t\tconst [chunk] = typedRows<{ rows: number; bytes: number }>(\n\t\t\tawait database.execute(\n\t\t\t\t`SELECT COUNT(*) AS rows, COALESCE(SUM(length(note)), 0) AS bytes FROM rw_orders WHERE id BETWEEN ? AND ?`,\n\t\t\t\tlower,\n\t\t\t\tupper,\n\t\t\t),\n\t\t);\n\t\tbytes += chunk?.bytes ?? 0;\n\t\tscannedRows += chunk?.rows ?? 0;\n\t}\n\n\treturn { rows: scannedRows, bytes };\n}\n\nexport const sqliteRealworldBench = actor({\n\toptions: {\n\t\tactionTimeout: 1_200_000,\n\t\tsleepGracePeriod: 30_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_customers (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\temail TEXT NOT NULL,\n\t\t\t\tplan TEXT NOT NULL,\n\t\t\t\tregion TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_orders (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tcustomer_id INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\ttotal_cents INTEGER NOT NULL,\n\t\t\t\tshard INTEGER NOT NULL,\n\t\t\t\tnote TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_order_items (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\tsku TEXT NOT NULL,\n\t\t\t\tquantity INTEGER NOT NULL,\n\t\t\t\tprice_cents INTEGER NOT NULL,\n\t\t\t\tline_no INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_events (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\tevent_type TEXT NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tentity_key TEXT NOT NULL,\n\t\t\t\tproperties TEXT NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_docs (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\texternal_key TEXT NOT NULL UNIQUE,\n\t\t\t\trow_rank INTEGER NOT NULL,\n\t\t\t\ttenant_id TEXT NOT NULL,\n\t\t\t\tbody TEXT NOT NULL,\n\t\t\t\tbody_bytes INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_ledger (\n\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\tentry_id INTEGER NOT NULL,\n\t\t\t\tamount_cents INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\tmemo TEXT NOT NULL,\n\t\t\t\tPRIMARY KEY (account_id, entry_id)\n\t\t\t) WITHOUT ROWID`);\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS rw_chat_log (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tthread_id TEXT NOT NULL,\n\t\t\t\tseq INTEGER NOT NULL,\n\t\t\t\trole TEXT NOT NULL,\n\t\t\t\tcontent TEXT NOT NULL,\n\t\t\t\tcontent_bytes INTEGER NOT NULL,\n\t\t\t\ttoken_estimate INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY AUTOINCREMENT, parent INTEGER, role TEXT NOT NULL, content TEXT NOT NULL, cancelled INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS tool_refs (id INTEGER PRIMARY KEY AUTOINCREMENT, msg_id INTEGER NOT NULL, tool_name TEXT NOT NULL, tool_call_id TEXT NOT NULL, status TEXT NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY, event_type TEXT NOT NULL, payload TEXT NOT NULL, created_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS tools (id INTEGER PRIMARY KEY AUTOINCREMENT, executor_id TEXT NOT NULL, name TEXT NOT NULL, spec TEXT NOT NULL, updated_at INTEGER NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_customer_created ON rw_orders(customer_id, created_at DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_status_created ON rw_orders(status, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_orders_created ON rw_orders(created_at DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_order_items_order ON rw_order_items(order_id)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_events_account_created ON rw_events(account_id, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_docs_external_rank ON rw_docs(external_key, row_rank)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_docs_tenant_rank ON rw_docs(tenant_id, row_rank)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_rw_chat_log_thread_seq ON rw_chat_log(thread_id, seq DESC)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_msgs_parent_role_cancelled_created_at ON msgs (parent, role, cancelled, created_at)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_tool_refs_msg_id ON tool_refs (msg_id)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tinspectCacheConfig: async (c) => {\n\t\t\tconst [cacheSize] = typedRows(\n\t\t\t\tawait c.db.execute(\"PRAGMA cache_size\"),\n\t\t\t);\n\t\t\tconst [pageSize] = typedRows(\n\t\t\t\tawait c.db.execute(\"PRAGMA page_size\"),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tsqliteCacheSizePragma: cacheSize?.cache_size ?? null,\n\t\t\t\tsqlitePageSize: pageSize?.page_size ?? null,\n\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t};\n\t\t},\n\n\t\tsetupWorkload: async (c, input: SetupInput) => {\n\t\t\tassertWorkload(input.workload);\n\t\t\tconst rowBytes = positiveInteger(\n\t\t\t\tinput.rowBytes,\n\t\t\t\tDEFAULT_ROW_BYTES,\n\t\t\t\t\"rowBytes\",\n\t\t\t);\n\t\t\tif (input.workload === \"migration-ddl-small\") {\n\t\t\t\tawait resetMigration(c.db);\n\t\t\t\treturn {\n\t\t\t\t\trows: 0,\n\t\t\t\t\ttargetBytes: 0,\n\t\t\t\t\trowBytes,\n\t\t\t\t\tsetupMs: 0,\n\t\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst targetBytes = positiveInteger(\n\t\t\t\tinput.targetBytes,\n\t\t\t\t8 * 1024 * 1024,\n\t\t\t\t\"targetBytes\",\n\t\t\t);\n\n\t\t\tswitch (input.workload) {\n\t\t\t\tcase \"small-rowid-point\":\n\t\t\t\tcase \"small-schema-read\":\n\t\t\t\tcase \"small-range-scan\":\n\t\t\t\tcase \"rowid-range-forward\":\n\t\t\t\tcase \"rowid-range-backward\":\n\t\t\t\tcase \"aggregate-status\":\n\t\t\t\tcase \"aggregate-time-bucket\":\n\t\t\t\tcase \"aggregate-tenant-time-range\":\n\t\t\t\tcase \"parallel-read-aggregates\":\n\t\t\t\tcase \"parallel-read-write-transition\":\n\t\t\t\tcase \"feed-order-by-limit\":\n\t\t\t\tcase \"feed-pagination-adjacent\":\n\t\t\t\tcase \"join-order-items\":\n\t\t\t\tcase \"random-point-lookups\":\n\t\t\t\tcase \"write-batch-after-wake\":\n\t\t\t\tcase \"update-hot-partition\":\n\t\t\t\tcase \"delete-churn-range-read\":\n\t\t\t\t\treturn seedCommerce(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"secondary-index-covering-range\":\n\t\t\t\tcase \"secondary-index-scattered-table\":\n\t\t\t\tcase \"hot-index-cold-table\":\n\t\t\t\t\treturn seedDocs(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"ledger-without-rowid-range\":\n\t\t\t\t\treturn seedLedger(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"chat-log-select-limit\":\n\t\t\t\tcase \"chat-log-select-indexed\":\n\t\t\t\tcase \"chat-log-count\":\n\t\t\t\tcase \"chat-log-sum\":\n\t\t\t\tcase \"chat-tool-read-fanout\":\n\t\t\t\t\treturn seedChatLog(c.db, targetBytes);\n\t\t\t\tcase \"chat-tool-script\":\n\t\t\t\t\treturn seedSqlRush(c.db, targetBytes);\n\t\t\t\tcase \"migration-create-indexes-large\":\n\t\t\t\t\treturn seedMigrationSource(c.db, targetBytes, rowBytes);\n\t\t\t\tcase \"migration-create-indexes-skewed-large\":\n\t\t\t\t\treturn seedMigrationSource(\n\t\t\t\t\t\tc.db,\n\t\t\t\t\t\ttargetBytes,\n\t\t\t\t\t\trowBytes,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t);\n\t\t\t\tcase \"migration-table-rebuild-large\":\n\t\t\t\tcase \"migration-add-column-large\":\n\t\t\t\t\treturn seedMigrationSource(c.db, targetBytes, rowBytes);\n\t\t\t}\n\t\t},\n\n\t\trunWorkload: async (c, input: RunInput) => {\n\t\t\tassertWorkload(input.workload);\n\t\t\tconst t0 = performance.now();\n\t\t\tlet details: Record;\n\n\t\t\tswitch (input.workload) {\n\t\t\t\tcase \"small-rowid-point\": {\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (let i = 0; i < 50; i += 1) {\n\t\t\t\t\t\tconst id = (i % 16) + 1;\n\t\t\t\t\t\tconst [row] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT length(note) AS bytes FROM rw_orders WHERE id = ?\",\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += row?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { ops: 50, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"small-schema-read\": {\n\t\t\t\t\tconst tables = await c.db.execute(\n\t\t\t\t\t\t\"SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index') ORDER BY name\",\n\t\t\t\t\t);\n\t\t\t\t\tconst columns = await c.db.execute(\n\t\t\t\t\t\t\"PRAGMA table_info(rw_orders)\",\n\t\t\t\t\t);\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tobjects: tables.length,\n\t\t\t\t\t\tcolumns: columns.length,\n\t\t\t\t\t\trows: count?.rows ?? 0,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"small-range-scan\":\n\t\t\t\tcase \"rowid-range-forward\": {\n\t\t\t\t\tdetails = await readRowidRange(c.db, \"forward\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"rowid-range-backward\": {\n\t\t\t\t\tdetails = await readRowidRange(c.db, \"backward\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"secondary-index-covering-range\": {\n\t\t\t\t\tconst rows = typedRows<{\n\t\t\t\t\t\texternal_key: string;\n\t\t\t\t\t\trow_rank: number;\n\t\t\t\t\t}>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT external_key, row_rank FROM rw_docs\n\t\t\t\t\t\tWHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff'\n\t\t\t\t\t\tORDER BY external_key`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet checksum = 0;\n\t\t\t\t\tfor (const row of rows)\n\t\t\t\t\t\tchecksum = (checksum + row.row_rank) >>> 0;\n\t\t\t\t\tdetails = { rows: rows.length, checksum };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"secondary-index-scattered-table\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT body_bytes AS bytes FROM rw_docs\n\t\t\t\t\t\tWHERE external_key BETWEEN 'doc-00000000' AND 'doc-ffffffff'\n\t\t\t\t\t\tORDER BY external_key`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of rows) bytes += row.bytes;\n\t\t\t\t\tdetails = { rows: rows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-status\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-time-bucket\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY bucket\n\t\t\t\t\t\tORDER BY bucket`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tbuckets: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"aggregate-tenant-time-range\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total\n\t\t\t\t\t\tFROM rw_events e\n\t\t\t\t\t\tJOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER)\n\t\t\t\t\t\tWHERE e.account_id = ? AND e.created_at BETWEEN ? AND ?\n\t\t\t\t\t\tGROUP BY e.event_type\n\t\t\t\t\t\tORDER BY e.event_type`,\n\t\t\t\t\t\t\t\"acct-7\",\n\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\t1_700_000_000_000 + 86_400_000,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"parallel-read-aggregates\": {\n\t\t\t\t\tconst [statusRows, bucketRows, tenantRows, joinRows] =\n\t\t\t\t\t\tawait Promise.all([\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t`SELECT (created_at / 300000) AS bucket, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY bucket\n\t\t\t\t\t\tORDER BY bucket`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t`SELECT e.event_type, COUNT(*) AS rows, SUM(o.total_cents) AS total\n\t\t\t\t\t\tFROM rw_events e\n\t\t\t\t\t\tJOIN rw_orders o ON o.id = CAST(substr(e.entity_key, 7) AS INTEGER)\n\t\t\t\t\t\tWHERE e.account_id = ? AND e.created_at BETWEEN ? AND ?\n\t\t\t\t\t\tGROUP BY e.event_type\n\t\t\t\t\t\tORDER BY e.event_type`,\n\t\t\t\t\t\t\t\t\"acct-7\",\n\t\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\t\t1_700_000_000_000 + 86_400_000,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t]);\n\t\t\t\t\tconst aggregates = [\n\t\t\t\t\t\t...typedRows(statusRows),\n\t\t\t\t\t\t...typedRows(bucketRows),\n\t\t\t\t\t\t...typedRows(tenantRows),\n\t\t\t\t\t\t...typedRows(joinRows),\n\t\t\t\t\t];\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\tgroups: aggregates.length,\n\t\t\t\t\t\trows: aggregates.reduce(\n\t\t\t\t\t\t\t(sum, row) => sum + row.rows,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t),\n\t\t\t\t\t\ttotal: aggregates.reduce(\n\t\t\t\t\t\t\t(sum, row) => sum + row.total,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"parallel-read-write-transition\": {\n\t\t\t\t\tconst readStatus = c.db.execute(\n\t\t\t\t\t\t`SELECT status, COUNT(*) AS rows, SUM(total_cents) AS total\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tGROUP BY status\n\t\t\t\t\t\tORDER BY status`,\n\t\t\t\t\t);\n\t\t\t\t\tconst readJoin = c.db.execute(\n\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t);\n\t\t\t\t\tconst writeHotShard = c.db.execute(\n\t\t\t\t\t\t\"UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 7\",\n\t\t\t\t\t);\n\t\t\t\t\tconst readAfterWrite = c.db.execute(\n\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 7\",\n\t\t\t\t\t);\n\t\t\t\t\tconst [statusRows, joinRows, , shardRows] =\n\t\t\t\t\t\tawait Promise.all([\n\t\t\t\t\t\t\treadStatus,\n\t\t\t\t\t\t\treadJoin,\n\t\t\t\t\t\t\twriteHotShard,\n\t\t\t\t\t\t\treadAfterWrite,\n\t\t\t\t\t\t]);\n\t\t\t\t\tconst aggregates = [\n\t\t\t\t\t\t...typedRows(statusRows),\n\t\t\t\t\t\t...typedRows(joinRows),\n\t\t\t\t\t];\n\t\t\t\t\tconst [shardCount] = typedRows(shardRows);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\treadOps: 3,\n\t\t\t\t\t\twriteOps: 1,\n\t\t\t\t\t\tgroups: aggregates.length,\n\t\t\t\t\t\trows:\n\t\t\t\t\t\t\taggregates.reduce((sum, row) => sum + row.rows, 0) +\n\t\t\t\t\t\t\t(shardCount?.rows ?? 0),\n\t\t\t\t\t\ttotal: aggregates.reduce(\n\t\t\t\t\t\t\t(sum, row) => sum + row.total,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"feed-order-by-limit\": {\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t`SELECT id, customer_id, created_at, status, total_cents\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at >= ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT 1000`,\n\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"feed-pagination-adjacent\": {\n\t\t\t\t\tconst firstPage = typedRows<{ created_at: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT created_at\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at >= ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT ?`,\n\t\t\t\t\t\t\t1_700_000_000_000,\n\t\t\t\t\t\t\tFEED_PAGE_ROWS,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst cursor =\n\t\t\t\t\t\tfirstPage.at(-1)?.created_at ?? 1_700_000_000_000;\n\t\t\t\t\tconst secondPage = await c.db.execute(\n\t\t\t\t\t\t`SELECT id, customer_id, created_at, status, total_cents\n\t\t\t\t\t\tFROM rw_orders\n\t\t\t\t\t\tWHERE created_at < ?\n\t\t\t\t\t\tORDER BY created_at DESC\n\t\t\t\t\t\tLIMIT ?`,\n\t\t\t\t\t\tcursor,\n\t\t\t\t\t\tFEED_PAGE_ROWS,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tfirstPageRows: firstPage.length,\n\t\t\t\t\t\trows: secondPage.length,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"join-order-items\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT o.status, COUNT(*) AS rows, SUM(oi.quantity * oi.price_cents) AS total\n\t\t\t\t\t\tFROM rw_orders o\n\t\t\t\t\t\tJOIN rw_order_items oi ON oi.order_id = o.id\n\t\t\t\t\t\tGROUP BY o.status\n\t\t\t\t\t\tORDER BY o.status`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tgroups: rows.length,\n\t\t\t\t\t\trows: rows.reduce((sum, row) => sum + row.rows, 0),\n\t\t\t\t\t\ttotal: rows.reduce((sum, row) => sum + row.total, 0),\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"random-point-lookups\": {\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst rows = Math.max(1, count?.rows ?? 1);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (let i = 0; i < POINT_LOOKUP_OPS; i += 1) {\n\t\t\t\t\t\tconst id = (pseudoRandom(i) % rows) + 1;\n\t\t\t\t\t\tconst [row] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT length(note) AS bytes FROM rw_orders WHERE id = ?\",\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += row?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { ops: POINT_LOOKUP_OPS, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"hot-index-cold-table\": {\n\t\t\t\t\tconst indexRows = typedRows<{ id: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT id\n\t\t\t\t\t\tFROM rw_docs\n\t\t\t\t\t\tWHERE tenant_id = ?\n\t\t\t\t\t\tORDER BY row_rank\n\t\t\t\t\t\tLIMIT 1000`,\n\t\t\t\t\t\t\t\"tenant-7\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of indexRows) {\n\t\t\t\t\t\tconst [doc] = typedRows(\n\t\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT body_bytes AS bytes FROM rw_docs WHERE id = ?\",\n\t\t\t\t\t\t\t\trow.id,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbytes += doc?.bytes ?? 0;\n\t\t\t\t\t}\n\t\t\t\t\tdetails = { rows: indexRows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"ledger-without-rowid-range\": {\n\t\t\t\t\tconst rows = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`SELECT account_id, entry_id, amount_cents, length(memo) AS bytes\n\t\t\t\t\t\tFROM rw_ledger\n\t\t\t\t\t\tWHERE account_id BETWEEN 'acct-0040' AND 'acct-0180'\n\t\t\t\t\t\tORDER BY account_id, entry_id`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tlet bytes = 0;\n\t\t\t\t\tfor (const row of rows) bytes += row.bytes;\n\t\t\t\t\tdetails = { rows: rows.length, bytes };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-select-limit\": {\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-select-indexed\": {\n\t\t\t\t\tconst expectedRows = Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.ceil(\n\t\t\t\t\t\t\tpositiveInteger(\n\t\t\t\t\t\t\t\tinput.targetBytes,\n\t\t\t\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t\t\t\"targetBytes\",\n\t\t\t\t\t\t\t) / CHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst lowerBound = Math.max(0, expectedRows - 100);\n\t\t\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\t\t\"SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\tlowerBound,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: rows.length };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-count\": {\n\t\t\t\t\tconst [row] = typedRows<{ count: number }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { ops: 1, rows: row?.count ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-log-sum\": {\n\t\t\t\t\tconst [row] = typedRows<{ total_bytes: number | null }>(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { ops: 1, bytes: row?.total_bytes ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-tool-read-fanout\": {\n\t\t\t\t\tconst expectedRows = Math.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\tMath.ceil(\n\t\t\t\t\t\t\tpositiveInteger(\n\t\t\t\t\t\t\t\tinput.targetBytes,\n\t\t\t\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t\t\t\"targetBytes\",\n\t\t\t\t\t\t\t) / CHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst lowerBound = Math.max(0, expectedRows - 100);\n\t\t\t\t\tconst [limitRows, indexedRows, countRows, sumRows] =\n\t\t\t\t\t\tawait Promise.all([\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM rw_chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT seq, role, content_bytes FROM rw_chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t\t\tlowerBound,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT COUNT(*) AS count FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM rw_chat_log WHERE thread_id = ?\",\n\t\t\t\t\t\t\t\tCHAT_THREAD_ID,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t]);\n\t\t\t\t\tconst [countRow] = typedRows<{ count: number }>(countRows);\n\t\t\t\t\tconst [sumRow] = typedRows<{ total_bytes: number | null }>(\n\t\t\t\t\t\tsumRows,\n\t\t\t\t\t);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 4,\n\t\t\t\t\t\tlimitRows: limitRows.length,\n\t\t\t\t\t\tindexedRows: indexedRows.length,\n\t\t\t\t\t\trows: countRow?.count ?? 0,\n\t\t\t\t\t\tbytes: sumRow?.total_bytes ?? 0,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"chat-tool-script\": {\n\t\t\t\t\tconst [\n\t\t\t\t\t\tmsgsRows,\n\t\t\t\t\t\ttoolRefsRows,\n\t\t\t\t\t\teventsRows,\n\t\t\t\t\t\tkvRows,\n\t\t\t\t\t\ttoolsRows,\n\t\t\t\t\t\tmetaRows,\n\t\t\t\t\t\tunresolvedRows,\n\t\t\t\t\t] = await Promise.all([\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, role, length(content) AS bytes FROM msgs WHERE parent IS NOT NULL AND role = ? AND cancelled = 0 ORDER BY created_at DESC LIMIT 50\",\n\t\t\t\t\t\t\t\"assistant\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, tool_name, status FROM tool_refs WHERE status = ? ORDER BY id DESC LIMIT 50\",\n\t\t\t\t\t\t\t\"pending\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT seq, event_type, length(payload) AS bytes FROM events WHERE seq > ? ORDER BY seq ASC LIMIT 100\",\n\t\t\t\t\t\t\t600,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT key, length(value) AS bytes FROM kv ORDER BY updated_at DESC LIMIT 20\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT id, name, length(spec) AS bytes FROM tools WHERE executor_id = ? ORDER BY updated_at DESC\",\n\t\t\t\t\t\t\t\"exec-1\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(\n\t\t\t\t\t\t\t\"SELECT key, length(value) AS bytes FROM meta\",\n\t\t\t\t\t\t),\n\t\t\t\t\t\tc.db.execute(`SELECT m.id, m.role, count(tr.id) AS pending_refs\n\t\t\t\t\t\t\tFROM msgs m\n\t\t\t\t\t\t\tLEFT JOIN tool_refs tr ON tr.msg_id = m.id AND tr.status = 'pending'\n\t\t\t\t\t\t\tWHERE m.role = 'assistant' AND m.cancelled = 0\n\t\t\t\t\t\t\tGROUP BY m.id\n\t\t\t\t\t\t\tORDER BY m.created_at DESC\n\t\t\t\t\t\t\tLIMIT 100`),\n\t\t\t\t\t]);\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\tops: 7,\n\t\t\t\t\t\tmsgsRows: msgsRows.length,\n\t\t\t\t\t\ttoolRefsRows: toolRefsRows.length,\n\t\t\t\t\t\teventsRows: eventsRows.length,\n\t\t\t\t\t\tkvRows: kvRows.length,\n\t\t\t\t\t\ttoolsRows: toolsRows.length,\n\t\t\t\t\t\tmetaRows: metaRows.length,\n\t\t\t\t\t\tunresolvedRows: unresolvedRows.length,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"write-batch-after-wake\": {\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst startId = (count?.rows ?? 0) + 1;\n\t\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\t\tfor (\n\t\t\t\t\t\tlet offset = 0;\n\t\t\t\t\t\toffset < 1000;\n\t\t\t\t\t\toffset += ORDER_BATCH_ROWS\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\t\t\tconst args: unknown[] = [];\n\t\t\t\t\t\tfor (\n\t\t\t\t\t\t\tlet i = offset;\n\t\t\t\t\t\t\ti < offset + ORDER_BATCH_ROWS;\n\t\t\t\t\t\t\ti += 1\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst id = startId + i;\n\t\t\t\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\t\t\t\targs.push(\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\t(i % 128) + 1,\n\t\t\t\t\t\t\t\t1_800_000_000_000 + i,\n\t\t\t\t\t\t\t\t\"pending\",\n\t\t\t\t\t\t\t\t1000 + i,\n\t\t\t\t\t\t\t\ti % 128,\n\t\t\t\t\t\t\t\tpayload(\n\t\t\t\t\t\t\t\t\t`wake-insert-${id}:`,\n\t\t\t\t\t\t\t\t\tDEFAULT_ROW_BYTES,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t`INSERT INTO rw_orders (id, customer_id, created_at, status, total_cents, shard, note) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t\t\t\t...args,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\t\tdetails = { rows: 1000 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"update-hot-partition\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"UPDATE rw_orders SET total_cents = total_cents + 1 WHERE shard BETWEEN 0 AND 15\",\n\t\t\t\t\t);\n\t\t\t\t\tconst [count] = typedRows(\n\t\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\t\"SELECT COUNT(*) AS rows FROM rw_orders WHERE shard BETWEEN 0 AND 15\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rows: count?.rows ?? 0 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"delete-churn-range-read\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"DELETE FROM rw_orders WHERE shard BETWEEN 0 AND 15\",\n\t\t\t\t\t);\n\t\t\t\t\tconst result = await readRowidRange(c.db, \"forward\");\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tdeletedShardCount: 16,\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-create-indexes-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_account ON rw_migration_source(account_id)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_created ON rw_migration_source(created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_status_total ON rw_migration_source(status, total_cents)\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { indexes: 3 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-create-indexes-skewed-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_skew_account ON rw_migration_source(account_id, created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_source_skew_status ON rw_migration_source(status, total_cents)\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { indexes: 2, skewed: true };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-table-rebuild-large\": {\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_source_rebuilt (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\taccount_id TEXT NOT NULL,\n\t\t\t\t\t\tstatus TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\ttotal_cents INTEGER NOT NULL,\n\t\t\t\t\t\tbody TEXT NOT NULL,\n\t\t\t\t\t\tarchived_at INTEGER\n\t\t\t\t\t)`);\n\t\t\t\t\tawait c.db.execute(`INSERT INTO rw_migration_source_rebuilt (\n\t\t\t\t\t\tid, account_id, status, created_at, total_cents, body, archived_at\n\t\t\t\t\t)\n\t\t\t\t\tSELECT id, account_id, status, created_at, total_cents, body, NULL\n\t\t\t\t\tFROM rw_migration_source`);\n\t\t\t\t\tawait c.db.execute(\"DROP TABLE rw_migration_source\");\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"ALTER TABLE rw_migration_source_rebuilt RENAME TO rw_migration_source\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { rebuilt: true };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-add-column-large\": {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"ALTER TABLE rw_migration_source ADD COLUMN archived_at INTEGER\",\n\t\t\t\t\t);\n\t\t\t\t\tdetails = { alters: 1, rewritesRows: false };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"migration-ddl-small\": {\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_empty (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\ttenant_id TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t\t)`);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"ALTER TABLE rw_migration_empty ADD COLUMN status TEXT\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"CREATE INDEX idx_rw_migration_empty_tenant_created ON rw_migration_empty(tenant_id, created_at)\",\n\t\t\t\t\t);\n\t\t\t\t\tawait c.db.execute(`CREATE TABLE rw_migration_audit (\n\t\t\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\t\t\tmigration_name TEXT NOT NULL,\n\t\t\t\t\t\tapplied_at INTEGER NOT NULL\n\t\t\t\t\t)`);\n\t\t\t\t\tdetails = { tables: 2, indexes: 1, alters: 1 };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst ms = performance.now() - t0;\n\t\t\treturn {\n\t\t\t\tms,\n\t\t\t\tworkload: input.workload,\n\t\t\t\t...details,\n\t\t\t\tpageCount: await queryPageCount(c.db),\n\t\t\t};\n\t\t},\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\n\nexport const testCounter = actor({\n\tstate: { count: 0 },\n\tactions: {\n\t\tincrement: (c, amount: number = 1) => {\n\t\t\tc.state.count += amount;\n\t\t\treturn c.state.count;\n\t\t},\n\t\tgetCount: (c) => {\n\t\t\treturn c.state.count;\n\t\t},\n\t\treset: (c) => {\n\t\t\tc.state.count = 0;\n\t\t\treturn c.state.count;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const testCounterSqlite = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tvalue INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tincrement: async (c, amount: number = 1) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE counter SET value = value + ? WHERE id = 1\",\n\t\t\t\tamount,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\tgetCount: async (c) => {\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"UPDATE counter SET value = 0 WHERE id = 1\");\n\t\t\treturn 0;\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nconst CHAT_LOG_CHUNK_BYTES = 4 * 1024;\nconst CHAT_LOG_INSERT_BATCH_SIZE = 50;\n\nfunction buildChatLogMessage(seq: number, targetBytes: number): string {\n\tconst prefix = `message-${seq}: `;\n\treturn prefix + \"x\".repeat(Math.max(0, targetBytes - prefix.length));\n}\n\nasync function seedChatLog(\n\tdatabase: {\n\t\texecute: (sql: string, ...args: unknown[]) => Promise;\n\t},\n\ttargetBytes: number,\n) {\n\tconst threadId = `chat-${crypto.randomUUID()}`;\n\tconst createdAtBase = Date.now();\n\tlet remainingBytes = targetBytes;\n\tlet rows = 0;\n\n\tawait database.execute(\"BEGIN\");\n\ttry {\n\t\twhile (remainingBytes > 0) {\n\t\t\tconst placeholders: string[] = [];\n\t\t\tconst args: unknown[] = [];\n\n\t\t\tfor (\n\t\t\t\tlet batchIndex = 0;\n\t\t\t\tbatchIndex < CHAT_LOG_INSERT_BATCH_SIZE && remainingBytes > 0;\n\t\t\t\tbatchIndex++\n\t\t\t) {\n\t\t\t\tconst contentBytes = Math.min(\n\t\t\t\t\tCHAT_LOG_CHUNK_BYTES,\n\t\t\t\t\tremainingBytes,\n\t\t\t\t);\n\t\t\t\tconst seq = rows;\n\t\t\t\tconst role = seq % 2 === 0 ? \"user\" : \"assistant\";\n\n\t\t\t\tplaceholders.push(\"(?, ?, ?, ?, ?, ?, ?)\");\n\t\t\t\targs.push(\n\t\t\t\t\tthreadId,\n\t\t\t\t\tseq,\n\t\t\t\t\trole,\n\t\t\t\t\tbuildChatLogMessage(seq, contentBytes),\n\t\t\t\t\tcontentBytes,\n\t\t\t\t\tMath.ceil(contentBytes / 4),\n\t\t\t\t\tcreatedAtBase + seq,\n\t\t\t\t);\n\n\t\t\t\tremainingBytes -= contentBytes;\n\t\t\t\trows++;\n\t\t\t}\n\n\t\t\tawait database.execute(\n\t\t\t\t`INSERT INTO chat_log (thread_id, seq, role, content, content_bytes, token_estimate, created_at) VALUES ${placeholders.join(\", \")}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t}\n\n\t\tawait database.execute(\"COMMIT\");\n\t} catch (err) {\n\t\tawait database.execute(\"ROLLBACK\");\n\t\tthrow err;\n\t}\n\n\treturn { threadId, rows, totalBytes: targetBytes };\n}\n\nexport const testSqliteBench = actor({\n\toptions: {\n\t\tactionTimeout: 300_000,\n\t},\n\tdb: db({\n\t\tonMigrate: async (database) => {\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tkey TEXT NOT NULL,\n\t\t\t\tvalue TEXT NOT NULL,\n\t\t\t\tnum INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tpayload BLOB,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_bench_key ON bench(key)\",\n\t\t\t);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_bench_num ON bench(num)\",\n\t\t\t);\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench_json (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tdata TEXT NOT NULL DEFAULT '{}'\n\t\t\t)`);\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS bench_secondary (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tbench_id INTEGER NOT NULL,\n\t\t\t\tlabel TEXT NOT NULL,\n\t\t\t\tscore REAL NOT NULL DEFAULT 0,\n\t\t\t\tFOREIGN KEY (bench_id) REFERENCES bench(id)\n\t\t\t)`);\n\n\t\t\tawait database.execute(`CREATE TABLE IF NOT EXISTS chat_log (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tthread_id TEXT NOT NULL,\n\t\t\t\tseq INTEGER NOT NULL,\n\t\t\t\trole TEXT NOT NULL,\n\t\t\t\tcontent TEXT NOT NULL,\n\t\t\t\tcontent_bytes INTEGER NOT NULL,\n\t\t\t\ttoken_estimate INTEGER NOT NULL,\n\t\t\t\tcreated_at INTEGER NOT NULL DEFAULT 0\n\t\t\t)`);\n\t\t\tawait database.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_chat_log_thread_seq ON chat_log(thread_id, seq DESC)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tnoop: (_c) => ({ ok: true }),\n\n\t\tgoToSleep: (c) => {\n\t\t\tc.sleep();\n\t\t\treturn { ok: true };\n\t\t},\n\n\t\tinsertSingle: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`k-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tinsertTx: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`k-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tinsertBatch: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst placeholders = Array.from(\n\t\t\t\t{ length: n },\n\t\t\t\t() => \"(?, ?, ?, ?)\",\n\t\t\t).join(\", \");\n\t\t\tconst args: unknown[] = [];\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\targs.push(`k-${i}`, `v-${i}`, i, Date.now());\n\t\t\t}\n\t\t\tawait c.db.execute(\n\t\t\t\t`INSERT INTO bench (key, value, num, created_at) VALUES ${placeholders}`,\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tpointRead: async (c, n: number) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES ('pr', 'pr', 0, 0)\",\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT id FROM bench WHERE key = 'pr' LIMIT 1\",\n\t\t\t);\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\"SELECT * FROM bench WHERE id = ?\", id);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tfullScan: async (c, seedRows: number) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < seedRows; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`scan-${i}`,\n\t\t\t\t\t`val-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\"SELECT * FROM bench\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\trangeScanIndexed: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`rs-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM bench WHERE num BETWEEN 100 AND 300\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\trangeScanUnindexed: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`ru-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM bench WHERE value BETWEEN 'v-100' AND 'v-300'\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tbulkUpdate: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`bu-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE bench SET value = 'updated', num = num + 1000 WHERE key LIKE 'bu-%'\",\n\t\t\t);\n\t\t\treturn { ms: performance.now() - t0, seedMs, ops: 200 };\n\t\t},\n\n\t\tbulkDelete: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`bd-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"DELETE FROM bench WHERE key LIKE 'bd-%'\");\n\t\t\treturn { ms: performance.now() - t0, seedMs, ops: 200 };\n\t\t},\n\n\t\thotRowUpdates: async (c, n: number) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES ('hot', 'v', 0, 0)\",\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT id FROM bench WHERE key = 'hot' LIMIT 1\",\n\t\t\t);\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"UPDATE bench SET num = ? WHERE id = ?\",\n\t\t\t\t\ti,\n\t\t\t\t\tid,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tvacuumAfterDelete: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`vac-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tawait c.db.execute(\"DELETE FROM bench WHERE key LIKE 'vac-%'\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"VACUUM\");\n\t\t\treturn { ms: performance.now() - t0, seedMs };\n\t\t},\n\n\t\tlargePayloadInsert: async (c, n: number) => {\n\t\t\tconst blob = \"x\".repeat(32 * 1024);\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t`lp-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tblob,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tmixedOltp: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\"oltp\",\n\t\t\t\t\"initial\",\n\t\t\t\t0,\n\t\t\t\tDate.now(),\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM bench WHERE key = 'oltp' LIMIT 1\",\n\t\t\t);\n\t\t\tconst id = (rows[0] as { id: number }).id;\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE bench SET value = 'updated', num = 1 WHERE id = ?\",\n\t\t\t\tid,\n\t\t\t);\n\t\t\tawait c.db.execute(\"SELECT * FROM bench WHERE id = ?\", id);\n\t\t\treturn { ms: performance.now() - t0, ops: 4 };\n\t\t},\n\n\t\tjsonInsertAndQuery: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tfor (let i = 0; i < 50; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench_json (data) VALUES (?)\",\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\tname: `item-${i}`,\n\t\t\t\t\t\ttags: [\"a\", \"b\"],\n\t\t\t\t\t\tscore: Math.random() * 100,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT id, json_extract(data, '$.name') as name, json_extract(data, '$.score') as score FROM bench_json ORDER BY json_extract(data, '$.score') DESC LIMIT 10\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: 51,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tjsonEachAgg: async (c) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO bench_json (data) VALUES (?)\",\n\t\t\t\tJSON.stringify({\n\t\t\t\t\titems: Array.from({ length: 100 }, (_, i) => ({\n\t\t\t\t\t\tid: i,\n\t\t\t\t\t\tval: i * 10,\n\t\t\t\t\t})),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT SUM(json_extract(value, '$.val')) as total FROM bench_json, json_each(json_extract(data, '$.items')) LIMIT 1\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\ttotal: (rows[0] as { total: number }).total,\n\t\t\t};\n\t\t},\n\n\t\tcomplexAggregation: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`grp-${i % 10}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT key, COUNT(*) as cnt, AVG(num) as avg_num, MIN(num) as min_num, MAX(num) as max_num FROM bench WHERE key LIKE 'grp-%' GROUP BY key ORDER BY cnt DESC\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\tgroups: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tcomplexSubquery: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`sq-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT * FROM bench WHERE num > (SELECT AVG(num) FROM bench) ORDER BY num DESC LIMIT 50\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tcomplexJoin: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`j-${i}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench_secondary (bench_id, label, score) VALUES (?, ?, ?)\",\n\t\t\t\t\ti + 1,\n\t\t\t\t\t`label-${i}`,\n\t\t\t\t\tMath.random() * 100,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT b.key, b.num, s.label, s.score FROM bench b INNER JOIN bench_secondary s ON s.bench_id = b.id WHERE b.key LIKE 'j-%' ORDER BY s.score DESC LIMIT 200\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tcomplexCteWindow: async (c) => {\n\t\t\tconst t0Seed = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 200; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bench (key, value, num, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\t\t`cte-${i % 10}`,\n\t\t\t\t\t`v-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t\tDate.now(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst seedMs = performance.now() - t0Seed;\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(`\n\t\t\t\tWITH ranked AS (\n\t\t\t\t\tSELECT key, num, ROW_NUMBER() OVER (PARTITION BY key ORDER BY num DESC) as rn,\n\t\t\t\t\t AVG(num) OVER (PARTITION BY key) as avg_num\n\t\t\t\t\tFROM bench\n\t\t\t\t\tWHERE key LIKE 'cte-%'\n\t\t\t\t)\n\t\t\t\tSELECT * FROM ranked WHERE rn <= 3 ORDER BY key, rn\n\t\t\t`);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tseedMs,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t};\n\t\t},\n\n\t\tmigrationTables: async (c, n: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS mig_${i} (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tdata TEXT NOT NULL DEFAULT ''\n\t\t\t\t)`);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: n };\n\t\t},\n\n\t\tchatLogInsert: async (c, totalBytes: number) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: seeded.rows,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogSelectLimit: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT seq, role, substr(content, 1, 128) AS preview FROM chat_log ORDER BY created_at DESC LIMIT 100\",\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: (rows as unknown[]).length,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogSelectIndexed: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst lowerBound = Math.max(0, seeded.rows - 100);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT seq, role, content_bytes FROM chat_log WHERE thread_id = ? AND seq >= ? ORDER BY seq DESC LIMIT 100\",\n\t\t\t\tseeded.threadId,\n\t\t\t\tlowerBound,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: (rows as unknown[]).length,\n\t\t\t\trows: (rows as unknown[]).length,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogCount: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT COUNT(*) AS count FROM chat_log WHERE thread_id = ?\",\n\t\t\t\tseeded.threadId,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: 1,\n\t\t\t\tcount: (rows[0] as { count: number }).count,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tchatLogSum: async (c, totalBytes: number) => {\n\t\t\tconst seeded = await seedChatLog(c.db, totalBytes);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT SUM(content_bytes) AS total_bytes FROM chat_log WHERE thread_id = ?\",\n\t\t\t\tseeded.threadId,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: 1,\n\t\t\t\ttotalBytes:\n\t\t\t\t\t(rows[0] as { total_bytes: number | null }).total_bytes ??\n\t\t\t\t\t0,\n\t\t\t\tbytes: seeded.totalBytes,\n\t\t\t};\n\t\t},\n\n\t\tlargeTxInsert500KB: async (c) => {\n\t\t\tconst targetBytes = 500 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\tlargeTxInsert1MB: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\t// 1 MiB total, 4096 × 256 B rows. Max NAPI crossings.\n\t\tlargeTxInsert1MBTinyRows: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 256;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\t// 1 MiB total, 256 × 4 KiB rows. Same shape as largeTxInsert1MB; kept as a sanity duplicate.\n\t\tlargeTxInsert1MBMediumRows: async (c) => {\n\t\t\tconst targetBytes = 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\t// 1 MiB total, 1 × 1 MiB row. One NAPI crossing, exercises SQLite overflow-page chain.\n\t\tlargeTxInsert1MBOneRow: async (c) => {\n\t\t\tconst rowSize = 1024 * 1024;\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\trowSize,\n\t\t\t);\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 1, bytes: rowSize };\n\t\t},\n\n\t\tlargeTxInsert5MB: async (c) => {\n\t\t\tconst targetBytes = 5 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\tlargeTxInsert10MB: async (c) => {\n\t\t\tconst targetBytes = 10 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\tlargeTxInsert50MB: async (c) => {\n\t\t\tconst targetBytes = 50 * 1024 * 1024;\n\t\t\tconst rowSize = 4 * 1024;\n\t\t\tconst rowCount = Math.ceil(targetBytes / rowSize);\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS large_tx (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < rowCount; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO large_tx (payload) VALUES (randomblob(?))\",\n\t\t\t\t\trowSize,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: rowCount,\n\t\t\t\tbytes: rowCount * rowSize,\n\t\t\t};\n\t\t},\n\n\t\t// Stress test: insert 1000 rows, delete them all, repeat 10 times.\n\t\t// Tests freelist reuse and space reclamation patterns.\n\t\tchurnInsertDelete: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS churn (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\tconst t0 = performance.now();\n\t\t\tconst cycles = 10;\n\t\t\tconst perCycle = 1000;\n\t\t\tfor (let cycle = 0; cycle < cycles; cycle++) {\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tfor (let i = 0; i < perCycle; i++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"INSERT INTO churn (payload) VALUES (randomblob(1024))\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait c.db.execute(\"DELETE FROM churn\");\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: cycles * perCycle,\n\t\t\t\tcycles,\n\t\t\t};\n\t\t},\n\n\t\t// Interleave inserts, updates, deletes in same transaction. Tests how\n\t\t// the VFS handles mixed page dirtying patterns.\n\t\tmixedOltpLarge: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS mixed_oltp (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tvalue INTEGER NOT NULL,\n\t\t\t\tdata BLOB NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM mixed_oltp\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))\",\n\t\t\t\t\ti,\n\t\t\t\t\ti * 2,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO mixed_oltp (id, value, data) VALUES (?, ?, randomblob(1024))\",\n\t\t\t\t\t500 + i,\n\t\t\t\t\ti * 3,\n\t\t\t\t);\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"UPDATE mixed_oltp SET value = value + 1 WHERE id = ?\",\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t\tif (i % 5 === 0) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"DELETE FROM mixed_oltp WHERE id = ?\",\n\t\t\t\t\t\ti - 50 >= 0 ? i - 50 : i,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 500 * 2 + 100 };\n\t\t},\n\n\t\t// Growing aggregation: insert then SELECT SUM after each batch.\n\t\t// Tests cache invalidation and read-after-write patterns.\n\t\tgrowingAggregation: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS agg_test (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM agg_test\");\n\t\t\tconst t0 = performance.now();\n\t\t\tconst batches = 20;\n\t\t\tconst perBatch = 100;\n\t\t\tlet lastSum = 0;\n\t\t\tfor (let batch = 0; batch < batches; batch++) {\n\t\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\t\tfor (let i = 0; i < perBatch; i++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t\"INSERT INTO agg_test (value) VALUES (?)\",\n\t\t\t\t\t\tbatch * perBatch + i,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\t\tconst rows = (await c.db.execute(\n\t\t\t\t\t\"SELECT SUM(value) AS s FROM agg_test\",\n\t\t\t\t)) as Array<{ s: number }>;\n\t\t\t\tlastSum = rows[0]?.s ?? 0;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tms: performance.now() - t0,\n\t\t\t\tops: batches * perBatch,\n\t\t\t\tbatches,\n\t\t\t\tlastSum,\n\t\t\t};\n\t\t},\n\n\t\t// Create index on already-populated table. Tests large rewrite patterns.\n\t\tindexCreationOnLargeTable: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS idx_test (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tkey TEXT NOT NULL,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DROP INDEX IF EXISTS idx_test_key\");\n\t\t\tawait c.db.execute(\"DELETE FROM idx_test\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 10000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO idx_test (key, value) VALUES (?, ?)\",\n\t\t\t\t\t`key-${i % 1000}-${i}`,\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"CREATE INDEX idx_test_key ON idx_test(key)\");\n\t\t\treturn { ms: performance.now() - t0, ops: 10000 };\n\t\t},\n\n\t\t// Update 1000 different rows in separate UPDATEs in one transaction.\n\t\t// Stresses B-tree navigation and page dirtying.\n\t\tbulkUpdate1000Rows: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS bulk_update (\n\t\t\t\tid INTEGER PRIMARY KEY,\n\t\t\t\tvalue INTEGER NOT NULL\n\t\t\t)`);\n\t\t\tawait c.db.execute(\"DELETE FROM bulk_update\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO bulk_update (id, value) VALUES (?, ?)\",\n\t\t\t\t\ti,\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"UPDATE bulk_update SET value = value + 1 WHERE id = ?\",\n\t\t\t\t\ti,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 1000 };\n\t\t},\n\n\t\t// Delete everything then re-insert. Tests truncate+regrow cycle.\n\t\ttruncateAndRegrow: async (c) => {\n\t\t\tawait c.db.execute(`CREATE TABLE IF NOT EXISTS regrow (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tpayload BLOB NOT NULL\n\t\t\t)`);\n\t\t\t// Seed\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO regrow (payload) VALUES (randomblob(1024))\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"DELETE FROM regrow\");\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 500; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\"INSERT INTO regrow (payload) VALUES (randomblob(1024))\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 500 };\n\t\t},\n\n\t\t// Many small tables vs one large. Tests schema page growth.\n\t\tmanySmallTables: async (c) => {\n\t\t\tconst t0 = performance.now();\n\t\t\tawait c.db.execute(\"BEGIN\");\n\t\t\tfor (let i = 0; i < 50; i++) {\n\t\t\t\tawait c.db.execute(\n\t\t\t\t\t`CREATE TABLE IF NOT EXISTS small_t_${i} (id INTEGER PRIMARY KEY, value INTEGER)`,\n\t\t\t\t);\n\t\t\t\tfor (let j = 0; j < 10; j++) {\n\t\t\t\t\tawait c.db.execute(\n\t\t\t\t\t\t`INSERT INTO small_t_${i} (id, value) VALUES (?, ?)`,\n\t\t\t\t\t\tj,\n\t\t\t\t\t\ti * j,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait c.db.execute(\"COMMIT\");\n\t\t\treturn { ms: performance.now() - t0, ops: 50 * 10, tables: 50 };\n\t\t},\n\t},\n});\n","import { actor } from \"rivetkit\";\nimport { db } from \"rivetkit/db\";\n\nexport const testSqliteLoad = actor({\n\tdb: db({\n\t\tonMigrate: async (db) => {\n\t\t\t// Migration 1: schema version tracking\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS schema_version (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tversion INTEGER NOT NULL DEFAULT 50\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, 50)\",\n\t\t\t);\n\n\t\t\t// Migration 2\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS users (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\temail TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 3\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS products (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tprice REAL NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 4\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS orders (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttotal REAL NOT NULL DEFAULT 0,\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 5\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS order_items (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tprice REAL NOT NULL,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 6\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS categories (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL UNIQUE,\n\t\t\t\t\tdescription TEXT\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 7\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_categories (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tcategory_id INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (product_id, category_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (category_id) REFERENCES categories(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 8\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS reviews (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\trating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),\n\t\t\t\t\tcomment TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 9\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS addresses (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tstreet TEXT NOT NULL,\n\t\t\t\t\tcity TEXT NOT NULL,\n\t\t\t\t\tstate TEXT,\n\t\t\t\t\tzip TEXT,\n\t\t\t\t\tcountry TEXT NOT NULL DEFAULT 'US',\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 10\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS payments (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\tamount REAL NOT NULL,\n\t\t\t\t\tmethod TEXT NOT NULL DEFAULT 'card',\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\t\tprocessed_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 11\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS inventory (\n\t\t\t\t\tproduct_id INTEGER PRIMARY KEY,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\treserved INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tlast_restocked_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 12\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS coupons (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tcode TEXT NOT NULL UNIQUE,\n\t\t\t\t\tdiscount_percent REAL NOT NULL,\n\t\t\t\t\tmax_uses INTEGER,\n\t\t\t\t\tused_count INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\texpires_at INTEGER\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 13\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS shipping (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\taddress_id INTEGER NOT NULL,\n\t\t\t\t\tcarrier TEXT,\n\t\t\t\t\ttracking_number TEXT,\n\t\t\t\t\tshipped_at INTEGER,\n\t\t\t\t\tdelivered_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id),\n\t\t\t\t\tFOREIGN KEY (address_id) REFERENCES addresses(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 14\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS tags (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL UNIQUE\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 15\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_tags (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\ttag_id INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (product_id, tag_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (tag_id) REFERENCES tags(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 16\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS wishlists (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tname TEXT NOT NULL DEFAULT 'Default',\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 17\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS wishlist_items (\n\t\t\t\t\twishlist_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tadded_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (wishlist_id, product_id),\n\t\t\t\t\tFOREIGN KEY (wishlist_id) REFERENCES wishlists(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 18\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS notifications (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttype TEXT NOT NULL,\n\t\t\t\t\tmessage TEXT NOT NULL,\n\t\t\t\t\tread INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 19\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS audit_log (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tentity_type TEXT NOT NULL,\n\t\t\t\t\tentity_id INTEGER NOT NULL,\n\t\t\t\t\taction TEXT NOT NULL,\n\t\t\t\t\tdetails TEXT,\n\t\t\t\t\tperformed_at INTEGER NOT NULL\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 20\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS sessions (\n\t\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\ttoken TEXT NOT NULL UNIQUE,\n\t\t\t\t\texpires_at INTEGER NOT NULL,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 21\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)\",\n\t\t\t);\n\n\t\t\t// Migration 22\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 23\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)\",\n\t\t\t);\n\n\t\t\t// Migration 24\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_reviews_product ON reviews(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 25\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_reviews_user ON reviews(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 26\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 27\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id)\",\n\t\t\t);\n\n\t\t\t// Migration 28\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 29\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)\",\n\t\t\t);\n\n\t\t\t// Migration 30\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_inventory_quantity ON inventory(quantity)\",\n\t\t\t);\n\n\t\t\t// Migration 31\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS returns (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\torder_id INTEGER NOT NULL,\n\t\t\t\t\treason TEXT NOT NULL,\n\t\t\t\t\tstatus TEXT NOT NULL DEFAULT 'requested',\n\t\t\t\t\trequested_at INTEGER NOT NULL,\n\t\t\t\t\tprocessed_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (order_id) REFERENCES orders(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 32\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS return_items (\n\t\t\t\t\treturn_id INTEGER NOT NULL,\n\t\t\t\t\torder_item_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (return_id, order_item_id),\n\t\t\t\t\tFOREIGN KEY (return_id) REFERENCES returns(id),\n\t\t\t\t\tFOREIGN KEY (order_item_id) REFERENCES order_items(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 33\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS suppliers (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tname TEXT NOT NULL,\n\t\t\t\t\tcontact_email TEXT,\n\t\t\t\t\tcountry TEXT\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 34\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_suppliers (\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tsupplier_id INTEGER NOT NULL,\n\t\t\t\t\tcost REAL NOT NULL,\n\t\t\t\t\tlead_time_days INTEGER,\n\t\t\t\t\tPRIMARY KEY (product_id, supplier_id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id),\n\t\t\t\t\tFOREIGN KEY (supplier_id) REFERENCES suppliers(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 35\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS price_history (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\told_price REAL NOT NULL,\n\t\t\t\t\tnew_price REAL NOT NULL,\n\t\t\t\t\tchanged_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 36\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS user_preferences (\n\t\t\t\t\tuser_id INTEGER PRIMARY KEY,\n\t\t\t\t\ttheme TEXT NOT NULL DEFAULT 'dark',\n\t\t\t\t\tlanguage TEXT NOT NULL DEFAULT 'en',\n\t\t\t\t\tnotifications_enabled INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 37\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS cart (\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tquantity INTEGER NOT NULL DEFAULT 1,\n\t\t\t\t\tadded_at INTEGER NOT NULL,\n\t\t\t\t\tPRIMARY KEY (user_id, product_id),\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id),\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 38\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS saved_searches (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tuser_id INTEGER NOT NULL,\n\t\t\t\t\tquery TEXT NOT NULL,\n\t\t\t\t\tfilters TEXT,\n\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\tFOREIGN KEY (user_id) REFERENCES users(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 39\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS product_images (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\turl TEXT NOT NULL,\n\t\t\t\t\talt_text TEXT,\n\t\t\t\t\tsort_order INTEGER NOT NULL DEFAULT 0,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 40\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS discounts (\n\t\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\t\tproduct_id INTEGER NOT NULL,\n\t\t\t\t\tdiscount_percent REAL NOT NULL,\n\t\t\t\t\tstarts_at INTEGER NOT NULL,\n\t\t\t\t\tends_at INTEGER,\n\t\t\t\t\tFOREIGN KEY (product_id) REFERENCES products(id)\n\t\t\t\t)\n\t\t\t`);\n\n\t\t\t// Migration 41\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 42\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_order_items_product ON order_items(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 43\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 44\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_shipping_order ON shipping(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 45\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_returns_order ON returns(order_id)\",\n\t\t\t);\n\n\t\t\t// Migration 46\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_price_history_product ON price_history(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 47\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_product_images_product ON product_images(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 48\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_discounts_product ON discounts(product_id)\",\n\t\t\t);\n\n\t\t\t// Migration 49\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 50\n\t\t\tawait db.execute(\n\t\t\t\t\"CREATE INDEX IF NOT EXISTS idx_saved_searches_user ON saved_searches(user_id)\",\n\t\t\t);\n\n\t\t\t// Migration 51: counter for benchmarking\n\t\t\tawait db.execute(`\n\t\t\t\tCREATE TABLE IF NOT EXISTS counter (\n\t\t\t\t\tid INTEGER PRIMARY KEY CHECK (id = 1),\n\t\t\t\t\tvalue INTEGER NOT NULL DEFAULT 0\n\t\t\t\t)\n\t\t\t`);\n\t\t\tawait db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)\",\n\t\t\t);\n\t\t},\n\t}),\n\tactions: {\n\t\tincrement: async (c, amount: number = 1) => {\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE counter SET value = value + ? WHERE id = 1\",\n\t\t\t\tamount,\n\t\t\t);\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\tgetCount: async (c) => {\n\t\t\tconst rows = await c.db.execute(\n\t\t\t\t\"SELECT value FROM counter WHERE id = 1\",\n\t\t\t);\n\t\t\treturn (rows[0] as { value: number }).value;\n\t\t},\n\t\treset: async (c) => {\n\t\t\tawait c.db.execute(\"UPDATE counter SET value = 0 WHERE id = 1\");\n\t\t\treturn 0;\n\t\t},\n\t\trunLoadTest: async (c) => {\n\t\t\tconst now = Date.now();\n\t\t\tconst results: string[] = [];\n\n\t\t\t// Query 1: Insert a user\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\"Load Test User\",\n\t\t\t\t`load-${now}@test.com`,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted user\");\n\n\t\t\t// Query 2: Get the user back\n\t\t\tconst users = await c.db.execute(\n\t\t\t\t\"SELECT * FROM users WHERE email = ?\",\n\t\t\t\t`load-${now}@test.com`,\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`fetched user: ${(users[0] as { name: string }).name}`,\n\t\t\t);\n\t\t\tconst userId = (users[0] as { id: number }).id;\n\n\t\t\t// Query 3: Insert a product\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO products (name, price, created_at) VALUES (?, ?, ?)\",\n\t\t\t\t\"Test Widget\",\n\t\t\t\t29.99,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted product\");\n\n\t\t\t// Query 4: Get products\n\t\t\tconst products = await c.db.execute(\n\t\t\t\t\"SELECT * FROM products LIMIT 10\",\n\t\t\t);\n\t\t\tresults.push(`fetched ${(products as unknown[]).length} products`);\n\t\t\tconst productId = (products[0] as { id: number }).id;\n\n\t\t\t// Query 5: Insert a category\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT OR IGNORE INTO categories (name, description) VALUES (?, ?)\",\n\t\t\t\t`test-cat-${now}`,\n\t\t\t\t\"A test category\",\n\t\t\t);\n\t\t\tresults.push(\"inserted category\");\n\n\t\t\t// Query 6: Get categories\n\t\t\tconst categories = await c.db.execute(\"SELECT * FROM categories\");\n\t\t\tresults.push(\n\t\t\t\t`fetched ${(categories as unknown[]).length} categories`,\n\t\t\t);\n\n\t\t\t// Query 7: Insert an order\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO orders (user_id, total, status, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t29.99,\n\t\t\t\t\"pending\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted order\");\n\n\t\t\t// Query 8: Get orders for user\n\t\t\tconst orders = await c.db.execute(\n\t\t\t\t\"SELECT * FROM orders WHERE user_id = ?\",\n\t\t\t\tuserId,\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`fetched ${(orders as unknown[]).length} orders for user`,\n\t\t\t);\n\t\t\tconst orderId = (orders[0] as { id: number }).id;\n\n\t\t\t// Query 9: Insert order item\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)\",\n\t\t\t\torderId,\n\t\t\t\tproductId,\n\t\t\t\t2,\n\t\t\t\t29.99,\n\t\t\t);\n\t\t\tresults.push(\"inserted order item\");\n\n\t\t\t// Query 10: Insert inventory\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT OR REPLACE INTO inventory (product_id, quantity, reserved, last_restocked_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tproductId,\n\t\t\t\t100,\n\t\t\t\t2,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted inventory\");\n\n\t\t\t// Query 11: Insert a review\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO reviews (user_id, product_id, rating, comment, created_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\tproductId,\n\t\t\t\t5,\n\t\t\t\t\"Great product!\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted review\");\n\n\t\t\t// Query 12: Get reviews with join\n\t\t\tconst reviews = await c.db.execute(\n\t\t\t\t\"SELECT r.*, u.name as reviewer FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.product_id = ?\",\n\t\t\t\tproductId,\n\t\t\t);\n\t\t\tresults.push(`fetched ${(reviews as unknown[]).length} reviews`);\n\n\t\t\t// Query 13: Insert notification\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO notifications (user_id, type, message, created_at) VALUES (?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t\"order\",\n\t\t\t\t\"Your order has been placed\",\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted notification\");\n\n\t\t\t// Query 14: Insert audit log entry\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO audit_log (entity_type, entity_id, action, details, performed_at) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\"order\",\n\t\t\t\torderId,\n\t\t\t\t\"created\",\n\t\t\t\t`Order created by user ${userId}`,\n\t\t\t\tnow,\n\t\t\t);\n\t\t\tresults.push(\"inserted audit log\");\n\n\t\t\t// Query 15: Aggregate query on orders\n\t\t\tconst orderStats = await c.db.execute(\n\t\t\t\t\"SELECT status, COUNT(*) as count, SUM(total) as total_value FROM orders GROUP BY status\",\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`order stats: ${(orderStats as unknown[]).length} statuses`,\n\t\t\t);\n\n\t\t\t// Query 16: Insert address\n\t\t\tawait c.db.execute(\n\t\t\t\t\"INSERT INTO addresses (user_id, street, city, state, zip, country) VALUES (?, ?, ?, ?, ?, ?)\",\n\t\t\t\tuserId,\n\t\t\t\t\"123 Test St\",\n\t\t\t\t\"Testville\",\n\t\t\t\t\"CA\",\n\t\t\t\t\"90210\",\n\t\t\t\t\"US\",\n\t\t\t);\n\t\t\tresults.push(\"inserted address\");\n\n\t\t\t// Query 17: Complex join query\n\t\t\tconst orderDetails = await c.db.execute(`\n\t\t\t\tSELECT o.id, o.status, o.total, u.name as customer, COUNT(oi.id) as item_count\n\t\t\t\tFROM orders o\n\t\t\t\tJOIN users u ON o.user_id = u.id\n\t\t\t\tLEFT JOIN order_items oi ON oi.order_id = o.id\n\t\t\t\tGROUP BY o.id\n\t\t\t\tLIMIT 10\n\t\t\t`);\n\t\t\tresults.push(\n\t\t\t\t`fetched ${(orderDetails as unknown[]).length} order details`,\n\t\t\t);\n\n\t\t\t// Query 18: Update order status\n\t\t\tawait c.db.execute(\n\t\t\t\t\"UPDATE orders SET status = ? WHERE id = ?\",\n\t\t\t\t\"completed\",\n\t\t\t\torderId,\n\t\t\t);\n\t\t\tresults.push(\"updated order status\");\n\n\t\t\t// Query 19: Get schema version\n\t\t\tconst version = await c.db.execute(\n\t\t\t\t\"SELECT version FROM schema_version WHERE id = 1\",\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t`schema version: ${(version[0] as { version: number }).version}`,\n\t\t\t);\n\n\t\t\t// Query 20: Count all tables\n\t\t\tconst tableCounts = await c.db.execute(`\n\t\t\t\tSELECT 'users' as tbl, COUNT(*) as cnt FROM users\n\t\t\t\tUNION ALL SELECT 'products', COUNT(*) FROM products\n\t\t\t\tUNION ALL SELECT 'orders', COUNT(*) FROM orders\n\t\t\t\tUNION ALL SELECT 'reviews', COUNT(*) FROM reviews\n\t\t\t\tUNION ALL SELECT 'categories', COUNT(*) FROM categories\n\t\t\t`);\n\t\t\tresults.push(\n\t\t\t\t`table counts: ${(tableCounts as unknown[]).length} tables checked`,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tqueriesRun: 20,\n\t\t\t\tresults,\n\t\t\t};\n\t\t},\n\t},\n});\n","// APPROVAL REQUEST (Queue Wait Demo)\n// Demonstrates: Queue waits with timeout for approval workflows\n// One actor per approval request - actor key is the request ID\n\nimport { actor, event, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type RequestStatus = \"pending\" | \"approved\" | \"rejected\" | \"timeout\";\n\nexport type ApprovalRequest = {\n\tid: string;\n\ttitle: string;\n\tdescription: string;\n\tstatus: RequestStatus;\n\tcreatedAt: number;\n\tdecidedAt?: number;\n\tdecidedBy?: string;\n\tdeciding?: boolean; // True when a decision is being processed\n};\n\nconst QUEUE_DECISION = \"decision\" as const;\n\nconst APPROVAL_TIMEOUT_MS = 30000;\n\nexport type ApprovalRequestInput = {\n\ttitle?: string;\n\tdescription?: string;\n};\n\ntype ApprovalDecision = {\n\tapproved: boolean;\n\tapprover: string;\n};\n\nexport const approval = actor({\n\tcreateState: (c, input?: ApprovalRequestInput): ApprovalRequest => ({\n\t\tid: c.key[0] as string,\n\t\ttitle: input?.title ?? \"Untitled Request\",\n\t\tdescription: input?.description ?? \"\",\n\t\tstatus: \"pending\",\n\t\tcreatedAt: Date.now(),\n\t}),\n\tqueues: {\n\t\tdecision: queue(),\n\t},\n\tevents: {\n\t\trequestUpdated: event(),\n\t\trequestCreated: event(),\n\t},\n\n\tactions: {\n\t\tgetRequest: (c): ApprovalRequest => c.state,\n\n\t\tapprove: async (c, approver: string) => {\n\t\t\tif (c.state.status !== \"pending\") return;\n\t\t\tc.state.deciding = true;\n\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\tawait c.queue.send(QUEUE_DECISION, { approved: true, approver });\n\t\t},\n\n\t\treject: async (c, approver: string) => {\n\t\t\tif (c.state.status !== \"pending\") return;\n\t\t\tc.state.deciding = true;\n\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\tawait c.queue.send(QUEUE_DECISION, { approved: false, approver });\n\t\t},\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"approval-loop\", async (loopCtx) => {\n\t\t\tawait loopCtx.step(\"init-request\", async (c) => {\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"waiting for approval decision\",\n\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\ttitle: c.state.title,\n\t\t\t\t});\n\t\t\t\tc.broadcast(\"requestCreated\", c.state);\n\t\t\t});\n\n\t\t\tconst [decisionMessage] = await loopCtx.queue.nextBatch(\n\t\t\t\t\"wait-decision\",\n\t\t\t\t{\n\t\t\t\t\tnames: [QUEUE_DECISION],\n\t\t\t\t\ttimeout: APPROVAL_TIMEOUT_MS,\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst decision = decisionMessage?.body ?? null;\n\n\t\t\tawait loopCtx.step(\"update-status\", async (c) => {\n\t\t\t\tc.state.deciding = false;\n\t\t\t\tif (decision === null) {\n\t\t\t\t\tc.state.status = \"timeout\";\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"request timed out\",\n\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t});\n\t\t\t\t} else if (decision.approved) {\n\t\t\t\t\tc.state.status = \"approved\";\n\t\t\t\t\tc.state.decidedBy = decision.approver;\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"request approved\",\n\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t\tapprover: decision.approver,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tc.state.status = \"rejected\";\n\t\t\t\t\tc.state.decidedBy = decision.approver;\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"request rejected\",\n\t\t\t\t\t\trequestId: c.state.id,\n\t\t\t\t\t\tapprover: decision.approver,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tc.state.decidedAt = Date.now();\n\t\t\t\tc.broadcast(\"requestUpdated\", c.state);\n\t\t\t});\n\n\t\t\treturn Loop.break(undefined);\n\t\t});\n\t}),\n});\n","// BATCH PROCESSOR (Loops Demo)\n// Demonstrates: Loop with persistent state (cursor) for batch processing\n// One actor per batch job - actor key is the job ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type BatchInfo = {\n\tid: number;\n\tcount: number;\n\tprocessedAt: number;\n};\n\nexport type BatchJob = {\n\tid: string;\n\ttotalItems: number;\n\tbatchSize: number;\n\tstatus: \"running\" | \"stopped\" | \"completed\";\n\tprocessedTotal: number;\n\tcurrentBatch: number;\n\tbatches: BatchInfo[];\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\nfunction fetchBatch(\n\tcursor: number,\n\tbatchSize: number,\n\ttotalItems: number,\n): { items: number[]; hasMore: boolean } {\n\tconst start = cursor * batchSize;\n\tconst end = Math.min(start + batchSize, totalItems);\n\tconst items = [];\n\tfor (let i = start; i < end; i++) {\n\t\titems.push(i);\n\t}\n\treturn {\n\t\titems,\n\t\thasMore: end < totalItems,\n\t};\n}\n\nexport type BatchJobInput = {\n\ttotalItems?: number;\n\tbatchSize?: number;\n};\n\nexport const batch = actor({\n\tcreateState: (c, input?: BatchJobInput): BatchJob => ({\n\t\tid: c.key[0] as string,\n\t\ttotalItems: input?.totalItems ?? 50,\n\t\tbatchSize: input?.batchSize ?? 5,\n\t\tstatus: \"running\",\n\t\tprocessedTotal: 0,\n\t\tcurrentBatch: 0,\n\t\tbatches: [],\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\tbatchProcessed: event(),\n\t\tstateChanged: event(),\n\t\tprocessingComplete: event<{\n\t\t\ttotalBatches: number;\n\t\t\ttotalItems: number;\n\t\t}>(),\n\t},\n\n\tactions: {\n\t\tgetJob: (c): BatchJob => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop({\n\t\t\tname: \"batch-loop\",\n\t\t\tstate: { cursor: 0 },\n\t\t\trun: async (batchCtx, loopState: { cursor: number }) => {\n\t\t\t\tconst batch = await batchCtx.step(\"fetch-batch\", async (c) => {\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"processing batch\",\n\t\t\t\t\t\tjobId: c.state.id,\n\t\t\t\t\t\tcursor: loopState.cursor,\n\t\t\t\t\t});\n\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\tsetTimeout(r, 200 + Math.random() * 300),\n\t\t\t\t\t);\n\t\t\t\t\treturn fetchBatch(\n\t\t\t\t\t\tloopState.cursor,\n\t\t\t\t\t\tc.state.batchSize,\n\t\t\t\t\t\tc.state.totalItems,\n\t\t\t\t\t);\n\t\t\t\t});\n\n\t\t\t\tawait batchCtx.step(\"process-batch\", async (c) => {\n\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\tsetTimeout(r, 300 + Math.random() * 500),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst batchInfo: BatchInfo = {\n\t\t\t\t\t\tid: loopState.cursor,\n\t\t\t\t\t\tcount: batch.items.length,\n\t\t\t\t\t\tprocessedAt: Date.now(),\n\t\t\t\t\t};\n\n\t\t\t\t\tc.state.currentBatch = loopState.cursor;\n\t\t\t\t\tc.state.processedTotal += batch.items.length;\n\t\t\t\t\tc.state.batches.push(batchInfo);\n\n\t\t\t\t\tc.broadcast(\"batchProcessed\", batchInfo);\n\t\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"batch processed\",\n\t\t\t\t\t\tjobId: c.state.id,\n\t\t\t\t\t\tcursor: loopState.cursor,\n\t\t\t\t\t\tcount: batch.items.length,\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tif (!batch.hasMore) {\n\t\t\t\t\tawait batchCtx.step(\"mark-complete\", async (c) => {\n\t\t\t\t\t\tc.state.status = \"completed\";\n\t\t\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\t\t\tc.broadcast(\"processingComplete\", {\n\t\t\t\t\t\t\ttotalBatches: loopState.cursor + 1,\n\t\t\t\t\t\t\ttotalItems: c.state.processedTotal,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\treturn Loop.break(loopState.cursor + 1);\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ cursor: loopState.cursor + 1 });\n\t\t\t},\n\t\t});\n\t}),\n});\n","// DASHBOARD (Join Demo)\n// Demonstrates: Parallel data fetching with join (wait-all)\n\nimport { actor, event, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type UserStats = {\n\tcount: number;\n\tactiveToday: number;\n\tnewThisWeek: number;\n};\n\nexport type OrderStats = {\n\tcount: number;\n\trevenue: number;\n\tavgOrderValue: number;\n};\n\nexport type MetricsStats = {\n\tpageViews: number;\n\tsessions: number;\n\tbounceRate: number;\n};\n\nexport type DashboardData = {\n\tusers: UserStats;\n\torders: OrderStats;\n\tmetrics: MetricsStats;\n\tfetchedAt: number;\n};\n\nexport type BranchStatus = \"pending\" | \"running\" | \"completed\" | \"failed\";\n\nexport type DashboardState = {\n\tdata: DashboardData | null;\n\tloading: boolean;\n\tbranches: {\n\t\tusers: BranchStatus;\n\t\torders: BranchStatus;\n\t\tmetrics: BranchStatus;\n\t};\n\tlastRefresh: number | null;\n};\n\nconst QUEUE_REFRESH = \"refresh\";\ntype RefreshMessage = Record;\n\nasync function fetchUserStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 800 + Math.random() * 1200));\n\treturn {\n\t\tcount: Math.floor(1000 + Math.random() * 500),\n\t\tactiveToday: Math.floor(100 + Math.random() * 200),\n\t\tnewThisWeek: Math.floor(20 + Math.random() * 80),\n\t};\n}\n\nasync function fetchOrderStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 600 + Math.random() * 1000));\n\tconst count = Math.floor(50 + Math.random() * 150);\n\tconst revenue = Math.floor(5000 + Math.random() * 15000);\n\treturn {\n\t\tcount,\n\t\trevenue,\n\t\tavgOrderValue: Math.round(revenue / count),\n\t};\n}\n\nasync function fetchMetricsStats(): Promise {\n\tawait new Promise((r) => setTimeout(r, 400 + Math.random() * 800));\n\treturn {\n\t\tpageViews: Math.floor(10000 + Math.random() * 50000),\n\t\tsessions: Math.floor(2000 + Math.random() * 8000),\n\t\tbounceRate: Math.round(30 + Math.random() * 40),\n\t};\n}\n\nexport const dashboard = actor({\n\tstate: {\n\t\tdata: null as DashboardData | null,\n\t\tloading: false,\n\t\tbranches: {\n\t\t\tusers: \"pending\" as BranchStatus,\n\t\t\torders: \"pending\" as BranchStatus,\n\t\t\tmetrics: \"pending\" as BranchStatus,\n\t\t},\n\t\tlastRefresh: null as number | null,\n\t},\n\tqueues: {\n\t\t[QUEUE_REFRESH]: queue(),\n\t},\n\tevents: {\n\t\tstateChanged: event(),\n\t\trefreshComplete: event(),\n\t},\n\n\tactions: {\n\t\trefresh: async (c) => {\n\t\t\tif (!c.state.loading) {\n\t\t\t\tc.state.loading = true;\n\t\t\t\tc.state.branches = {\n\t\t\t\t\tusers: \"pending\",\n\t\t\t\t\torders: \"pending\",\n\t\t\t\t\tmetrics: \"pending\",\n\t\t\t\t};\n\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\tawait c.queue.send(QUEUE_REFRESH, {});\n\t\t\t}\n\t\t},\n\n\t\tgetState: (c): DashboardState => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"refresh-loop\", async (loopCtx) => {\n\t\t\tawait loopCtx.queue.next(\"wait-refresh\", {\n\t\t\t\tnames: [QUEUE_REFRESH],\n\t\t\t});\n\n\t\t\tctx.log.info({ msg: \"starting dashboard refresh\" });\n\n\t\t\tconst results = await loopCtx.join(\"fetch-all\", {\n\t\t\t\tusers: {\n\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.users = \"running\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst data = await branchCtx.step(\n\t\t\t\t\t\t\t\"fetch-users\",\n\t\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\t\treturn await fetchUserStats();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.users = \"completed\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\torders: {\n\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.orders = \"running\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst data = await branchCtx.step(\n\t\t\t\t\t\t\t\"fetch-orders\",\n\t\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\t\treturn await fetchOrderStats();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.orders = \"completed\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tmetrics: {\n\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\tawait branchCtx.step(\"mark-running\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.metrics = \"running\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst data = await branchCtx.step(\n\t\t\t\t\t\t\t\"fetch-metrics\",\n\t\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\t\treturn await fetchMetricsStats();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tawait branchCtx.step(\"mark-complete\", async (bc) => {\n\t\t\t\t\t\t\tbc.state.branches.metrics = \"completed\";\n\t\t\t\t\t\t\tbc.broadcast(\"stateChanged\", bc.state);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tawait loopCtx.step(\"save-data\", async (c) => {\n\t\t\t\tc.state.data = {\n\t\t\t\t\tusers: results.users,\n\t\t\t\t\torders: results.orders,\n\t\t\t\t\tmetrics: results.metrics,\n\t\t\t\t\tfetchedAt: Date.now(),\n\t\t\t\t};\n\t\t\t\tc.state.loading = false;\n\t\t\t\tc.state.lastRefresh = Date.now();\n\t\t\t\tc.broadcast(\"stateChanged\", c.state);\n\t\t\t\tc.broadcast(\"refreshComplete\", c.state.data);\n\t\t\t});\n\n\t\t\tctx.log.info({ msg: \"dashboard refresh complete\" });\n\n\t\t\treturn Loop.continue(undefined);\n\t\t});\n\t}),\n});\n","import { actor, queue } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nfunction delay(ms: number): Promise {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type WorkflowHistorySimpleState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"completed\";\n\tlastStep?: string;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n\toutput?: { success: boolean; processedItems: number };\n};\n\nexport const workflowHistorySimple = actor({\n\tcreateState: (c): WorkflowHistorySimpleState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistorySimpleState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\tc.state.lastStep = \"start\";\n\t\t\tc.state.startedAt = Date.now();\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait delay(700);\n\n\t\tawait ctx.step(\"process\", async (c) => {\n\t\t\tc.state.lastStep = \"process\";\n\t\t\treturn { processed: true, items: 5 };\n\t\t});\n\n\t\tawait delay(2200);\n\n\t\tawait ctx.step(\"validate\", async (c) => {\n\t\t\tc.state.lastStep = \"validate\";\n\t\t\treturn { valid: true };\n\t\t});\n\n\t\tawait delay(600);\n\n\t\tawait ctx.step(\"complete\", async (c) => {\n\t\t\tc.state.lastStep = \"complete\";\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\tc.state.output = { success: true, processedItems: 3 };\n\t\t\treturn { success: true };\n\t\t});\n\t}),\n});\n\nconst LOOP_ITEMS = [\"A\", \"B\", \"C\"];\n\nexport type WorkflowHistoryLoopState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tprocessed: number;\n\tbatches: Array<{ index: number; item: string }>;\n\tcompletedAt?: number;\n};\n\nexport const workflowHistoryLoop = actor({\n\tcreateState: (c): WorkflowHistoryLoopState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tprocessed: 0,\n\t\tbatches: [],\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryLoopState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { batchSize: LOOP_ITEMS.length };\n\t\t});\n\n\t\tawait ctx.loop({\n\t\t\tname: \"batch-loop\",\n\t\t\tstate: { index: 0 },\n\t\t\tcommitInterval: 1,\n\t\t\thistoryEvery: 1,\n\t\t\thistoryKeep: LOOP_ITEMS.length,\n\t\t\trun: async (loopCtx, loopState: { index: number }) => {\n\t\t\t\tconst item = LOOP_ITEMS[loopState.index];\n\n\t\t\t\tawait loopCtx.step(`process-${loopState.index}`, async (c) => {\n\t\t\t\t\tc.state.processed += 1;\n\t\t\t\t\tc.state.batches.push({ index: loopState.index, item });\n\t\t\t\t\treturn { item, status: \"done\" };\n\t\t\t\t});\n\n\t\t\t\tif (loopState.index >= LOOP_ITEMS.length - 1) {\n\t\t\t\t\treturn Loop.break({ processed: LOOP_ITEMS.length });\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ index: loopState.index + 1 });\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.step(\"finalize\", async (c) => {\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn { allProcessed: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryJoinState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"completed\";\n\tresult?: {\n\t\tapi: string;\n\t\trows: number;\n\t\tcacheHit: boolean;\n\t};\n};\n\nexport const workflowHistoryJoin = actor({\n\tcreateState: (c): WorkflowHistoryJoinState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryJoinState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { ready: true };\n\t\t});\n\n\t\tconst results = await ctx.join(\"parallel-tasks\", {\n\t\t\t\"fetch-api\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-a\", async (branchCtx) => {\n\t\t\t\t\t\tawait delay(120);\n\t\t\t\t\t\treturn { fetched: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { data: \"api-response\" };\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"query-db\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-b\", async (branchCtx) => {\n\t\t\t\t\t\tawait delay(200);\n\t\t\t\t\t\treturn { queried: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { rows: 42 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"check-cache\": {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.step(\"task-c\", async (branchCtx) => {\n\t\t\t\t\t\tawait delay(60);\n\t\t\t\t\t\treturn { checked: true };\n\t\t\t\t\t});\n\t\t\t\t\treturn { hit: true };\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.step(\"merge-results\", async (c) => {\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.result = {\n\t\t\t\tapi: results[\"fetch-api\"].data,\n\t\t\t\trows: results[\"query-db\"].rows,\n\t\t\t\tcacheHit: results[\"check-cache\"].hit,\n\t\t\t};\n\t\t\treturn { merged: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryRaceState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\twinner?: string;\n\tresult?: string;\n};\n\nexport const workflowHistoryRace = actor({\n\tcreateState: (c): WorkflowHistoryRaceState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryRaceState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"begin\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { started: true };\n\t\t});\n\n\t\tconst { winner, value } = await ctx.race<{\n\t\t\tprovider: string;\n\t\t\tlatency: number;\n\t\t}>(\"race-providers\", [\n\t\t\t{\n\t\t\t\tname: \"provider-fast\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"provider-fast-step\", 50);\n\t\t\t\t\treturn { provider: \"cdn-edge\", latency: 12 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"provider-slow\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"provider-slow-step\", 200);\n\t\t\t\t\treturn { provider: \"origin\", latency: 120 };\n\t\t\t\t},\n\t\t\t},\n\t\t]);\n\n\t\tawait ctx.step(\"use-result\", async (c) => {\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.winner = winner;\n\t\t\tc.state.result = value.provider;\n\t\t\treturn { used: value.provider };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryFullState = {\n\tid: string;\n\tstatus: \"pending\" | \"running\" | \"waiting\" | \"completed\" | \"failed\";\n\tseededMessages: boolean;\n\tlastStep?: string;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n};\n\nconst QUEUE_ORDER_CREATED = \"order:created\";\nconst QUEUE_ORDER_UPDATED = \"order:updated\";\nconst QUEUE_ORDER_ITEM = \"order:item\";\nconst QUEUE_ORDER_ARTIFACT = \"order:artifact\";\nconst QUEUE_ORDER_READY = \"order:ready\";\nconst QUEUE_ORDER_OPTIONAL = \"order:optional\";\n\ntype OrderCreatedMessage = { id: string };\ntype OrderUpdatedMessage = { id: string; status: string };\ntype OrderItemMessage = { sku: string; qty: number };\ntype OrderArtifactMessage = { artifactId: string };\ntype OrderReadyMessage = { batch: number };\ntype OrderOptionalMessage = { note?: string };\n\ntype MessageSeed =\n\t| { name: typeof QUEUE_ORDER_CREATED; payload: OrderCreatedMessage }\n\t| { name: typeof QUEUE_ORDER_UPDATED; payload: OrderUpdatedMessage }\n\t| { name: typeof QUEUE_ORDER_ITEM; payload: OrderItemMessage }\n\t| { name: typeof QUEUE_ORDER_ARTIFACT; payload: OrderArtifactMessage }\n\t| { name: typeof QUEUE_ORDER_READY; payload: OrderReadyMessage };\n\nconst FULL_WORKFLOW_MESSAGE_SEEDS: MessageSeed[] = [\n\t{ name: QUEUE_ORDER_CREATED, payload: { id: \"order-1\" } },\n\t{ name: QUEUE_ORDER_UPDATED, payload: { id: \"order-1\", status: \"paid\" } },\n\t{ name: QUEUE_ORDER_ITEM, payload: { sku: \"sku-0\", qty: 1 } },\n\t{ name: QUEUE_ORDER_ITEM, payload: { sku: \"sku-4\", qty: 1 } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-0\" } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-1\" } },\n\t{ name: QUEUE_ORDER_ARTIFACT, payload: { artifactId: \"artifact-2\" } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 3 } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 0 } },\n\t{ name: QUEUE_ORDER_READY, payload: { batch: 2 } },\n];\n\nconst FULL_WORKFLOW_ITEMS = [\n\t{ id: \"item-1\", basePrice: 100, tax: 8 },\n\t{ id: \"item-2\", basePrice: 115, tax: 9 },\n\t{ id: \"item-3\", basePrice: 130, tax: 10 },\n\t{ id: \"item-4\", basePrice: 145, tax: 12 },\n];\n\nexport const workflowHistoryFull = actor({\n\tcreateState: (c): WorkflowHistoryFullState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t\tseededMessages: false,\n\t}),\n\tqueues: {\n\t\t[QUEUE_ORDER_CREATED]: queue(),\n\t\t[QUEUE_ORDER_UPDATED]: queue(),\n\t\t[QUEUE_ORDER_ITEM]: queue(),\n\t\t[QUEUE_ORDER_ARTIFACT]: queue(),\n\t\t[QUEUE_ORDER_READY]: queue(),\n\t\t[QUEUE_ORDER_OPTIONAL]: queue(),\n\t},\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryFullState => c.state,\n\t\tseedMessages: async (c) => {\n\t\t\tif (c.state.seededMessages) return;\n\t\t\tfor (const seed of FULL_WORKFLOW_MESSAGE_SEEDS) {\n\t\t\t\tawait c.queue.send(seed.name, seed.payload);\n\t\t\t}\n\t\t\tc.state.seededMessages = true;\n\t\t},\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"bootstrap\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\tc.state.lastStep = \"bootstrap\";\n\t\t\tc.state.startedAt = Date.now();\n\t\t\treturn {\n\t\t\t\trequestId: `req-${c.state.id}`,\n\t\t\t\tstartedAt: Date.now(),\n\t\t\t};\n\t\t});\n\n\t\tawait ctx.step(\"validate-input\", async (c) => {\n\t\t\tc.state.lastStep = \"validate-input\";\n\t\t\treturn true;\n\t\t});\n\n\t\tawait ctx.rollbackCheckpoint(\"checkpoint-after-validation\");\n\n\t\tawait ctx.step(\"load-user-profile\", async (c) => {\n\t\t\tc.state.lastStep = \"load-user-profile\";\n\t\t\treturn {\n\t\t\t\tid: \"user-123\",\n\t\t\t\ttier: \"standard\",\n\t\t\t\tflags: [\"email-verified\", \"promo-eligible\"],\n\t\t\t};\n\t\t});\n\n\t\tawait ctx.step(\"compute-discount\", async (c) => {\n\t\t\tc.state.lastStep = \"compute-discount\";\n\t\t\treturn { percent: 5, reason: \"tier-discount\" };\n\t\t});\n\n\t\tawait ctx.step(\"ephemeral-cache-check\", async (c) => {\n\t\t\tc.state.lastStep = \"ephemeral-cache-check\";\n\t\t\treturn { cacheHit: false, tier: \"standard\" };\n\t\t});\n\n\t\tawait ctx.rollbackCheckpoint(\"checkpoint-before-reserve\");\n\n\t\tawait ctx.loop({\n\t\t\tname: \"process-items-loop\",\n\t\t\tstate: { index: 0 },\n\t\t\tcommitInterval: 1,\n\t\t\thistoryEvery: 1,\n\t\t\thistoryKeep: 2,\n\t\t\trun: async (loopCtx, loopState: { index: number }) => {\n\t\t\t\tconst item = FULL_WORKFLOW_ITEMS[loopState.index];\n\t\t\t\tif (!item) {\n\t\t\t\t\treturn Loop.break({ count: FULL_WORKFLOW_ITEMS.length });\n\t\t\t\t}\n\n\t\t\t\tawait loopCtx.step(\n\t\t\t\t\t`fetch-item-${loopState.index}`,\n\t\t\t\t\tasync (loopCtx) => {\n\t\t\t\t\t\treturn { itemId: item.id, basePrice: item.basePrice };\n\t\t\t\t\t},\n\t\t\t\t);\n\n\t\t\t\tawait loopCtx.step(\n\t\t\t\t\t`compute-tax-${loopState.index}`,\n\t\t\t\t\tasync (loopCtx) => {\n\t\t\t\t\t\treturn item.tax;\n\t\t\t\t\t},\n\t\t\t\t);\n\n\t\t\t\tawait loopCtx.step(\n\t\t\t\t\t`reserve-inventory-${loopState.index}`,\n\t\t\t\t\tasync (loopCtx) => ({\n\t\t\t\t\t\treservationId: `res-${loopState.index}`,\n\t\t\t\t\t\titemId: item.id,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tif (loopState.index >= FULL_WORKFLOW_ITEMS.length - 1) {\n\t\t\t\t\treturn Loop.break({\n\t\t\t\t\t\tcount: FULL_WORKFLOW_ITEMS.length,\n\t\t\t\t\t\ttotal: 504,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn Loop.continue({ index: loopState.index + 1 });\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.sleep(\"short-cooldown\", 40);\n\t\tawait ctx.sleep(\"cooldown-sleep\", 60);\n\t\tawait ctx.sleep(\"wait-until-deadline\", 45);\n\n\t\tawait ctx.step(\"compute-deadlines\", async (ctx) => {\n\t\t\tconst readyBy = Date.now() + 800;\n\t\t\tconst readyBatchBy = Date.now() + 1100;\n\t\t\treturn { readyBy, readyBatchBy };\n\t\t});\n\n\t\tawait ctx.queue.next(\"listen-order-created\", {\n\t\t\tnames: [QUEUE_ORDER_CREATED],\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-order-updated-timeout\", {\n\t\t\tnames: [QUEUE_ORDER_UPDATED],\n\t\t\ttimeout: 250,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-batch-two\", {\n\t\t\tnames: [QUEUE_ORDER_ITEM],\n\t\t\tcount: 2,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-artifacts-timeout\", {\n\t\t\tnames: [QUEUE_ORDER_ARTIFACT],\n\t\t\tcount: 3,\n\t\t\ttimeout: 300,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-optional\", {\n\t\t\tnames: [QUEUE_ORDER_OPTIONAL],\n\t\t\ttimeout: 200,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-until\", {\n\t\t\tnames: [QUEUE_ORDER_READY],\n\t\t\ttimeout: 300,\n\t\t});\n\t\tawait ctx.queue.nextBatch(\"listen-batch-until\", {\n\t\t\tnames: [QUEUE_ORDER_READY],\n\t\t\tcount: 2,\n\t\t\ttimeout: 400,\n\t\t});\n\n\t\tawait ctx.join(\"join-dependencies\", {\n\t\t\tinventory: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst reserved = await branchCtx.step(\n\t\t\t\t\t\t\"inventory-audit\",\n\t\t\t\t\t\tasync (branchCtx) => 4,\n\t\t\t\t\t);\n\t\t\t\t\tawait branchCtx.sleep(\"join-inventory-sleep\", 35);\n\t\t\t\t\treturn {\n\t\t\t\t\t\treserved,\n\t\t\t\t\t\tchecked: 4,\n\t\t\t\t\t\tnotes: [\"inventory-ok\", \"items=4\"],\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t\tpricing: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst method = await branchCtx.step(\n\t\t\t\t\t\t\"pricing-method\",\n\t\t\t\t\t\tasync (branchCtx) => \"promo\",\n\t\t\t\t\t);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsubtotal: 504,\n\t\t\t\t\t\tdiscount: 25,\n\t\t\t\t\t\ttotal: 479,\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t},\n\t\t\tshipping: {\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tconst zone = await branchCtx.step(\n\t\t\t\t\t\t\"shipping-zone\",\n\t\t\t\t\t\tasync (branchCtx) => \"us-east\",\n\t\t\t\t\t);\n\t\t\t\t\tawait branchCtx.sleep(\"join-shipping-sleep\", 35);\n\t\t\t\t\treturn { method: \"ground\", etaDays: 4, zone };\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tawait ctx.race(\"race-fulfillment\", [\n\t\t\t{\n\t\t\t\tname: \"race-fast\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"race-fast-sleep\", 70);\n\t\t\t\t\treturn { method: \"express\", cost: 18, etaDays: 1 };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"race-slow\",\n\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\tawait branchCtx.sleep(\"race-slow-sleep\", 250);\n\t\t\t\t\treturn { method: \"ground\", cost: 8, etaDays: 4 };\n\t\t\t\t},\n\t\t\t},\n\t\t]);\n\n\t\tawait ctx.removed(\"legacy-step-placeholder\", \"step\");\n\n\t\tawait ctx.step(\"finalize\", async (c) => {\n\t\t\tc.state.lastStep = \"finalize\";\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn true;\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryInProgressState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tprocessingDurationMs: number;\n\tprogress: number;\n\tstartedAt?: number;\n\tcompletedAt?: number;\n};\n\nexport type WorkflowHistoryInProgressInput = {\n\tprocessingDurationMs?: number;\n};\n\nexport const workflowHistoryInProgress = actor({\n\tcreateState: (\n\t\tc,\n\t\tinput?: WorkflowHistoryInProgressInput,\n\t): WorkflowHistoryInProgressState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tprocessingDurationMs: input?.processingDurationMs ?? 30000,\n\t\tprogress: 0,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryInProgressState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async (c) => {\n\t\t\tc.state.startedAt = Date.now();\n\t\t\tc.state.progress = 10;\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait ctx.step(\"fetch-data\", async (c) => {\n\t\t\tc.state.progress = 25;\n\t\t\treturn { fetched: true, records: 100 };\n\t\t});\n\n\t\tawait ctx.step(\"process\", async (c) => {\n\t\t\tc.state.progress = 42;\n\t\t\tawait delay(c.state.processingDurationMs);\n\t\t\tc.state.status = \"completed\";\n\t\t\tc.state.completedAt = Date.now();\n\t\t\treturn { processed: true };\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryRetryingState = {\n\tid: string;\n\tstatus: \"running\" | \"completed\";\n\tattempts: number;\n\tlastError?: string;\n\tsucceedAfter: number;\n};\n\nconst RETRY_MAX_RETRIES = 20;\n\nexport const workflowHistoryRetrying = actor({\n\tcreateState: (c): WorkflowHistoryRetryingState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tattempts: 0,\n\t\tsucceedAfter: 999,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryRetryingState => c.state,\n\t\tallowSuccess: (c, afterAttempt?: number) => {\n\t\t\tc.state.succeedAfter = afterAttempt ?? c.state.attempts + 1;\n\t\t},\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"start\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { ready: true };\n\t\t});\n\n\t\tawait ctx.step({\n\t\t\tname: \"api-call\",\n\t\t\tmaxRetries: RETRY_MAX_RETRIES,\n\t\t\tretryBackoffBase: 250,\n\t\t\tretryBackoffMax: 1500,\n\t\t\trun: async (c) => {\n\t\t\t\tc.state.attempts += 1;\n\t\t\t\tif (c.state.attempts < c.state.succeedAfter) {\n\t\t\t\t\tconst error = new Error(\"Connection timeout after 5000ms\");\n\t\t\t\t\tc.state.lastError = error.message;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tc.state.status = \"completed\";\n\t\t\t\tc.state.lastError = undefined;\n\t\t\t\treturn { success: true };\n\t\t\t},\n\t\t});\n\t}),\n});\n\nexport type WorkflowHistoryFailedState = {\n\tid: string;\n\tstatus: \"running\" | \"failed\";\n\tattempts: number;\n\tlastError?: string;\n};\n\nconst FAILED_MAX_RETRIES = 3;\n\nexport const workflowHistoryFailed = actor({\n\tcreateState: (c): WorkflowHistoryFailedState => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"running\",\n\t\tattempts: 0,\n\t}),\n\tactions: {\n\t\tgetState: (c): WorkflowHistoryFailedState => c.state,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.step(\"init\", async (c) => {\n\t\t\tc.state.status = \"running\";\n\t\t\treturn { initialized: true };\n\t\t});\n\n\t\tawait ctx.step(\"validate\", async (ctx) => {\n\t\t\treturn { valid: true };\n\t\t});\n\n\t\tawait ctx.step({\n\t\t\tname: \"process\",\n\t\t\tmaxRetries: FAILED_MAX_RETRIES,\n\t\t\tretryBackoffBase: 200,\n\t\t\tretryBackoffMax: 800,\n\t\t\trun: async (c) => {\n\t\t\t\tc.state.attempts += 1;\n\t\t\t\tconst error = new Error(\n\t\t\t\t\t\"Database connection failed: ECONNREFUSED\",\n\t\t\t\t);\n\t\t\t\tc.state.lastError = error.message;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t});\n\t}),\n});\n","// ORDER PROCESSOR (Steps Demo)\n// Demonstrates: Sequential workflow steps with automatic retries\n// One actor per order - actor key is the order ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type OrderStatus =\n\t| \"pending\"\n\t| \"validating\"\n\t| \"charging\"\n\t| \"fulfilling\"\n\t| \"completed\"\n\t| \"failed\";\n\nexport type Order = {\n\tid: string;\n\tstatus: OrderStatus;\n\tstep: number;\n\terror?: string;\n\tcreatedAt: number;\n\tcompletedAt?: number;\n};\n\nasync function simulateWork(name: string, failChance = 0.1): Promise {\n\tawait new Promise((resolve) =>\n\t\tsetTimeout(resolve, 500 + Math.random() * 1000),\n\t);\n\tif (Math.random() < failChance) {\n\t\tthrow new Error(`${name} failed (simulated)`);\n\t}\n}\n\nexport const order = actor({\n\tcreateState: (c): Order => ({\n\t\tid: c.key[0] as string,\n\t\tstatus: \"pending\",\n\t\tstep: 0,\n\t\tcreatedAt: Date.now(),\n\t}),\n\tevents: {\n\t\torderUpdated: event(),\n\t},\n\n\tactions: {\n\t\tgetOrder: (c): Order => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"process-order\", async (loopCtx) => {\n\t\t\tawait loopCtx.step(\"validate\", async (c) => {\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"processing order\",\n\t\t\t\t\torderId: c.state.id,\n\t\t\t\t});\n\t\t\t\tc.state.status = \"validating\";\n\t\t\t\tc.state.step = 1;\n\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\tawait simulateWork(\"validation\", 0.05);\n\t\t\t});\n\n\t\t\tawait loopCtx.step(\"charge\", async (c) => {\n\t\t\t\tc.state.status = \"charging\";\n\t\t\t\tc.state.step = 2;\n\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\tawait simulateWork(\"payment\", 0.1);\n\t\t\t});\n\n\t\t\tawait loopCtx.step(\"fulfill\", async (c) => {\n\t\t\t\tc.state.status = \"fulfilling\";\n\t\t\t\tc.state.step = 3;\n\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\tawait simulateWork(\"fulfillment\", 0.05);\n\t\t\t});\n\n\t\t\tawait loopCtx.step(\"complete\", async (c) => {\n\t\t\t\tc.state.status = \"completed\";\n\t\t\t\tc.state.step = 4;\n\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\tc.broadcast(\"orderUpdated\", c.state);\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"order completed\",\n\t\t\t\t\torderId: c.state.id,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\treturn Loop.break(undefined);\n\t\t});\n\t}),\n});\n","// PAYMENT PROCESSOR (Rollback Demo)\n// Demonstrates: Rollback checkpoints with compensating actions\n// One actor per transaction - actor key is the transaction ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type TransactionStep = {\n\tname: string;\n\tstatus: \"pending\" | \"completed\" | \"rolled_back\";\n\tcompletedAt?: number;\n\trolledBackAt?: number;\n};\n\nexport type Transaction = {\n\tid: string;\n\tamount: number;\n\tshouldFail: boolean;\n\tstatus:\n\t\t| \"pending\"\n\t\t| \"reserving\"\n\t\t| \"charging\"\n\t\t| \"completing\"\n\t\t| \"completed\"\n\t\t| \"rolling_back\"\n\t\t| \"failed\";\n\tsteps: TransactionStep[];\n\terror?: string;\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\nexport type TransactionInput = {\n\tamount?: number;\n\tshouldFail?: boolean;\n};\n\nexport const payment = actor({\n\tcreateState: (c, input?: TransactionInput): Transaction => ({\n\t\tid: c.key[0] as string,\n\t\tamount: input?.amount ?? 100,\n\t\tshouldFail: input?.shouldFail ?? false,\n\t\tstatus: \"pending\",\n\t\tsteps: [\n\t\t\t{ name: \"reserve-inventory\", status: \"pending\" },\n\t\t\t{ name: \"charge-card\", status: \"pending\" },\n\t\t\t{ name: \"complete-order\", status: \"pending\" },\n\t\t],\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\ttransactionStarted: event(),\n\t\ttransactionUpdated: event(),\n\t\ttransactionCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTransaction: (c): Transaction => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"payment-loop\", async (loopCtx) => {\n\t\t\tawait loopCtx.step(\"init-payment\", async (c) => {\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"starting payment processing\",\n\t\t\t\t\ttxId: c.state.id,\n\t\t\t\t\tamount: c.state.amount,\n\t\t\t\t\tshouldFail: c.state.shouldFail,\n\t\t\t\t});\n\t\t\t\tc.broadcast(\"transactionStarted\", c.state);\n\t\t\t});\n\n\t\t\tawait loopCtx.rollbackCheckpoint(\"payment-checkpoint\");\n\n\t\t\t// Step 1: Reserve inventory\n\t\t\tawait loopCtx.step({\n\t\t\t\tname: \"reserve-inventory\",\n\t\t\t\trun: async (c) => {\n\t\t\t\t\tc.state.status = \"reserving\";\n\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t(s) => s.name === \"reserve-inventory\",\n\t\t\t\t\t);\n\t\t\t\t\tif (step) {\n\t\t\t\t\t\tstep.status = \"completed\";\n\t\t\t\t\t\tstep.completedAt = Date.now();\n\t\t\t\t\t}\n\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\tsetTimeout(r, 500 + Math.random() * 500),\n\t\t\t\t\t);\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"inventory reserved\",\n\t\t\t\t\t\ttxId: c.state.id,\n\t\t\t\t\t});\n\t\t\t\t\treturn { reserved: true };\n\t\t\t\t},\n\t\t\t\trollback: async (c) => {\n\t\t\t\t\t// Set rolling_back status on first rollback\n\t\t\t\t\tc.state.status = \"rolling_back\";\n\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t(s) => s.name === \"reserve-inventory\",\n\t\t\t\t\t);\n\t\t\t\t\tif (step) {\n\t\t\t\t\t\tstep.status = \"rolled_back\";\n\t\t\t\t\t\tstep.rolledBackAt = Date.now();\n\t\t\t\t\t}\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"inventory released\",\n\t\t\t\t\t\ttxId: c.state.id,\n\t\t\t\t\t});\n\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\t\t\t\t\t// Small delay so UI can show the rollback\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 400));\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Step 2: Charge card\n\t\t\tawait loopCtx.step({\n\t\t\t\tname: \"charge-card\",\n\t\t\t\trun: async (c) => {\n\t\t\t\t\tc.state.status = \"charging\";\n\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t(s) => s.name === \"charge-card\",\n\t\t\t\t\t);\n\t\t\t\t\tif (step) {\n\t\t\t\t\t\tstep.status = \"completed\";\n\t\t\t\t\t\tstep.completedAt = Date.now();\n\t\t\t\t\t}\n\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\t\tsetTimeout(r, 500 + Math.random() * 500),\n\t\t\t\t\t);\n\n\t\t\t\t\tif (c.state.shouldFail) {\n\t\t\t\t\t\tthrow new Error(\"Payment declined (simulated)\");\n\t\t\t\t\t}\n\n\t\t\t\t\tc.log.info({ msg: \"card charged\", txId: c.state.id });\n\t\t\t\t\treturn { chargeId: `ch_${c.state.id}` };\n\t\t\t\t},\n\t\t\t\trollback: async (c) => {\n\t\t\t\t\tc.state.status = \"rolling_back\";\n\t\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t\t(s) => s.name === \"charge-card\",\n\t\t\t\t\t);\n\t\t\t\t\tif (step) {\n\t\t\t\t\t\tstep.status = \"rolled_back\";\n\t\t\t\t\t\tstep.rolledBackAt = Date.now();\n\t\t\t\t\t}\n\t\t\t\t\tc.log.info({ msg: \"charge refunded\", txId: c.state.id });\n\t\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\t\t\t\t\t// Small delay so UI can show the rollback\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 400));\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Step 3: Complete order\n\t\t\tawait loopCtx.step(\"complete-order\", async (c) => {\n\t\t\t\tc.state.status = \"completing\";\n\t\t\t\tconst step = c.state.steps.find(\n\t\t\t\t\t(s) => s.name === \"complete-order\",\n\t\t\t\t);\n\t\t\t\tif (step) step.status = \"completed\";\n\t\t\t\tc.broadcast(\"transactionUpdated\", c.state);\n\n\t\t\t\tawait new Promise((r) =>\n\t\t\t\t\tsetTimeout(r, 300 + Math.random() * 300),\n\t\t\t\t);\n\n\t\t\t\tc.state.status = \"completed\";\n\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\tc.log.info({ msg: \"order completed\", txId: c.state.id });\n\t\t\t\tc.broadcast(\"transactionCompleted\", c.state);\n\t\t\t});\n\n\t\t\treturn Loop.break(undefined);\n\t\t});\n\t}),\n});\n","// RACE RUNNER (Race Demo)\n// Demonstrates: Race (parallel first-wins) for timeout patterns\n// One actor per race task - actor key is the task ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type RaceTask = {\n\tid: string;\n\tworkDurationMs: number;\n\ttimeoutMs: number;\n\tstatus: \"running\" | \"work_won\" | \"timeout_won\";\n\tresult?: string;\n\tstartedAt: number;\n\tcompletedAt?: number;\n\tactualDurationMs?: number;\n};\n\nexport type RaceTaskInput = {\n\tworkDurationMs?: number;\n\ttimeoutMs?: number;\n};\n\nexport const race = actor({\n\tcreateState: (c, input?: RaceTaskInput): RaceTask => ({\n\t\tid: c.key[0] as string,\n\t\tworkDurationMs: input?.workDurationMs ?? 2000,\n\t\ttimeoutMs: input?.timeoutMs ?? 3000,\n\t\tstatus: \"running\",\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\traceStarted: event(),\n\t\traceCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTask: (c): RaceTask => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"race-loop\", async (loopCtx) => {\n\t\t\t// Get durations inside a step since state is only available in steps\n\t\t\tconst { workDurationMs, timeoutMs, taskId } = await loopCtx.step(\n\t\t\t\t\"start-race\",\n\t\t\t\tasync (c) => {\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"starting race\",\n\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\tworkDurationMs: c.state.workDurationMs,\n\t\t\t\t\t\ttimeoutMs: c.state.timeoutMs,\n\t\t\t\t\t});\n\t\t\t\t\tc.broadcast(\"raceStarted\", c.state);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tworkDurationMs: c.state.workDurationMs,\n\t\t\t\t\t\ttimeoutMs: c.state.timeoutMs,\n\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst { winner, value } = await loopCtx.race(\"work-vs-timeout\", [\n\t\t\t\t{\n\t\t\t\t\tname: \"work\",\n\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\tawait branchCtx.sleep(\"simulate-work\", workDurationMs);\n\t\t\t\t\t\treturn await branchCtx.step(\n\t\t\t\t\t\t\t\"complete-work\",\n\t\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\t\treturn `Result for task ${taskId}`;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"timeout\",\n\t\t\t\t\trun: async (branchCtx) => {\n\t\t\t\t\t\tawait branchCtx.sleep(\"timeout-wait\", timeoutMs);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tawait loopCtx.step(\"save-result\", async (c) => {\n\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\tc.state.actualDurationMs =\n\t\t\t\t\tc.state.completedAt - c.state.startedAt;\n\n\t\t\t\tif (winner === \"work\") {\n\t\t\t\t\tc.state.status = \"work_won\";\n\t\t\t\t\tc.state.result = value as string;\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"work completed before timeout\",\n\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\tdurationMs: c.state.actualDurationMs,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tc.state.status = \"timeout_won\";\n\t\t\t\t\tc.log.info({\n\t\t\t\t\t\tmsg: \"timeout won the race\",\n\t\t\t\t\t\ttaskId: c.state.id,\n\t\t\t\t\t\tdurationMs: c.state.actualDurationMs,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tc.broadcast(\"raceCompleted\", c.state);\n\t\t\t});\n\n\t\t\treturn Loop.break(undefined);\n\t\t});\n\t}),\n});\n","// TIMER (Sleep Demo)\n// Demonstrates: Durable sleep that survives restarts\n// One actor per timer - actor key is the timer ID\n\nimport { actor, event } from \"rivetkit\";\nimport { Loop, workflow } from \"rivetkit/workflow\";\n\nexport type Timer = {\n\tid: string;\n\tname: string;\n\tdurationMs: number;\n\tstartedAt: number;\n\tcompletedAt?: number;\n};\n\nexport type TimerInput = {\n\tname?: string;\n\tdurationMs?: number;\n};\n\nexport const timer = actor({\n\tcreateState: (c, input?: TimerInput): Timer => ({\n\t\tid: c.key[0] as string,\n\t\tname: input?.name ?? \"Timer\",\n\t\tdurationMs: input?.durationMs ?? 10000,\n\t\tstartedAt: Date.now(),\n\t}),\n\tevents: {\n\t\ttimerStarted: event(),\n\t\ttimerCompleted: event(),\n\t},\n\n\tactions: {\n\t\tgetTimer: (c): Timer => c.state,\n\t},\n\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"timer-loop\", async (loopCtx) => {\n\t\t\t// Get duration inside a step since state is only available in steps\n\t\t\tconst durationMs = await loopCtx.step(\"start-timer\", async (c) => {\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"starting timer\",\n\t\t\t\t\ttimerId: c.state.id,\n\t\t\t\t\tdurationMs: c.state.durationMs,\n\t\t\t\t});\n\t\t\t\tc.broadcast(\"timerStarted\", c.state);\n\t\t\t\treturn c.state.durationMs;\n\t\t\t});\n\n\t\t\tawait loopCtx.sleep(\"countdown\", durationMs);\n\n\t\t\tawait loopCtx.step(\"complete-timer\", async (c) => {\n\t\t\t\tc.state.completedAt = Date.now();\n\t\t\t\tc.broadcast(\"timerCompleted\", c.state);\n\t\t\t\tc.log.info({\n\t\t\t\t\tmsg: \"timer completed\",\n\t\t\t\t\ttimerId: c.state.id,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\treturn Loop.break(undefined);\n\t\t});\n\t}),\n\n\toptions: {\n\t\tsleepTimeout: 1000,\n\t},\n});\n","import { actor, event, queue } from \"rivetkit\";\nimport { Loop, type WorkflowStepContextOf, workflow } from \"rivetkit/workflow\";\n\nconst WORKFLOW_GUARD_KV_KEY = \"__rivet_actor_workflow_guard_triggered\";\n\nconst WORKFLOW_QUEUE_NAME = \"workflow-default\";\nconst WORKFLOW_TIMEOUT_QUEUE_NAME = \"workflow-timeout\";\n\nexport const workflowCounterActor = actor({\n\tstate: {\n\t\trunCount: 0,\n\t\tguardTriggered: false,\n\t\thistory: [] as number[],\n\t},\n\trun: workflow(async (ctx) => {\n\t\tlet leakedCtx:\n\t\t\t| WorkflowStepContextOf\n\t\t\t| undefined;\n\t\tawait ctx.loop(\"counter\", async (loopCtx) => {\n\t\t\tawait loopCtx.step(\"increment\", async (c) => {\n\t\t\t\tincrementWorkflowCounter(c);\n\t\t\t\t// Capture the step context to verify it cannot be used after\n\t\t\t\t// its step has finished.\n\t\t\t\tleakedCtx = c;\n\t\t\t});\n\n\t\t\tif (leakedCtx) {\n\t\t\t\ttry {\n\t\t\t\t\t// Accessing state on a finished step context should throw.\n\t\t\t\t\t// biome-ignore lint/style/noUnusedExpressions: intentionally checking accessor.\n\t\t\t\t\tleakedCtx.state;\n\t\t\t\t} catch {}\n\t\t\t}\n\n\t\t\tawait loopCtx.sleep(\"idle\", 25);\n\t\t\treturn Loop.continue(undefined);\n\t\t});\n\t}),\n\tactions: {\n\t\tgetState: async (c) => {\n\t\t\tconst guardFlag = await c.kv.get(WORKFLOW_GUARD_KV_KEY);\n\t\t\tif (guardFlag === \"true\") {\n\t\t\t\tc.state.guardTriggered = true;\n\t\t\t}\n\t\t\treturn c.state;\n\t\t},\n\t},\n\toptions: {\n\t\tsleepTimeout: 50,\n\t},\n});\n\nexport const workflowQueueActor = actor({\n\tstate: {\n\t\treceived: [] as unknown[],\n\t},\n\tqueues: {\n\t\t[WORKFLOW_QUEUE_NAME]: queue(),\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"queue\", async (loopCtx) => {\n\t\t\tconst message = await loopCtx.queue.next(\"queue-wait\", {\n\t\t\t\tnames: [WORKFLOW_QUEUE_NAME],\n\t\t\t\tcompletable: true,\n\t\t\t});\n\t\t\tif (!message.complete) {\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t}\n\t\t\tconst complete = message.complete;\n\t\t\tawait loopCtx.step(\"store-message\", async (c) => {\n\t\t\t\tawait storeWorkflowQueueMessage(c, message.body, complete);\n\t\t\t});\n\t\t\treturn Loop.continue(undefined);\n\t\t});\n\t}),\n\tactions: {\n\t\tgetMessages: (c) => c.state.received,\n\t},\n});\n\nexport const workflowSleepActor = actor({\n\tstate: {\n\t\tticks: 0,\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"sleep\", async (loopCtx) => {\n\t\t\tawait loopCtx.step(\"tick\", async (c) => {\n\t\t\t\tincrementWorkflowSleepTick(c);\n\t\t\t});\n\t\t\tawait loopCtx.sleep(\"delay\", 40);\n\t\t\treturn Loop.continue(undefined);\n\t\t});\n\t}),\n\tactions: {\n\t\tgetState: (c) => c.state,\n\t},\n\toptions: {\n\t\tsleepTimeout: 50,\n\t},\n});\n\nexport const workflowQueueTimeoutActor = actor({\n\tstate: {\n\t\tprocessed: 0,\n\t\tticks: 0,\n\t\tlastTickAt: null as number | null,\n\t\tlastJob: null as { id: string; payload: string } | null,\n\t\ttimeoutMs: 2_000,\n\t},\n\tevents: {\n\t\ttick: event<{ ticks: number; at: number }>(),\n\t\tjobProcessed: event<{\n\t\t\tprocessed: number;\n\t\t\tjob: { id: string; payload: string };\n\t\t}>(),\n\t},\n\tqueues: {\n\t\t[WORKFLOW_TIMEOUT_QUEUE_NAME]: queue<{ id: string; payload: string }>(),\n\t},\n\trun: workflow(async (ctx) => {\n\t\tawait ctx.loop(\"queue-timeout-loop\", async (loopCtx) => {\n\t\t\tconst timeoutMs = await loopCtx.step(\"read-timeout\", async (c) =>\n\t\t\t\treadWorkflowTimeoutMs(c),\n\t\t\t);\n\n\t\t\tconst [message] = await loopCtx.queue.nextBatch(\n\t\t\t\t\"wait-job-or-timeout\",\n\t\t\t\t{\n\t\t\t\t\tnames: [WORKFLOW_TIMEOUT_QUEUE_NAME],\n\t\t\t\t\ttimeout: timeoutMs,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!message) {\n\t\t\t\tawait loopCtx.step(\"tick\", async (c) => {\n\t\t\t\t\trecordWorkflowTimeoutTick(c);\n\t\t\t\t});\n\t\t\t\treturn Loop.continue(undefined);\n\t\t\t}\n\n\t\t\tawait loopCtx.step(\"process-job\", async (c) => {\n\t\t\t\tprocessWorkflowTimeoutJob(c, message.body);\n\t\t\t});\n\t\t\treturn Loop.continue(undefined);\n\t\t});\n\t}),\n\tactions: {\n\t\tenqueueJob: async (c, payload: string) => {\n\t\t\tconst job = { id: crypto.randomUUID(), payload };\n\t\t\tawait c.queue.send(WORKFLOW_TIMEOUT_QUEUE_NAME, job);\n\t\t\treturn job;\n\t\t},\n\t\tsetTimeoutMs: (c, timeoutMs: number) => {\n\t\t\tc.state.timeoutMs = Math.max(100, Math.floor(timeoutMs));\n\t\t\treturn c.state.timeoutMs;\n\t\t},\n\t\tgetState: (c) => c.state,\n\t},\n});\n\nfunction incrementWorkflowCounter(\n\tctx: WorkflowStepContextOf,\n): void {\n\tctx.state.runCount += 1;\n\tctx.state.history.push(ctx.state.runCount);\n}\n\nasync function storeWorkflowQueueMessage(\n\tctx: WorkflowStepContextOf,\n\tbody: unknown,\n\tcomplete: (response: { echo: unknown }) => Promise,\n): Promise {\n\tctx.state.received.push(body);\n\tawait complete({ echo: body });\n}\n\nfunction incrementWorkflowSleepTick(\n\tctx: WorkflowStepContextOf,\n): void {\n\tctx.state.ticks += 1;\n}\n\nfunction readWorkflowTimeoutMs(\n\tctx: WorkflowStepContextOf,\n): number {\n\treturn ctx.state.timeoutMs;\n}\n\nfunction recordWorkflowTimeoutTick(\n\tctx: WorkflowStepContextOf,\n): void {\n\tconst at = Date.now();\n\tctx.state.ticks += 1;\n\tctx.state.lastTickAt = at;\n\tctx.broadcast(\"tick\", {\n\t\tticks: ctx.state.ticks,\n\t\tat,\n\t});\n}\n\nfunction processWorkflowTimeoutJob(\n\tctx: WorkflowStepContextOf,\n\tjob: { id: string; payload: string },\n): void {\n\tctx.state.processed += 1;\n\tctx.state.lastJob = job;\n\tctx.broadcast(\"jobProcessed\", {\n\t\tprocessed: ctx.state.processed,\n\t\tjob,\n\t});\n}\n\nexport { WORKFLOW_QUEUE_NAME, WORKFLOW_TIMEOUT_QUEUE_NAME };\n","export type KitchenSinkMode = \"serverless\" | \"serverful\" | \"serverless-local\";\n\nexport function resolveMode(): KitchenSinkMode {\n\tconst explicit = process.env.RIVET_KITCHEN_SINK_MODE;\n\tif (\n\t\texplicit === \"serverless\" ||\n\t\texplicit === \"serverful\" ||\n\t\texplicit === \"serverless-local\"\n\t) {\n\t\treturn explicit;\n\t}\n\tif (explicit !== undefined && explicit !== \"\") {\n\t\tthrow new Error(\n\t\t\t`RIVET_KITCHEN_SINK_MODE must be one of \"serverless\", \"serverful\", or \"serverless-local\" (got \"${explicit}\")`,\n\t\t);\n\t}\n\n\tif (process.env.RIVET_RUN_ENGINE === \"1\") return \"serverless-local\";\n\tif (process.env.RIVET_SERVERLESS_URL !== undefined)\n\t\treturn \"serverless-local\";\n\tif (process.env.KITCHEN_SINK_SERVERLESS_URL !== undefined) {\n\t\treturn \"serverless-local\";\n\t}\n\n\treturn \"serverless\";\n}\n"],"mappings":"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","names":["Hono","setup","actor","resolve","actor","resolve","actor","UserError","resolve","actor","actor","text","actor","event","c","actor","UserError","resolve","actor","event","actor","event","actor","event","actor","event","actor","resolve","actor","event","actor","app","Hono","actor","actor","text","actor","event","actor","event","actor","resolve","event","actor","event","actor","actor","actor","actor","resolve","actor","event","actor","event","sleep","resolve","actor","event","queue","actor","event","queue","payload","actor","actor","actor","actor","actor","event","db","actor","db","todos","actor","db","actor","db","actor","actor","db","actor","resolve","events","result1","result2","actor","db","payload","sleep","resolve","event","actor","db","resolve","send","event","result","text","actor","db","sleep","resolve","send","payload","event","actor","db","queryOne","payload","counter","mode","event","actor","db","sleep","resolve","event","actor","event","actor","db","AsyncMutex","resolve","MESSAGE_COUNT","MESSAGE_TOOL_REF_COUNT","TOOL_CALL_COUNT","EXECUTOR_TOOL_COUNT","THREAD_EVENT_COUNT","MESSAGE_CONTENT_BYTES","THREAD_EVENT_PAYLOAD_BYTES","TOOL_CALL_RESULT_BYTES","EXECUTOR_TOOL_SCHEMA_BYTES","event","result","runCatchupSnapshot","numberField","stringField","runBuildToolPlanContext","runRecoverToolCalls","delay","timedQuery","hasPendingLaunch","text","batchInsert","messageId","toolUseID","actor","db","DEFAULT_ROW_BYTES","DEFAULT_BATCH_ROWS","positiveInteger","actor","db","DEFAULT_ROW_BYTES","finiteInt","numberField","queryOne","storageStats","actor","db","DEFAULT_ROW_BYTES","positiveInteger","typedRows","batchInsert","checksum","actor","actor","db","actor","db","CHAT_LOG_CHUNK_BYTES","CHAT_LOG_INSERT_BATCH_SIZE","buildChatLogMessage","seedChatLog","batch","actor","db","actor","event","queue","actor","event","Loop","workflow","batch","actor","event","queue","Loop","workflow","actor","queue","Loop","workflow","delay","resolve","branchCtx","loopCtx","ctx","actor","event","Loop","workflow","resolve","actor","event","Loop","workflow","actor","event","Loop","workflow","actor","event","Loop","workflow","actor","event","queue","Loop","workflow","payload","numberFromEnv","registry","setup","sleep","Hono","registry"]} \ No newline at end of file diff --git a/examples/kitchen-sink/scripts/seed-large-dbs.ts b/examples/kitchen-sink/scripts/seed-large-dbs.ts new file mode 100644 index 0000000000..411b1bd4ed --- /dev/null +++ b/examples/kitchen-sink/scripts/seed-large-dbs.ts @@ -0,0 +1,223 @@ +#!/usr/bin/env -S pnpm exec tsx + +// Seeds many large, uncompacted actor SQLite databases for the compaction +// load test. Creates N unique growDb actor keys and grows each to a target +// size with append-only inserts (one commit per batch). Concurrency-limited. +// +// The engine under test has hot compaction disabled, so every commit +// accumulates as an uncompacted delta chain in FDB. This driver only issues +// action calls; the actual SQLite writes happen on the serverful kitchen-sink +// runners that the engine routes to (pool "k8s"). +// +// Usage (in-cluster Job or laptop): +// RIVET_ENDPOINT="http://default:@rivet-engine.rivet-engine.svc.cluster.local:6420" \ +// SEED_COUNT=200 SEED_TARGET_MB=64 SEED_CONCURRENCY=16 \ +// node --import tsx scripts/seed-large-dbs.ts +// +// All knobs are env vars (flags also accepted): +// RIVET_ENDPOINT engine guard endpoint incl. namespace:token userinfo +// SEED_COUNT number of distinct actor DBs to create (default 100) +// SEED_TARGET_MB target size per DB in MiB (default 64) +// SEED_BATCH_ROWS rows per committed batch (default 128) +// SEED_ROW_BYTES payload bytes per row (default 8192) +// SEED_CONCURRENCY max DBs grown in parallel (default 16) +// SEED_KEY_PREFIX actor key prefix (default "grow") +// SEED_RUN_ID run tag mixed into keys (default timestamp) +// SEED_MAX_CALLS max grow() calls per DB before giving up (default 100) + +import { createClient } from "rivetkit/client"; +import type { registry } from "../src/index.ts"; + +interface Args { + endpoint: string; + count: number; + targetMb: number; + batchRows: number; + rowBytes: number; + concurrency: number; + keyPrefix: string; + runId: string; + maxCalls: number; +} + +function envNum(name: string, fallback: number): number { + const raw = process.env[name]; + if (raw === undefined || raw === "") return fallback; + const value = Number(raw); + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`${name} must be a positive number, got ${raw}`); + } + return Math.floor(value); +} + +function flag(argv: string[], name: string): string | undefined { + const prefix = `${name}=`; + const inline = argv.find((a) => a.startsWith(prefix)); + if (inline) return inline.slice(prefix.length); + const idx = argv.indexOf(name); + if (idx >= 0) return argv[idx + 1]; + return undefined; +} + +function parseArgs(argv: string[]): Args { + const endpoint = flag(argv, "--endpoint") ?? process.env.RIVET_ENDPOINT; + if (!endpoint) { + throw new Error("missing endpoint: set RIVET_ENDPOINT or pass --endpoint"); + } + const runId = + flag(argv, "--run-id") ?? + process.env.SEED_RUN_ID ?? + new Date().toISOString().replace(/[:.]/g, "-"); + return { + endpoint, + count: Number(flag(argv, "--count")) || envNum("SEED_COUNT", 100), + targetMb: Number(flag(argv, "--target-mb")) || envNum("SEED_TARGET_MB", 64), + batchRows: + Number(flag(argv, "--batch-rows")) || envNum("SEED_BATCH_ROWS", 64), + rowBytes: Number(flag(argv, "--row-bytes")) || envNum("SEED_ROW_BYTES", 8192), + concurrency: + Number(flag(argv, "--concurrency")) || envNum("SEED_CONCURRENCY", 16), + keyPrefix: flag(argv, "--key-prefix") ?? process.env.SEED_KEY_PREFIX ?? "grow", + runId, + maxCalls: Number(flag(argv, "--max-calls")) || envNum("SEED_MAX_CALLS", 100), + }; +} + +function fmtBytes(bytes: number): string { + return `${(bytes / 1024 / 1024).toFixed(1)}MiB`; +} + +interface DbOutcome { + key: string; + ok: boolean; + sizeBytes: number; + pageCount: number; + calls: number; + error?: string; +} + +async function growOne( + client: ReturnType>, + args: Args, + index: number, +): Promise { + const key = `${args.keyPrefix}-${args.runId}-${String(index).padStart(6, "0")}`; + const handle = client.growDb.getOrCreate(key); + let calls = 0; + let last = { done: false, sizeBytes: 0, pageCount: 0 }; + try { + while (!last.done && calls < args.maxCalls) { + const result = await handle.grow({ + targetMb: args.targetMb, + batchRows: args.batchRows, + rowBytes: args.rowBytes, + }); + calls += 1; + last = { + done: result.done, + sizeBytes: result.sizeBytes, + pageCount: result.pageCount, + }; + } + return { + key, + ok: last.done, + sizeBytes: last.sizeBytes, + pageCount: last.pageCount, + calls, + ...(last.done + ? {} + : { error: `not done after ${calls} calls` }), + }; + } catch (err) { + return { + key, + ok: false, + sizeBytes: last.sizeBytes, + pageCount: last.pageCount, + calls, + error: err instanceof Error ? err.message : String(err), + }; + } +} + +async function main(): Promise { + const args = parseArgs(process.argv.slice(2)); + const client = createClient(args.endpoint); + + const endpointForLog = args.endpoint.replace(/\/\/[^@]*@/, "//@"); + console.log( + JSON.stringify({ + event: "seed_start", + endpoint: endpointForLog, + count: args.count, + targetMb: args.targetMb, + batchRows: args.batchRows, + rowBytes: args.rowBytes, + concurrency: args.concurrency, + runId: args.runId, + }), + ); + + const outcomes: DbOutcome[] = new Array(args.count); + let next = 0; + let completed = 0; + let totalBytes = 0; + const startedAt = Date.now(); + + async function worker(): Promise { + while (true) { + const index = next; + if (index >= args.count) return; + next += 1; + const outcome = await growOne(client, args, index); + outcomes[index] = outcome; + completed += 1; + totalBytes += outcome.sizeBytes; + console.log( + JSON.stringify({ + event: "seed_db_done", + key: outcome.key, + ok: outcome.ok, + size: fmtBytes(outcome.sizeBytes), + pageCount: outcome.pageCount, + calls: outcome.calls, + completed, + total: args.count, + totalBytes: fmtBytes(totalBytes), + ...(outcome.error ? { error: outcome.error } : {}), + }), + ); + } + } + + await Promise.all( + new Array(Math.min(args.concurrency, args.count)).fill(0).map(() => worker()), + ); + + const ok = outcomes.filter((o) => o.ok).length; + const failed = outcomes.filter((o) => !o.ok); + console.log( + JSON.stringify({ + event: "seed_end", + ok, + failed: failed.length, + total: args.count, + totalBytes: fmtBytes(totalBytes), + elapsedSec: Math.round((Date.now() - startedAt) / 1000), + failures: failed.slice(0, 20).map((o) => ({ key: o.key, error: o.error })), + }), + ); + + if (failed.length > 0) process.exitCode = 1; +} + +main().catch((err) => { + console.error( + JSON.stringify({ + event: "seed_fatal", + error: err instanceof Error ? (err.stack ?? err.message) : String(err), + }), + ); + process.exit(1); +}); diff --git a/examples/kitchen-sink/src/actors/testing/grow-db.ts b/examples/kitchen-sink/src/actors/testing/grow-db.ts new file mode 100644 index 0000000000..cba3df5e4f --- /dev/null +++ b/examples/kitchen-sink/src/actors/testing/grow-db.ts @@ -0,0 +1,170 @@ +import { actor } from "rivetkit"; +import { db } from "rivetkit/db"; + +// Seeding vessel for the "large uncompacted SQLite DB" load test. +// +// The old engine build under test has hot compaction disabled, so every +// commit accumulates as an uncompacted delta chain in FDB that never folds. +// This actor grows a persistent SQLite database with append-only inserts and +// one commit per batch, producing both large page volume and many deltas. +// +// It deliberately avoids VACUUM, DELETE, and whole-database scans (no +// integrity_check) so the database only ever grows and each grow() call costs +// O(added bytes), not O(current size). + +interface GrowInput { + // Target total on-disk size in mebibytes. grow() is idempotent to this + // target: calling it again after the target is reached is a cheap no-op. + targetMb?: number; + // Rows inserted per committed batch. Each batch is one BEGIN/COMMIT, so + // this controls commit (delta) granularity. + batchRows?: number; + // Payload bytes per row, generated server-side via randomblob(). + rowBytes?: number; + // Wall-clock budget for a single grow() call in milliseconds. When the + // budget is exceeded the call returns done=false so the driver can resume + // with another call. Keeps a single action under the action timeout. + budgetMs?: number; +} + +interface StorageStats { + pageCount: number; + pageSize: number; + freelistCount: number; + sizeBytes: number; +} + +interface GrowResult extends StorageStats { + done: boolean; + targetBytes: number; + batchesThisCall: number; + rowsThisCall: number; + elapsedMs: number; +} + +const DEFAULT_TARGET_MB = 64; +// 64 rows x 8 KiB payload dirties ~130 SQLite pages per commit, comfortably +// under the depot MAX_COMMIT_DIRTY_PAGES (320) cap and giving one uncompacted +// FDB delta per batch. +const DEFAULT_BATCH_ROWS = 64; +const DEFAULT_ROW_BYTES = 8 * 1024; +const DEFAULT_BUDGET_MS = 120_000; +const MEBIBYTE = 1024 * 1024; + +function finiteInt(value: number | undefined, fallback: number): number { + if (value === undefined) return fallback; + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`expected a positive finite number, got ${value}`); + } + return Math.floor(value); +} + +async function queryOne( + database: { execute: (sql: string, ...args: unknown[]) => Promise }, + sql: string, +): Promise { + const rows = await database.execute(sql); + if (!rows[0]) throw new Error(`query returned no rows: ${sql}`); + return rows[0] as T; +} + +async function storageStats(database: { + execute: (sql: string, ...args: unknown[]) => Promise; +}): Promise { + const [pageCount, pageSize, freelistCount] = await Promise.all([ + queryOne<{ page_count: number }>(database, "PRAGMA page_count"), + queryOne<{ page_size: number }>(database, "PRAGMA page_size"), + queryOne<{ freelist_count: number }>(database, "PRAGMA freelist_count"), + ]); + return { + pageCount: pageCount.page_count, + pageSize: pageSize.page_size, + freelistCount: freelistCount.freelist_count, + sizeBytes: pageCount.page_count * pageSize.page_size, + }; +} + +export const growDb = actor({ + options: { + // Generous per-action timeout. grow() self-limits with budgetMs and + // returns done=false before this fires. + actionTimeout: 300_000, + }, + db: db({ + onMigrate: async (database) => { + await database.execute(` + CREATE TABLE IF NOT EXISTS grow_rows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch INTEGER NOT NULL, + payload BLOB NOT NULL, + created_at INTEGER NOT NULL + ) + `); + }, + }), + actions: { + grow: async (c, input: GrowInput = {}): Promise => { + const startedAt = performance.now(); + const targetMb = finiteInt(input.targetMb, DEFAULT_TARGET_MB); + const batchRows = finiteInt(input.batchRows, DEFAULT_BATCH_ROWS); + const rowBytes = finiteInt(input.rowBytes, DEFAULT_ROW_BYTES); + const budgetMs = finiteInt(input.budgetMs, DEFAULT_BUDGET_MS); + const targetBytes = targetMb * MEBIBYTE; + + const placeholders = new Array(batchRows) + .fill("(?, randomblob(?), ?)") + .join(", "); + + let batchesThisCall = 0; + let rowsThisCall = 0; + let stats = await storageStats(c.db); + + while (stats.sizeBytes < targetBytes) { + if (performance.now() - startedAt >= budgetMs) { + return { + ...stats, + done: false, + targetBytes, + batchesThisCall, + rowsThisCall, + elapsedMs: Math.round(performance.now() - startedAt), + }; + } + + const now = Date.now(); + const args: unknown[] = []; + for (let i = 0; i < batchRows; i += 1) { + args.push(batchesThisCall, rowBytes, now); + } + + // One commit per batch so each iteration produces exactly one + // uncompacted delta in FDB. + await c.db.execute("BEGIN"); + try { + await c.db.execute( + `INSERT INTO grow_rows (batch, payload, created_at) VALUES ${placeholders}`, + ...args, + ); + await c.db.execute("COMMIT"); + } catch (err) { + await c.db.execute("ROLLBACK").catch(() => undefined); + throw err; + } + + batchesThisCall += 1; + rowsThisCall += batchRows; + stats = await storageStats(c.db); + } + + return { + ...stats, + done: true, + targetBytes, + batchesThisCall, + rowsThisCall, + elapsedMs: Math.round(performance.now() - startedAt), + }; + }, + stats: async (c): Promise => storageStats(c.db), + }, +}); diff --git a/examples/kitchen-sink/src/index.ts b/examples/kitchen-sink/src/index.ts index 6f91ac9536..0712f2446d 100644 --- a/examples/kitchen-sink/src/index.ts +++ b/examples/kitchen-sink/src/index.ts @@ -89,6 +89,7 @@ import { uniqueVarActor, } from "./actors/state/vars.ts"; // Testing +import { growDb } from "./actors/testing/grow-db.ts"; import { inlineClientActor } from "./actors/testing/inline-client.ts"; import { loadTestAgent } from "./actors/testing/load-test-agent.ts"; import { loadTestAgent2 } from "./actors/testing/load-test-agent-2.ts"; @@ -289,6 +290,7 @@ export const registry = setup({ sqliteRealworldBench, rawSqliteFuzzer, sqliteMemoryPressure, + growDb, mockAgenticLoop, sleepCloseFuzz, loadTestAgent, From ffb4bff6edae1c1495e0ea18c4ebc9dc9a89c7a3 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 7 Jul 2026 14:14:24 -0700 Subject: [PATCH 18/19] [slopfix] docs(self-hosting): correct postgres config format and document single vs multi-node nats --- .../docs/self-hosting/configuration.mdx | 2 +- .../content/docs/self-hosting/filesystem.mdx | 2 +- .../content/docs/self-hosting/postgres.mdx | 59 ++++++++++++------- .../self-hosting/production-checklist.mdx | 6 +- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/website/src/content/docs/self-hosting/configuration.mdx b/website/src/content/docs/self-hosting/configuration.mdx index 8fb19f129c..364a40f766 100644 --- a/website/src/content/docs/self-hosting/configuration.mdx +++ b/website/src/content/docs/self-hosting/configuration.mdx @@ -18,7 +18,7 @@ Rivet supports JSON, JSON5, JSONC, YAML, YML, and environment variable configura **Environment Variables** -Use the `RIVET__` prefix with `__` as separator to configure properties in the config. For example: set the `RIVET__postgres__url` environment variable for `postgres.url`. +Use the `RIVET__` prefix with `__` as separator to configure properties in the config. For example: set the `RIVET__POSTGRES__URL` environment variable for `postgres.url`. **Configuration Paths** diff --git a/website/src/content/docs/self-hosting/filesystem.mdx b/website/src/content/docs/self-hosting/filesystem.mdx index fbc083298d..53012c9841 100644 --- a/website/src/content/docs/self-hosting/filesystem.mdx +++ b/website/src/content/docs/self-hosting/filesystem.mdx @@ -21,7 +21,7 @@ For a single-node deployment, use the file system backend (RocksDB-based). For m ``` ```bash Environment-variables -RIVET__file_system__path="/var/lib/rivet/data" +RIVET__FILE_SYSTEM__PATH="/var/lib/rivet/data" ``` diff --git a/website/src/content/docs/self-hosting/postgres.mdx b/website/src/content/docs/self-hosting/postgres.mdx index ef737738fc..60731b1e22 100644 --- a/website/src/content/docs/self-hosting/postgres.mdx +++ b/website/src/content/docs/self-hosting/postgres.mdx @@ -10,11 +10,12 @@ PostgreSQL is the recommended backend for multi-node self-hosted deployments. It ## Overview -PostgreSQL is the storage and coordination backend for self-hosted Rivet deployments that run more than one engine node. Multiple engine nodes can share a single PostgreSQL instance with no extra coordination service to deploy. Rivet handles leader election, failover, and version sequencing internally. +PostgreSQL is the storage and coordination backend for self-hosted Rivet deployments. It can run a single engine node on its own, or back multiple engine nodes when paired with NATS. Rivet handles leader election, failover, and version sequencing internally. Use PostgreSQL when you need: -- **Multiple engine nodes** behind a load balancer for redundancy and horizontal scaling. +- **A durable, managed system of record** instead of local RocksDB storage. +- **Multiple engine nodes** behind a load balancer for redundancy and horizontal scaling (requires NATS). - **Multi-region deployments** (deploy one PostgreSQL instance per region, see [Multi-Region](/docs/self-hosting/multi-region)). - **High availability** with a managed or self-managed primary/replica failover setup. @@ -26,8 +27,23 @@ Pick your database backend based on how many engine nodes you run: - **Multi-node**: Use PostgreSQL as the database and NATS for pub/sub. PostgreSQL can be shared across engine nodes and is production-ready for light-to-moderate workloads, up to roughly 1,000 concurrent actors. - **Enterprise scale**: Beyond that, or for high-throughput realtime workloads, PostgreSQL is not the right fit. Contact [enterprise support](https://rivet.dev/sales) about FoundationDB. +## Single-Node vs Multi-Node + +Whether PostgreSQL runs Rivet single-node or multi-node depends on whether NATS is configured for pub/sub: + +- **Single-node (no NATS):** Configure only `postgres`. Pub/sub uses the in-memory driver and UniversalDB runs in single-node mode with an in-process commit resolver. Run exactly one engine node against this deployment. +- **Multi-node (with NATS):** Configure `postgres` **and** a top-level `nats` block for pub/sub. Pub/sub runs over NATS, and UniversalDB inherits that NATS config to run in multi-node mode, using NATS for follower-to-leader commit transport. Multiple engine nodes can then share the same PostgreSQL instance. + +Configure NATS for pub/sub to make PostgreSQL multi-node. PostgreSQL inherits the pub/sub NATS config automatically, so a single top-level `nats` block drives both pub/sub and UniversalDB multi-node coordination. See [Multi-Node Configuration](#multi-node-configuration) below. + + +Do not run more than one engine node against a PostgreSQL deployment without NATS configured. Without NATS, UniversalDB runs single-node: the first node takes leadership, and any additional node cannot obtain leadership and will eventually error. + + ## Basic Configuration +A single-node deployment needs only a `postgres` block: + ```json Configuration-file @@ -39,18 +55,14 @@ Pick your database backend based on how many engine nodes you run: ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://user:password@host:5432/database" +RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database" ``` -The configuration above is a complete single-node deployment and needs no pub/sub configuration, though for most single-node deployments the [file system backend](/docs/self-hosting/filesystem) (RocksDB) is the recommended choice. For multi-node deployments, add NATS as the pub/sub backend so engine nodes can coordinate (see below). - -## Pub/Sub (NATS) +## Multi-Node Configuration -Rivet uses a pub/sub backend for realtime messaging between engine nodes. Single-node deployments do not need any pub/sub configuration. - -Multi-node PostgreSQL deployments require NATS as the pub/sub backend so engine nodes can coordinate. Deploy 2+ NATS replicas for high availability. +To run multiple engine nodes against the same PostgreSQL instance, add a top-level `nats` block to configure NATS for pub/sub. PostgreSQL inherits this config to run multi-node. Point every engine node at the same PostgreSQL instance and the same NATS cluster: @@ -60,14 +72,21 @@ Multi-node PostgreSQL deployments require NATS as the pub/sub backend so engine "url": "postgresql://user:password@host:5432/database" }, "nats": { - "addresses": ["nats:4222"] + "addresses": ["nats-1:4222", "nats-2:4222"] } } ``` +```bash Environment-variables +RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database" +RIVET__NATS__ADDRESSES="nats-1:4222,nats-2:4222" +``` + -See the [production checklist](/docs/self-hosting/production-checklist#nats) and [Configuration](/docs/self-hosting/configuration) for details. +The top-level `nats` block configures pub/sub, and UniversalDB inherits it to enable multi-node mode. This is the recommended way to make PostgreSQL multi-node. For NATS high availability, run at least two NATS replicas. + +`postgres.nats` is for advanced setups only: set it to point UniversalDB at a different NATS cluster than the one used for pub/sub. Leave it unset and rely on inheritance from the top-level `nats` block unless you specifically need to separate the two. ## Requirements and Recommendations @@ -124,7 +143,7 @@ Use direct connection (not connection pooler). ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://pscale_api_.:@.pg.psdb.cloud:5432/postgres?sslmode=require" +RIVET__POSTGRES__URL="postgresql://pscale_api_.:@.pg.psdb.cloud:5432/postgres?sslmode=require" ``` @@ -148,7 +167,7 @@ Use direct connection on port `5432` (not connection pooler). ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=disable" +RIVET__POSTGRES__URL="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=disable" ``` @@ -171,8 +190,8 @@ Download the root certificate from your Supabase dashboard and specify its path. ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=require" -RIVET__postgres__ssl__root_cert_path="/path/to/supabase-ca.crt" +RIVET__POSTGRES__URL="postgresql://postgres:@db..supabase.co:5432/postgres?sslmode=require" +RIVET__POSTGRES__SSL__ROOT_CERT_PATH="/path/to/supabase-ca.crt" ``` @@ -196,7 +215,7 @@ To enable SSL for Postgres, add `sslmode=require` to your PostgreSQL connection ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://user:password@host.example.com:5432/database?sslmode=require" +RIVET__POSTGRES__URL="postgresql://user:password@host.example.com:5432/database?sslmode=require" ``` @@ -229,10 +248,10 @@ For databases using custom certificate authorities (e.g., Supabase) or requiring ``` ```bash Environment-variables -RIVET__postgres__url="postgresql://user:password@host:5432/database?sslmode=require" -RIVET__postgres__ssl__root_cert_path="/path/to/root-ca.crt" -RIVET__postgres__ssl__client_cert_path="/path/to/client.crt" -RIVET__postgres__ssl__client_key_path="/path/to/client.key" +RIVET__POSTGRES__URL="postgresql://user:password@host:5432/database?sslmode=require" +RIVET__POSTGRES__SSL__ROOT_CERT_PATH="/path/to/root-ca.crt" +RIVET__POSTGRES__SSL__CLIENT_CERT_PATH="/path/to/client.crt" +RIVET__POSTGRES__SSL__CLIENT_KEY_PATH="/path/to/client.key" ``` diff --git a/website/src/content/docs/self-hosting/production-checklist.mdx b/website/src/content/docs/self-hosting/production-checklist.mdx index ad9c653e0d..2b811eefce 100644 --- a/website/src/content/docs/self-hosting/production-checklist.mdx +++ b/website/src/content/docs/self-hosting/production-checklist.mdx @@ -34,7 +34,7 @@ Also review the [general production checklist](/docs/general/production-checklis ## PostgreSQL -- **Use PostgreSQL for multi-node and multi-region deployments.** Multiple engine nodes can share one PostgreSQL instance; no extra coordination service is required. PostgreSQL is production-ready for light-to-moderate workloads (up to roughly 1,000 concurrent actors) but is not built for enterprise scale. See [PostgreSQL](/docs/self-hosting/postgres). +- **Use PostgreSQL for multi-node and multi-region deployments.** Multiple engine nodes can share one PostgreSQL instance. Multi-node also requires NATS (see below). PostgreSQL is production-ready for light-to-moderate workloads (up to roughly 1,000 concurrent actors) but is not built for enterprise scale. See [PostgreSQL](/docs/self-hosting/postgres). - **Raise `max_connections`.** Each engine node opens well over a hundred connections under load. Size `max_connections` to at least `(number of engine nodes × 150)` plus headroom. PostgreSQL's default of `100` is too low. See [Connection Limits](/docs/self-hosting/postgres#connection-limits). - **Do not use a connection pooler.** Rivet requires direct connections. Do not put PgBouncer, Supavisor, or RDS Proxy in front of PostgreSQL. - **Give PostgreSQL dedicated resources.** Provision dedicated CPU, memory, and fast disk, and keep autovacuum healthy. PostgreSQL is the system of record for the whole deployment. @@ -45,8 +45,8 @@ Also review the [general production checklist](/docs/general/production-checklis ## NATS -- **Use NATS for pub/sub in multi-node deployments.** NATS is the pub/sub backend that coordinates realtime messaging between engine nodes. Single-node deployments do not need any pub/sub configuration. See [Configuration](/docs/self-hosting/configuration). -- **Deploy 2+ NATS replicas.** Run at least two NATS replicas for high availability. +- **NATS is required for multi-node PostgreSQL deployments.** Multiple engine nodes on PostgreSQL coordinate through NATS for pub/sub and UniversalDB commit transport. Single-node deployments (one engine node, or the RocksDB file system backend) do not need NATS. See [Single-Node vs Multi-Node](/docs/self-hosting/postgres#single-node-vs-multi-node). +- **Deploy 2+ NATS replicas.** For multi-node deployments, run at least two NATS replicas for high availability. ## Monitoring From 9ee4cfecde0521b90abf914c69ed3d3d0544a5f2 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Tue, 7 Jul 2026 17:25:02 -0700 Subject: [PATCH 19/19] chore: Clean up --- engine/packages/gasoline/src/db/kv/mod.rs | 11 ----------- self-host/compose/template/src/docker-compose.ts | 1 - 2 files changed, 12 deletions(-) diff --git a/engine/packages/gasoline/src/db/kv/mod.rs b/engine/packages/gasoline/src/db/kv/mod.rs index 38862982bc..3718e65723 100644 --- a/engine/packages/gasoline/src/db/kv/mod.rs +++ b/engine/packages/gasoline/src/db/kv/mod.rs @@ -2619,17 +2619,6 @@ impl Database for DatabaseKv { .context("failed to publish signal") .map_err(WorkflowError::Udb)?; - if signal_name == "pegboard_actor2_wake" { - tracing::info!( - ?ray_id, - ?workflow_id, - ?signal_id, - signal_name, - elapsed_ms = started_at.elapsed().as_secs_f64() * 1000.0, - "published workflow wake signal", - ); - } - self.bump(BumpSubSubject::SignalPublish { to_workflow_id: workflow_id, }); diff --git a/self-host/compose/template/src/docker-compose.ts b/self-host/compose/template/src/docker-compose.ts index 703559ced7..1018716cb2 100644 --- a/self-host/compose/template/src/docker-compose.ts +++ b/self-host/compose/template/src/docker-compose.ts @@ -308,7 +308,6 @@ export function generateDockerCompose(context: TemplateContext) { "RUST_LOG_ANSI_COLOR=1", "RUST_LOG=universaldb::driver::postgres=debug", "RIVET_OTEL_ENABLED=1", - "RIVET_OTEL_SAMPLER_RATIO=1", `RIVET_OTEL_GRPC_ENDPOINT=http://${context.getServiceHost("otel-collector", datacenter.name)}:4317`, // "RUST_LOG=debug,hyper=info", ],